diff --git a/.github/workflows/docc.yml b/.github/workflows/docc.yml new file mode 100644 index 0000000..a7946fb --- /dev/null +++ b/.github/workflows/docc.yml @@ -0,0 +1,56 @@ +name: DocC + +on: + push: + branches: ["main"] + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: macos-14 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Select Xcode 15.2 + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.2' + + - name: Build DocC + run: | + swift package --allow-writing-to-directory ./docs \ + generate-documentation --target LucaCore \ + --disable-indexing \ + --output-path ./docs \ + --transform-for-static-hosting \ + --hosting-base-path Luca + + # Add redirect from root to documentation + echo '' > docs/index.html + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: 'docs' + + - id: deployment + name: Deploy to GitHub Pages + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 69e10fa..452e7e3 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ xcuserdata/ # Artifacts .build/ .luca +docs/ diff --git a/Package.resolved b/Package.resolved index 3f8b191..3aba835 100644 --- a/Package.resolved +++ b/Package.resolved @@ -54,6 +54,24 @@ "version" : "4.0.0" } }, + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-docc-plugin", + "state" : { + "revision" : "3e4f133a77e644a5812911a0513aeb7288b07d06", + "version" : "1.4.5" + } + }, + { + "identity" : "swift-docc-symbolkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-docc-symbolkit", + "state" : { + "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", + "version" : "1.0.0" + } + }, { "identity" : "swift-log", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 0b4c714..db38b79 100644 --- a/Package.swift +++ b/Package.swift @@ -14,7 +14,8 @@ let package = Package( .package(url: "https://github.com/apple/swift-argument-parser", exact: "1.6.1"), .package(url: "https://github.com/apple/swift-crypto.git", exact: "4.0.0"), .package(url: "https://github.com/tuist/Noora", exact: "0.49.1"), - .package(url: "https://github.com/jpsim/Yams.git", exact: "6.1.0") + .package(url: "https://github.com/jpsim/Yams.git", exact: "6.1.0"), + .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0") ], targets: [ .executableTarget( diff --git a/Sources/LucaCore/Core/Downloader/Downloader.swift b/Sources/LucaCore/Core/Downloader/Downloader.swift index b178649..9090238 100644 --- a/Sources/LucaCore/Core/Downloader/Downloader.swift +++ b/Sources/LucaCore/Core/Downloader/Downloader.swift @@ -2,6 +2,21 @@ import Foundation +/// Downloads release archives and executables from remote URLs. +/// +/// The `Downloader` handles fetching tool releases from remote servers, +/// supporting both archive formats (ZIP, tar.gz) and standalone executables. +/// +/// ## Supported File Types +/// +/// - `.zip` - ZIP archives +/// - `.tar.gz` - Gzipped tar archives +/// - No extension - Treated as executable files +/// +/// ## Topics +/// +/// ### Downloading Files +/// - ``downloadRelease(at:)`` struct Downloader: Downloading { enum DownloaderError: Error, LocalizedError, Equatable { @@ -26,6 +41,12 @@ struct Downloader: Downloading { self.fileDownloader = fileDownloader } + /// Downloads a release from the specified URL. + /// + /// - Parameter url: The URL to download from. + /// - Returns: A URL to the downloaded file in a temporary location. + /// - Throws: ``DownloaderError/unsupportedFileType(_:)`` if the URL has + /// an unsupported file extension. func downloadRelease(at url: URL) async throws -> URL { if SupportedFileTypes.allCases.contains(where: { url.lastPathComponent.hasSuffix($0.rawValue) diff --git a/Sources/LucaCore/Core/Installer/Installer.swift b/Sources/LucaCore/Core/Installer/Installer.swift index 8046001..b1759cf 100644 --- a/Sources/LucaCore/Core/Installer/Installer.swift +++ b/Sources/LucaCore/Core/Installer/Installer.swift @@ -3,6 +3,34 @@ import Foundation import Noora +/// Downloads, verifies, and installs tools from remote URLs. +/// +/// The `Installer` orchestrates the complete installation process for development tools: +/// 1. Downloads the release archive or executable from the specified URL +/// 2. Validates the checksum (if provided) +/// 3. Extracts archives or handles executables +/// 4. Validates architecture compatibility +/// 5. Sets executable permissions +/// 6. Creates symlinks in the project's `.luca/active/` directory +/// +/// ## Usage +/// +/// ```swift +/// let installer = Installer( +/// fileManager: FileManager.default, +/// ignoreArchitectureCheck: false, +/// printer: Printer() +/// ) +/// try await installer.install(installationType: .spec(path: lucafilePath)) +/// ``` +/// +/// ## Topics +/// +/// ### Installing Tools +/// - ``install(installationType:)`` +/// +/// ### Installation Types +/// - ``InstallationType`` public struct Installer { enum InstallerError: Error, LocalizedError { @@ -53,6 +81,12 @@ public struct Installer { self.noora = noora } + /// Installs tools based on the specified installation type. + /// + /// - Parameter installationType: Specifies how to determine which tools to install. + /// Can be either `.spec` to read from a Lucafile, or `.github` to install directly + /// from a GitHub release. + /// - Throws: An error if downloading, extracting, or linking fails. public func install(installationType: InstallationType) async throws { if quiet { try await installQuietly(installationType: installationType) diff --git a/Sources/LucaCore/Core/SpecLoader/SpecLoader.swift b/Sources/LucaCore/Core/SpecLoader/SpecLoader.swift index 5fa043b..dc665e4 100644 --- a/Sources/LucaCore/Core/SpecLoader/SpecLoader.swift +++ b/Sources/LucaCore/Core/SpecLoader/SpecLoader.swift @@ -3,6 +3,32 @@ import Foundation import Yams +/// Loads and parses Lucafile specifications. +/// +/// The `SpecLoader` reads YAML-formatted Lucafiles and converts them into +/// ``Spec`` objects that define the tools required for a project. +/// +/// ## Lucafile Format +/// +/// A Lucafile is a YAML file with the following structure: +/// +/// ```yaml +/// --- +/// tools: +/// - name: SwiftLint +/// version: 0.61.0 +/// url: https://github.com/realm/SwiftLint/releases/... +/// binaryPath: bin/swiftlint +/// ``` +/// +/// ## Topics +/// +/// ### Loading Specs +/// - ``loadSpec(at:)`` +/// +/// ### Related Types +/// - ``Spec`` +/// - ``Tool`` struct SpecLoader: SpecLoading { enum SpecLoaderError: Error, LocalizedError { @@ -25,6 +51,12 @@ struct SpecLoader: SpecLoading { self.fileManager = fileManager } + /// Loads a spec from the specified file path. + /// + /// - Parameter path: The URL to the Lucafile. + /// - Returns: A ``Spec`` containing the parsed tool definitions. + /// - Throws: ``SpecLoaderError/missingSpec(_:)`` if the file doesn't exist, + /// or ``SpecLoaderError/invalidSpec(_:_:)`` if the YAML is malformed. func loadSpec(at path: URL) throws -> Spec { guard let data = fileManager.contents(atPath: path.path) else { throw SpecLoaderError.missingSpec(path.path) diff --git a/Sources/LucaCore/Core/SymLinker/SymLinker.swift b/Sources/LucaCore/Core/SymLinker/SymLinker.swift index 90c0487..0252b8c 100644 --- a/Sources/LucaCore/Core/SymLinker/SymLinker.swift +++ b/Sources/LucaCore/Core/SymLinker/SymLinker.swift @@ -2,6 +2,19 @@ import Foundation +/// Creates and manages symbolic links for installed tools. +/// +/// The `SymLinker` is responsible for creating symbolic links in the project's +/// `.luca/active/` directory that point to the actual tool binaries stored +/// in `~/.luca/tools/`. +/// +/// This allows projects to reference tools via a consistent path while +/// supporting multiple versions installed globally. +/// +/// ## Topics +/// +/// ### Creating Symlinks +/// - ``setSymLink(for:)`` struct SymLinker: SymLinking { enum SymLinkerError: Error, LocalizedError, Equatable { @@ -23,6 +36,12 @@ struct SymLinker: SymLinking { // MARK: - Internal + /// Creates a symbolic link for the specified tool. + /// + /// - Parameter tool: The tool to create a symlink for. + /// - Returns: The URL of the created symlink. + /// - Throws: ``SymLinkerError/missingBinaryFile(binaryName:expectedLocation:)`` + /// if the binary file doesn't exist at the expected location. @discardableResult func setSymLink(for tool: Tool) throws -> URL { let symLinkFile = fileManager.activeFolder diff --git a/Sources/LucaCore/Core/Unarchiver/Unarchiver.swift b/Sources/LucaCore/Core/Unarchiver/Unarchiver.swift index 3ca4d09..72fe6be 100644 --- a/Sources/LucaCore/Core/Unarchiver/Unarchiver.swift +++ b/Sources/LucaCore/Core/Unarchiver/Unarchiver.swift @@ -2,6 +2,20 @@ import Foundation +/// Extracts tool archives to their installation destination. +/// +/// The `Unarchiver` handles extracting ZIP and tar.gz archives using +/// system utilities (`unzip` and `tar`). +/// +/// ## Supported Formats +/// +/// - **ZIP** - Extracted using `/usr/bin/unzip` +/// - **tar.gz** - Extracted using `/usr/bin/tar` +/// +/// ## Topics +/// +/// ### Extracting Archives +/// - ``unarchive(filePath:installationDestination:)`` struct Unarchiver: Unarchiving { enum UnarchiverError: Error, LocalizedError { @@ -29,6 +43,15 @@ struct Unarchiver: Unarchiving { self.fileTypeDetector = fileTypeDetector } + /// Extracts an archive to the specified destination. + /// + /// - Parameters: + /// - filePath: The path to the archive file. + /// - installationDestination: The directory to extract files into. + /// - Throws: ``UnarchiverError/unrecognisedFileType(_:)`` if the file type + /// cannot be determined, ``UnarchiverError/notAnArchive(_:)`` if the file + /// is an executable, or ``UnarchiverError/failedToUnarchive(_:)`` if + /// extraction fails. func unarchive(filePath: URL, installationDestination: URL) throws { try fileManager.createDirectory(at: installationDestination, withIntermediateDirectories: true) diff --git a/Sources/LucaCore/LucaCore.docc/Contributing.md b/Sources/LucaCore/LucaCore.docc/Contributing.md new file mode 100644 index 0000000..2d6e1f1 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Contributing.md @@ -0,0 +1,149 @@ +# Contributing to Luca + +Learn how to contribute to the Luca project. + +## Overview + +Thank you for considering contributing to Luca! This guide will help you get started with the development process. + +## Ways to Contribute + +- Reporting bugs +- Suggesting features +- Writing or improving documentation +- Fixing bugs +- Implementing features +- Reviewing code +- Answering questions + +## Prerequisites + +Before you begin contributing, ensure you have: + +- Git installed +- Swift 5.7 or later +- Xcode (for developing on macOS) +- A GitHub account + +## Development Environment Setup + +### 1. Fork and Clone + +```bash +# Fork the repository on GitHub, then clone your fork +git clone https://github.com/YOUR-USERNAME/Luca.git +cd Luca +``` + +### 2. Add Upstream Remote + +```bash +git remote add upstream https://github.com/LucaTools/Luca.git +``` + +### 3. Create a Feature Branch + +```bash +git checkout -b feature/your-feature-name +``` + +## Building and Testing + +### Build the Project + +```bash +swift build +``` + +### Run Tests + +```bash +swift test +``` + +### Build Documentation Locally + +```bash +swift package --disable-sandbox preview-documentation --target LucaCore +``` + +This starts a local server at `http://localhost:8000` where you can preview the documentation. + +## Code Style Guidelines + +- Follow Swift's official style guide +- Write clear, readable code with descriptive names +- Add comments where necessary +- Write unit tests for new features + +## Commit Message Guidelines + +- Use the present tense ("Add feature" not "Added feature") +- Use the imperative mood ("Move cursor to..." not "Moves cursor to...") +- Limit the first line to 72 characters or less +- Reference issues and pull requests after the first line + +### Example Commit Message + +``` +Add support for downloading from private repositories + +This adds authentication options when specifying ZIP URLs. +Fixes #123 +``` + +## Submitting Changes + +### 1. Push Your Changes + +```bash +git push origin feature/your-feature-name +``` + +### 2. Create a Pull Request + +1. Go to your fork on GitHub +2. Select your branch +3. Click 'Pull Request' +4. Fill out the pull request template + +### 3. Code Review + +Request a code review from one of the maintainers. + +## Pull Request Checklist + +- [ ] Update documentation if interface changes +- [ ] Add tests for new functionality +- [ ] Ensure all tests pass +- [ ] Update CHANGELOG.md with your changes + +## Architecture Overview + +### Project Structure + +``` +Sources/ +├── LucaCLI/ # Command-line interface +│ └── Commands/ # CLI commands (install, uninstall, etc.) +└── LucaCore/ # Core library + ├── Core/ # Core functionality + └── Models/ # Data models +``` + +### Key Components + +- **Installer**: Orchestrates the tool installation process +- **SpecLoader**: Parses Lucafile configurations +- **Downloader**: Handles file downloads from URLs +- **Unarchiver**: Extracts archives (ZIP, tar.gz) +- **SymLinker**: Creates symlinks in project directories + +## Resources + +- [Issue Tracker](https://github.com/LucaTools/Luca/issues) +- [Pull Request Template](https://github.com/LucaTools/Luca/blob/main/.github/PULL_REQUEST_TEMPLATE.md) + +## Recognition + +Contributors are recognized in the project's README and CHANGELOG. diff --git a/Sources/LucaCore/LucaCore.docc/GettingStarted.md b/Sources/LucaCore/LucaCore.docc/GettingStarted.md new file mode 100644 index 0000000..08e9957 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/GettingStarted.md @@ -0,0 +1,91 @@ +# Getting Started with Luca + +Learn how to install Luca and set up your first project with managed tools. + +## Overview + +Luca is a lightweight tool manager for macOS that helps developers install, manage, and activate specific versions of development tools in their projects. It creates project-specific tool environments without polluting your global PATH. + +## Installation + +### Using the Install Script (Recommended) + +Install the latest stable version with a single command: + +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/LucaTools/LucaScripts/HEAD/install.sh)" +``` + +#### Pinning a Specific Version + +To pin a specific version of Luca for your project, create a `.luca-version` file before running the install script: + +```bash +echo "1.0.0" > .luca-version +``` + +### Building from Source + +If you prefer to build from source: + +```bash +git clone https://github.com/LucaTools/Luca.git +cd Luca +swift build -c release +cp -f .build/release/luca /usr/local/bin/luca +``` + +## Requirements + +- macOS 13.0 or later +- Swift 5.7 or later (for building from source) + +## Quick Start + +### 1. Create a Lucafile + +Create a `Lucafile` in your project's root directory: + +```yaml +--- +tools: + - name: SwiftLint + binaryPath: SwiftLintBinary.artifactbundle/swiftlint-0.61.0-macos/bin/swiftlint + version: 0.61.0 + url: https://github.com/realm/SwiftLint/releases/download/0.61.0/SwiftLintBinary.artifactbundle.zip +``` + +### 2. Install Tools + +Run Luca to download and install the tools: + +```bash +luca install +``` + +### 3. Use Your Tools + +Tools are symlinked to `.luca/active/` in your project directory: + +```bash +.luca/active/swiftlint --version +``` + +## Installing from GitHub Directly + +You can also install tools directly from GitHub releases without a Lucafile: + +```bash +luca install TogglesPlatform/ToggleGen@1.0.0 +``` + +If the release asset naming is ambiguous, specify the asset name: + +```bash +luca install krzysztofzablocki/sourcery@2.2.7 --asset sourcery-2.2.7.zip +``` + +## Next Steps + +- Learn about the configuration format +- Follow the tutorial for a hands-on introduction diff --git a/Sources/LucaCore/LucaCore.docc/LucaCore.md b/Sources/LucaCore/LucaCore.docc/LucaCore.md new file mode 100644 index 0000000..67b068e --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/LucaCore.md @@ -0,0 +1,76 @@ +# ``LucaCore`` + +A lightweight tool manager for macOS that helps developers install, manage, and activate specific versions of development tools. + +## Overview + +Luca helps you manage command-line tools by downloading, verifying, and installing binaries from GitHub releases. Define your tools in a `Lucafile` and let Luca handle the rest. + +### Key Features + +- **Version-specific installations**: Install specific versions of tools needed for your project +- **Project isolation**: Each project can have its own set of active tools +- **Simple specification**: Define required tools in a simple YAML file (Lucafile) +- **Zero configuration**: Just create a Lucafile and run `luca install` +- **No PATH pollution**: Tools are symlinked locally in your project directory +- **Idempotent operations**: Safe to run multiple times + +## Topics + +### Essentials + +- +- + +### Tutorials + +- + +### Contributing + +- + +### Core Components + +- ``Installer`` +- ``SymLinker`` +- ``SpecLoader`` +- ``Tool`` +- ``Spec`` + +### Downloading and Extraction + +- ``Downloader`` +- ``Unarchiver`` +- ``FileTypeDetector`` + +### Validation + +- ``ChecksumValidator`` +- ``ChecksumCalculator`` +- ``ArchitectureValidator`` + +### Tool Management + +- ``InstalledToolsLister`` +- ``LinkedToolsLister`` +- ``Uninstaller`` +- ``Unlinker`` + +### Git Integration + +- ``GitHookInstaller`` +- ``GitIgnoreManager`` + +### GitHub Integration + +- ``GitHubReleaseURLFactory`` +- ``ReleaseInfoProvider`` + +### Utilities + +- ``PermissionManager`` +- ``Printer`` +- ``BinaryFinder`` +- ``ToolFactory`` +- ``VersionLister`` diff --git a/Sources/LucaCore/LucaCore.docc/Lucafile.md b/Sources/LucaCore/LucaCore.docc/Lucafile.md new file mode 100644 index 0000000..0aa6cbe --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Lucafile.md @@ -0,0 +1,150 @@ +# Lucafile Configuration + +Learn how to configure your project's tool dependencies using a Lucafile. + +## Overview + +A Lucafile is a YAML configuration file that defines the tools your project needs. Luca reads this file to download, install, and manage tool versions specific to your project. + +## File Location + +Place your `Lucafile` (or `Lucafile.yml`) in your project's root directory. + +## Basic Structure + +```yaml +--- +tools: + - name: ToolName + version: 1.2.3 + url: https://example.com/tool-1.2.3.zip +``` + +## Configuration Fields + +### Required Fields + +| Field | Description | +|-------|-------------| +| `name` | Logical name for the tool (used for organizing and referencing) | +| `version` | The version to install | +| `url` | Remote URL to the archive or executable file | + +### Optional Fields + +| Field | Description | +|-------|-------------| +| `binaryPath` | Path to the binary within an archive (required for archives with nested structure) | +| `desiredBinaryName` | Custom name for the binary when stored locally | +| `checksum` | Hash value for verifying the downloaded file | +| `algorithm` | Hash algorithm used: `md5`, `sha1`, `sha256`, or `sha512` | + +## Examples + +### Simple Executable + +For tools distributed as standalone executables: + +```yaml +tools: + - name: FirebaseCLI + version: 14.12.1 + url: https://github.com/firebase/firebase-tools/releases/download/v14.12.1/firebase-tools-macos +``` + +### Archive with Binary Path + +For tools distributed in archives with nested directory structures: + +```yaml +tools: + - name: SwiftLint + binaryPath: SwiftLintBinary.artifactbundle/swiftlint-0.61.0-macos/bin/swiftlint + version: 0.61.0 + url: https://github.com/realm/SwiftLint/releases/download/0.61.0/SwiftLintBinary.artifactbundle.zip +``` + +### Simple Archive + +For tools where the binary is at the root of the archive: + +```yaml +tools: + - name: Tuist + binaryPath: tuist + version: 4.80.0 + url: https://github.com/tuist/tuist/releases/download/4.80.0/tuist.zip +``` + +### With Checksum Verification + +For enhanced security, verify downloads with checksums: + +```yaml +tools: + - name: SwiftLint + binaryPath: SwiftLintBinary.artifactbundle/swiftlint-0.61.0-macos/bin/swiftlint + version: 0.61.0 + url: https://github.com/realm/SwiftLint/releases/download/0.61.0/SwiftLintBinary.artifactbundle.zip + checksum: e0a6540d01434f436335a9b12c7fabc3a6f5c3e8... + algorithm: sha256 +``` + +### Complete Example + +A typical Lucafile with multiple tools: + +```yaml +--- +tools: + - name: FirebaseCLI + version: 14.12.1 + url: https://github.com/firebase/firebase-tools/releases/download/v14.12.1/firebase-tools-macos + + - name: SwiftLint + binaryPath: SwiftLintBinary.artifactbundle/swiftlint-0.61.0-macos/bin/swiftlint + version: 0.61.0 + url: https://github.com/realm/SwiftLint/releases/download/0.61.0/SwiftLintBinary.artifactbundle.zip + + - name: Tuist + binaryPath: tuist + version: 4.80.0 + url: https://github.com/tuist/tuist/releases/download/4.80.0/tuist.zip + + - name: Sourcery + binaryPath: bin/sourcery + version: 2.2.7 + url: https://github.com/krzysztofzablocki/Sourcery/releases/download/2.2.7/sourcery-2.2.7.zip + checksum: abc123... + algorithm: sha256 +``` + +## How Tools Are Stored + +When you run `luca install`, Luca: + +1. Downloads the specified files +2. Verifies checksums (if provided) +3. Extracts archives (if applicable) +4. Stores binaries in `~/.luca/tools/{name}/{version}/` +5. Creates symlinks in `.luca/active/` in your project + +## Best Practices + +### Version Pinning + +Always specify exact versions to ensure reproducible builds across your team. + +### Checksum Verification + +Use checksums for critical tools to verify integrity and protect against tampering. + +### Git Integration + +Add `.luca/active/` to your `.gitignore` file. Luca can manage this automatically with the `--install-git-hook` flag. + +## Related + +- +- ``Spec`` +- ``SpecLoader`` diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/InstallingTools.tutorial b/Sources/LucaCore/LucaCore.docc/Tutorials/InstallingTools.tutorial new file mode 100644 index 0000000..521ccf1 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/InstallingTools.tutorial @@ -0,0 +1,106 @@ +@Tutorial(time: 10) { + @Intro(title: "Installing Your First Tool") { + Learn how to install Luca and configure your first project with managed tools. + + In this tutorial, you'll install Luca, create a Lucafile, and set up + SwiftLint for your project. + } + + @Section(title: "Install Luca") { + @ContentAndMedia { + First, you need to install Luca on your machine. You can either use + the install script or build from source. + } + + @Steps { + @Step { + Open Terminal and run the install script to download and install + the latest version of Luca. + + @Code(name: "Terminal", file: "install-luca.sh") + } + + @Step { + Verify the installation by checking the version. + + @Code(name: "Terminal", file: "verify-install.sh") + } + } + } + + @Section(title: "Create a Lucafile") { + @ContentAndMedia { + A Lucafile defines the tools your project needs. Create one in your + project's root directory to specify which tools and versions to install. + } + + @Steps { + @Step { + Navigate to your project directory. + + @Code(name: "Terminal", file: "cd-project.sh") + } + + @Step { + Create a new file named `Lucafile` with your preferred text editor. + Start with the YAML header. + + @Code(name: "Lucafile", file: "Lucafile-01.yml") + } + + @Step { + Add a tools section with your first tool definition. Here we'll + add SwiftLint as an example. + + @Code(name: "Lucafile", file: "Lucafile-02.yml") + } + } + } + + @Section(title: "Install Tools") { + @ContentAndMedia { + With your Lucafile ready, use Luca to download and install the + specified tools. + } + + @Steps { + @Step { + Run `luca install` to download and set up all tools defined in + your Lucafile. + + @Code(name: "Terminal", file: "luca-install.sh") + } + + @Step { + Luca downloads the tool, extracts it, and creates a symlink in + `.luca/active/`. You can now use the tool directly. + + @Code(name: "Terminal", file: "use-swiftlint.sh") + } + } + } + + @Section(title: "Install from GitHub Directly") { + @ContentAndMedia { + You can also install tools directly from GitHub without modifying + your Lucafile. This is useful for quick installations or testing + different versions. + } + + @Steps { + @Step { + Install a tool by specifying the GitHub organization, repository, + and version. + + @Code(name: "Terminal", file: "install-github.sh") + } + + @Step { + If the release has multiple assets, specify which one to download + using the `--asset` flag. + + @Code(name: "Terminal", file: "install-asset.sh") + } + } + } +} diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Luca.tutorial b/Sources/LucaCore/LucaCore.docc/Tutorials/Luca.tutorial new file mode 100644 index 0000000..18f4f0e --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Luca.tutorial @@ -0,0 +1,19 @@ +@Tutorials(name: "Luca") { + @Intro(title: "Meet Luca") { + Learn how to use Luca to manage your development tools efficiently. + Luca helps you install, version, and organize command-line tools + specific to each of your projects. + } + + @Chapter(name: "Getting Started") { + Install Luca and set up your first Lucafile to manage project tools. + + @TutorialReference(tutorial: "doc:InstallingTools") + } + + @Chapter(name: "Managing Tools") { + Learn how to list, update, and remove tools from your projects. + + @TutorialReference(tutorial: "doc:ManagingTools") + } +} diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/ManagingTools.tutorial b/Sources/LucaCore/LucaCore.docc/Tutorials/ManagingTools.tutorial new file mode 100644 index 0000000..6a2bbfb --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/ManagingTools.tutorial @@ -0,0 +1,98 @@ +@Tutorial(time: 8) { + @Intro(title: "Managing Your Tools") { + Learn how to list, inspect, and remove tools managed by Luca. + + In this tutorial, you'll learn the commands for viewing installed tools, + checking which tools are linked in your project, and removing tools you + no longer need. + } + + @Section(title: "List Installed Tools") { + @ContentAndMedia { + Luca keeps track of all tools installed on your system. You can view + them along with their installed versions. + } + + @Steps { + @Step { + Run `luca installed` to see all tools and versions that have been + installed globally. + + @Code(name: "Terminal", file: "luca-installed.sh") + } + + @Step { + The output shows each tool name with its installed versions listed + below it. + + @Code(name: "Output", file: "installed-output.txt") + } + } + } + + @Section(title: "List Linked Tools") { + @ContentAndMedia { + Each project can have different tools linked. Use `luca linked` to + see which tools are active in your current project. + } + + @Steps { + @Step { + Navigate to your project directory and run `luca linked`. + + @Code(name: "Terminal", file: "luca-linked.sh") + } + + @Step { + The output shows detailed information about each linked tool, + including its version, binary name, and storage location. + + @Code(name: "Output", file: "linked-output.txt") + } + } + } + + @Section(title: "Unlink Tools") { + @ContentAndMedia { + Remove a tool's symlink from your project without uninstalling it + globally. This is useful when you no longer need a tool in a + specific project. + } + + @Steps { + @Step { + Use `luca unlink` with the binary name to remove the symlink. + + @Code(name: "Terminal", file: "luca-unlink.sh") + } + + @Step { + The tool remains installed globally and can be re-linked later + by running `luca install` again. + } + } + } + + @Section(title: "Uninstall Tools") { + @ContentAndMedia { + Completely remove a tool version from your system. This removes + the tool from the global installation directory. + } + + @Steps { + @Step { + To uninstall a specific version, use `luca uninstall` with the + tool name and version. + + @Code(name: "Terminal", file: "luca-uninstall.sh") + } + + @Step { + If you don't specify a version, Luca will prompt you to select + which version to uninstall. + + @Code(name: "Terminal", file: "luca-uninstall-prompt.sh") + } + } + } +} diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/Lucafile-01.yml b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/Lucafile-01.yml new file mode 100644 index 0000000..5b3da25 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/Lucafile-01.yml @@ -0,0 +1,2 @@ +--- +tools: diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/Lucafile-02.yml b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/Lucafile-02.yml new file mode 100644 index 0000000..ec3f104 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/Lucafile-02.yml @@ -0,0 +1,6 @@ +--- +tools: + - name: SwiftLint + binaryPath: SwiftLintBinary.artifactbundle/swiftlint-0.61.0-macos/bin/swiftlint + version: 0.61.0 + url: https://github.com/realm/SwiftLint/releases/download/0.61.0/SwiftLintBinary.artifactbundle.zip diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/cd-project.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/cd-project.sh new file mode 100644 index 0000000..e998814 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/cd-project.sh @@ -0,0 +1 @@ +cd ~/Projects/MyApp diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/install-asset.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/install-asset.sh new file mode 100644 index 0000000..0dc7965 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/install-asset.sh @@ -0,0 +1 @@ +luca install krzysztofzablocki/sourcery@2.2.7 --asset sourcery-2.2.7.zip diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/install-github.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/install-github.sh new file mode 100644 index 0000000..524d195 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/install-github.sh @@ -0,0 +1 @@ +luca install TogglesPlatform/ToggleGen@1.0.0 diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/install-luca.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/install-luca.sh new file mode 100644 index 0000000..82055a4 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/install-luca.sh @@ -0,0 +1 @@ +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/LucaTools/LucaScripts/HEAD/install.sh)" diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/installed-output.txt b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/installed-output.txt new file mode 100644 index 0000000..f5a50e0 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/installed-output.txt @@ -0,0 +1,11 @@ +FirebaseCLI: + - 14.12.1 +Sourcery: + - 2.2.5 + - 2.2.7 +SwiftLint: + - 0.53.0 + - 0.61.0 +tuist: + - 4.78.0 + - 4.80.0 diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/linked-output.txt b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/linked-output.txt new file mode 100644 index 0000000..c0167b7 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/linked-output.txt @@ -0,0 +1,8 @@ +SwiftLint: + version: 0.61.0 + binary: swiftlint + location: /Users/you/.luca/tools/SwiftLint/0.61.0/swiftlint +tuist: + version: 4.80.0 + binary: tuist + location: /Users/you/.luca/tools/tuist/4.80.0/tuist diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-install.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-install.sh new file mode 100644 index 0000000..14ac100 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-install.sh @@ -0,0 +1 @@ +luca install diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-installed.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-installed.sh new file mode 100644 index 0000000..97609c3 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-installed.sh @@ -0,0 +1 @@ +luca installed diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-linked.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-linked.sh new file mode 100644 index 0000000..71e855a --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-linked.sh @@ -0,0 +1 @@ +luca linked diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-uninstall-prompt.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-uninstall-prompt.sh new file mode 100644 index 0000000..6f86ab0 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-uninstall-prompt.sh @@ -0,0 +1,2 @@ +luca uninstall SwiftLint +# Luca will prompt you to select a version to uninstall diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-uninstall.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-uninstall.sh new file mode 100644 index 0000000..a99a253 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-uninstall.sh @@ -0,0 +1 @@ +luca uninstall SwiftLint@0.53.0 diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-unlink.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-unlink.sh new file mode 100644 index 0000000..45f6b69 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/luca-unlink.sh @@ -0,0 +1 @@ +luca unlink swiftlint diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/use-swiftlint.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/use-swiftlint.sh new file mode 100644 index 0000000..45e6cc6 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/use-swiftlint.sh @@ -0,0 +1,2 @@ +.luca/active/swiftlint --version +# Output: 0.61.0 diff --git a/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/verify-install.sh b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/verify-install.sh new file mode 100644 index 0000000..a16a4f6 --- /dev/null +++ b/Sources/LucaCore/LucaCore.docc/Tutorials/Resources/verify-install.sh @@ -0,0 +1 @@ +luca --version diff --git a/Sources/LucaCore/Models/Spec.swift b/Sources/LucaCore/Models/Spec.swift index 621f95c..ee5dc04 100644 --- a/Sources/LucaCore/Models/Spec.swift +++ b/Sources/LucaCore/Models/Spec.swift @@ -2,6 +2,33 @@ import Foundation +/// A specification defining the tools required for a project. +/// +/// A `Spec` represents the parsed contents of a Lucafile. It contains +/// an array of ``Tool`` definitions that Luca will install and manage. +/// +/// ## Lucafile Example +/// +/// ```yaml +/// --- +/// tools: +/// - name: SwiftLint +/// version: 0.61.0 +/// url: https://github.com/realm/SwiftLint/releases/... +/// - name: Tuist +/// version: 4.80.0 +/// url: https://github.com/tuist/tuist/releases/... +/// ``` +/// +/// ## Topics +/// +/// ### Properties +/// - ``tools`` +/// +/// ### Related Types +/// - ``Tool`` +/// - ``SpecLoader`` struct Spec: Codable { + /// The list of tools defined in the specification. let tools: [Tool] } diff --git a/Sources/LucaCore/Models/Tool.swift b/Sources/LucaCore/Models/Tool.swift index ca2ef0c..6f6b470 100644 --- a/Sources/LucaCore/Models/Tool.swift +++ b/Sources/LucaCore/Models/Tool.swift @@ -3,6 +3,37 @@ import Foundation /// A single installable tool (binary) described in the spec file. +/// +/// A `Tool` represents a command-line tool that can be downloaded, installed, +/// and linked by Luca. Each tool is identified by a name and version, and +/// includes information about where to download it and how to locate the +/// binary within the downloaded archive. +/// +/// ## Example +/// +/// ```yaml +/// - name: SwiftLint +/// version: 0.61.0 +/// url: https://github.com/realm/SwiftLint/releases/download/0.61.0/SwiftLintBinary.artifactbundle.zip +/// binaryPath: SwiftLintBinary.artifactbundle/swiftlint-0.61.0-macos/bin/swiftlint +/// ``` +/// +/// ## Topics +/// +/// ### Required Properties +/// - ``name`` +/// - ``version`` +/// - ``url`` +/// +/// ### Optional Properties +/// - ``binaryPath`` +/// - ``desiredBinaryName`` +/// - ``checksum`` +/// - ``algorithm`` +/// +/// ### Computed Properties +/// - ``expectedBinaryName`` +/// - ``effectiveBinaryPath`` struct Tool: Codable { /// Logical name of the tool (used for directory hierarchy). let name: String