Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions Sources/LucaCLI/Commands/CalculateChecksumCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,27 @@ struct CalculateChecksumCommand: AsyncParsableCommand {

static let configuration = CommandConfiguration(
commandName: "calculate-checksum",
abstract: "Calculates the checksum of a file."
abstract: "Calculate the checksum of a file.",
discussion: """
Computes a hash for integrity verification.
Use this to generate checksums for Lucafile entries.
"""
)

@Argument(help: "The path to the file.")
@Argument(help: ArgumentHelp(
"Path to the file to hash.",
discussion: """
Example:
luca calculate-checksum ./downloads/tool.zip
""",
valueName: "path"
))
var file: String

@Option(help: "The algorithm to use.")
@Option(help: ArgumentHelp(
"Hash algorithm to use.",
valueName: "algorithm"
))
var algorithm: ChecksumAlgorithm = .sha256

func run() async throws {
Expand Down
170 changes: 111 additions & 59 deletions Sources/LucaCLI/Commands/InstallCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,91 +26,143 @@ struct InstallCommand: AsyncParsableCommand {

static let configuration = CommandConfiguration(
commandName: "install",
abstract: """
Install versions of tools defined in a spec file or directly from GitHub releases.

Example of usages:

# From a spec

luca install
luca install --spec ./Lucafile

# Individual installations

luca install TogglesPlatform/Toggles@1.0.0
luca install krzysztofzablocki/Sourcery@2.2.7
--asset sourcery-2.2.7.zip
--binary-path bin/sourcery
luca install firebase/firebase-tools@v14.12.1
--desired-binary-name firebase

# Inline installations

luca install
--name ToggleGen
--version 1.0.0
--url https://github.com/TogglesPlatform/ToggleGen/releases/download/1.0.0/ToggleGen-macOS-universal-binary.zip

luca install
--name ToggleGen
--version 1.0.0
--url https://github.com/TogglesPlatform/ToggleGen/releases/download/1.0.0/ToggleGen-macOS-universal-binary.zip
--checksum e0a6540d01434f436335a9f48405ffd008020043c9b1c21d38742fc8e7c9fdc3
--algorithm sha256

luca install
--name Sourcery
--version 2.2.7
--url https://github.com/krzysztofzablocki/Sourcery/releases/download/2.2.7/sourcery-2.2.7.zip
--binary-path bin/sourcery

luca install
--name FirebaseCLI
--version 14.12.1
--url https://github.com/firebase/firebase-tools/releases/download/v14.12.1/firebase-tools-macos
--desired-binary-name firebase
--ignore-arch-check
"""
abstract: "Install tools from a spec file or GitHub releases.",
discussion: """
Supports three installation modes:
- Spec file: Install all tools defined in a Lucafile
- Individual: Install from GitHub using org/repo@version format
- Inline: Install from a direct URL with explicit parameters

See parameter help for detailed examples.
"""
)

@Option(name: .long, help: "The location of the spec file.")
@Option(name: .long, help: ArgumentHelp(
"Path to the spec file.",
discussion: """
Defaults to './Lucafile' in the current directory if not specified.
Examples:
luca install
luca install --spec ./config/Lucafile
""",
valueName: "path"
))
var spec: String?

@Argument(help: "Tool to install in format 'organization/repository@version'")
@Argument(help: ArgumentHelp(
"Tool to install in 'org/repo@version' format.",
discussion: """
Examples:
luca install TogglesPlatform/Toggles@1.0.0
luca install krzysztofzablocki/Sourcery@2.2.7 --asset sourcery-2.2.7.zip
""",
valueName: "org/repo@version"
))
var identifier: String?

@Option(help: "Name of the tool to install.")
@Option(help: ArgumentHelp(
"Name of the tool (inline mode).",
discussion: """
Requires --version and --url.
Example:
luca install --name ToggleGen --version 1.0.0 \\
--url https://github.com/.../ToggleGen.zip
""",
valueName: "tool-name"
))
var name: String?

@Option(help: "Version of the tool to install.")
@Option(help: ArgumentHelp(
"Version of the tool (inline mode).",
discussion: "Requires --name and --url.",
valueName: "version"
))
var version: String?

@Option(help: "URL of the asset for the tool to install.")
@Option(help: ArgumentHelp(
"URL of the asset to download (inline mode).",
discussion: "Requires --name and --version.",
valueName: "url"
))
var url: String?

@Option(help: "Filename of the asset associated with the release.")
@Option(help: ArgumentHelp(
"Filename of the release asset to download.",
discussion: """
Use when the release contains multiple assets.
Example:
luca install krzysztofzablocki/Sourcery@2.2.7 \\
--asset sourcery-2.2.7.zip
""",
valueName: "filename"
))
var asset: String?

@Option(help: "Binary path for the asset associated with the release.")
@Option(help: ArgumentHelp(
"Path to the executable inside the archive.",
discussion: """
Required when the binary is nested within the archive.
Example:
--binary-path bin/sourcery
""",
valueName: "path"
))
var binaryPath: String?

@Option(help: "Name of the binary stored locally. Requires `url` to point to an executable file, ignored otherwise.")
@Option(help: ArgumentHelp(
"Local name for the installed binary.",
discussion: """
Useful when the downloaded file has a different name.
Example:
luca install firebase/firebase-tools@v14.12.1 \\
--desired-binary-name firebase
""",
valueName: "name"
))
var desiredBinaryName: String?

@Option(help: "Checksum of the asset associated with the release.")
@Option(help: ArgumentHelp(
"Expected checksum for integrity verification.",
discussion: """
Use with --algorithm.
Example:
--checksum e0a6540d01434f436335a9f48405ffd00802...
""",
valueName: "hash"
))
var checksum: String?

@Option(help: "Algorithm to use to verify the integrity of the asset.")
@Option(help: ArgumentHelp(
"Hash algorithm for checksum verification.",
valueName: "algorithm"
))
var algorithm: ChecksumAlgorithm?

@Flag(help: "Skip architecture compatibility validation during installation.")
@Flag(help: ArgumentHelp(
"Skip architecture compatibility validation.",
discussion: """
Use for platform-independent executables.
Example:
luca install firebase/firebase-tools@v14.12.1 \\
--desired-binary-name firebase \\
--ignore-arch-check
"""
))
var ignoreArchCheck: Bool = false

@Flag(inversion: .prefixedNo, help: "Install the post-checkout git hook in the current repository.")
@Flag(inversion: .prefixedNo, help: ArgumentHelp(
"Install the post-checkout git hook.",
discussion: """
Enabled by default. Automatically runs 'luca install' after git checkout.
Use --no-install-post-checkout-git-hook to disable.
"""
))
var installPostCheckoutGitHook: Bool = true

@Flag(help: "Suppress all output except final success message.")
@Flag(help: ArgumentHelp(
"Suppress output except final success message.",
discussion: "Useful for CI/CD pipelines or scripting."
))
var quiet: Bool = false

func run() async throws {
Expand Down
6 changes: 5 additions & 1 deletion Sources/LucaCLI/Commands/InstalledCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ struct InstalledCommand: AsyncParsableCommand {

static let configuration = CommandConfiguration(
commandName: "installed",
abstract: "List installed tools and versions."
abstract: "List installed tools and versions.",
discussion: """
Shows all tools in the local cache with their installed versions.
Use 'luca linked' to see which versions are currently active.
"""
)

func run() async throws {
Expand Down
6 changes: 5 additions & 1 deletion Sources/LucaCLI/Commands/LinkedCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ struct LinkedCommand: AsyncParsableCommand {

static let configuration = CommandConfiguration(
commandName: "linked",
abstract: "List tools linked in the current project."
abstract: "List tools linked in the current project.",
discussion: """
Shows all symlinks in the active folder with their versions and paths.
Use 'luca installed' to see all available tool versions.
"""
)

func run() async throws {
Expand Down
25 changes: 17 additions & 8 deletions Sources/LucaCLI/Commands/LucaCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@ struct LucaCommand: AsyncParsableCommand {
commandName: "luca",
abstract: "A modern tool manager that helps you install and manage development tools.",
version: version,
subcommands: [
CalculateChecksumCommand.self,
InstallCommand.self,
InstalledCommand.self,
LinkedCommand.self,
UninstallCommand.self,
UnlinkCommand.self
]
groupedSubcommands: [
CommandGroup(name: "Installation", subcommands: [
InstallCommand.self,
UninstallCommand.self
]),
CommandGroup(name: "Linking", subcommands: [
LinkedCommand.self,
UnlinkCommand.self
]),
CommandGroup(name: "Listing", subcommands: [
InstalledCommand.self
]),
CommandGroup(name: "Validation", subcommands: [
CalculateChecksumCommand.self
])
],
defaultSubcommand: InstallCommand.self
)
}
16 changes: 14 additions & 2 deletions Sources/LucaCLI/Commands/UninstallCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,22 @@ struct UninstallCommand: AsyncParsableCommand {

static let configuration = CommandConfiguration(
commandName: "uninstall",
abstract: "Uninstall a specific tool version."
abstract: "Uninstall a specific tool version.",
discussion: """
Removes an installed tool version from the local cache.
If no version is specified, prompts for selection interactively.
"""
)

@Argument(help: "The tool to uninstall, optionally with version (e.g. SwiftLint@0.61.0)")
@Argument(help: ArgumentHelp(
"Tool to uninstall, optionally with version.",
discussion: """
Examples:
luca uninstall SwiftLint
luca uninstall SwiftLint@0.61.0
""",
valueName: "tool[@version]"
))
var tool: String

func run() async throws {
Expand Down
15 changes: 13 additions & 2 deletions Sources/LucaCLI/Commands/UnlinkCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,21 @@ struct UnlinkCommand: AsyncParsableCommand {

static let configuration = CommandConfiguration(
commandName: "unlink",
abstract: "Removes a specific symlink from the active folder."
abstract: "Remove a symlink from the active folder.",
discussion: """
Removes the symlink without uninstalling the underlying tool.
The tool remains available for re-linking to a different version.
"""
)

@Argument(help: "The name of the symlink to remove (e.g. swiftlint)")
@Argument(help: ArgumentHelp(
"Name of the symlink to remove.",
discussion: """
Example:
luca unlink swiftlint
""",
valueName: "name"
))
var symlink: String

func run() async throws {
Expand Down