From eafe2a2b94ffc6c8996ef506ed53148245e6be76 Mon Sep 17 00:00:00 2001 From: laosb Date: Sun, 5 Oct 2025 16:01:45 +0800 Subject: [PATCH 1/7] fix: Rename bundles to archives in artifact bundle index --- Package.swift | 4 ++-- .../TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift | 2 +- Sources/TailwindCSSCLIArtifactBundler/Models.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Package.swift b/Package.swift index f28ff15..d72f61b 100644 --- a/Package.swift +++ b/Package.swift @@ -25,8 +25,8 @@ let package = Package( .binaryTarget( name: "TailwindCSSCLI", url: - "https://github.com/laosb/SwiftTailwind/releases/download/1.1.0.test.0%2Btw.4.1.14/tailwindcss.artifactbundleindex", - checksum: "cdc10d3dbd0ad593d68df5564f22c5fc6ffe57facb30a8665d0be073e7fb9c66" + "https://github.com/laosb/SwiftTailwind/releases/download/1.1.0-test.2+tw.4.1.14/tailwindcss.artifactbundleindex", + checksum: "4462492d557c16c07a3c8c07980eea54e4460d925c1b9a097ad91f1c901440ec" ), .target( name: "SwiftTailwindExample", diff --git a/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift index 4141e9a..bc219f2 100644 --- a/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift +++ b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift @@ -225,7 +225,7 @@ class ArtifactBundleBuilder { let index = ArtifactBundleIndex( schemaVersion: "1.0", - bundles: bundles + archives: bundles ) let encoder = JSONEncoder() diff --git a/Sources/TailwindCSSCLIArtifactBundler/Models.swift b/Sources/TailwindCSSCLIArtifactBundler/Models.swift index 6d74643..79214ad 100644 --- a/Sources/TailwindCSSCLIArtifactBundler/Models.swift +++ b/Sources/TailwindCSSCLIArtifactBundler/Models.swift @@ -28,7 +28,7 @@ struct ArtifactVariant: Codable { /// Represents the .artifactbundleindex file structure struct ArtifactBundleIndex: Codable { let schemaVersion: String - let bundles: [Bundle] + let archives: [Bundle] // The proposal says it's "bundles" but the actual implementation uses "archives" } struct Bundle: Codable { From b4f1cf9dbd179b35cca2ca1d3fd781950f9746c8 Mon Sep 17 00:00:00 2001 From: laosb Date: Sun, 5 Oct 2025 17:25:53 +0800 Subject: [PATCH 2/7] refactor: Update TailwindCSSCLI binary target and refactor builder --- Package.swift | 4 +- .../ArtifactBundleBuilder.swift | 101 +----------------- 2 files changed, 4 insertions(+), 101 deletions(-) diff --git a/Package.swift b/Package.swift index d72f61b..81ffc58 100644 --- a/Package.swift +++ b/Package.swift @@ -25,8 +25,8 @@ let package = Package( .binaryTarget( name: "TailwindCSSCLI", url: - "https://github.com/laosb/SwiftTailwind/releases/download/1.1.0-test.2+tw.4.1.14/tailwindcss.artifactbundleindex", - checksum: "4462492d557c16c07a3c8c07980eea54e4460d925c1b9a097ad91f1c901440ec" + "https://github.com/laosb/SwiftTailwind/releases/download/1.1.0-test.3+tw.4.1.14/tailwindcss.artifactbundleindex", + checksum: "3287be503b5d954d1946a110cf2a1d14d37f9941983afe55f1743ddb5b487392" ), .target( name: "SwiftTailwindExample", diff --git a/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift index bc219f2..043eae6 100644 --- a/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift +++ b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift @@ -1,11 +1,10 @@ -import Crypto import Foundation class ArtifactBundleBuilder { private let version: String private let workDir: String private let outputDir: String - private let fileManager = FileManager.default + let fileManager = FileManager.default private let binaryConfigurations: [BinaryConfiguration] = [ BinaryConfiguration(binaryName: "tailwindcss-linux-x64", triple: "x86_64-unknown-linux-gnu"), @@ -106,20 +105,6 @@ class ArtifactBundleBuilder { return BundleInfo(fileName: zipFileName, checksum: checksum, triple: config.triple) } - private func downloadFile(from urlString: String, to destination: String) throws { - guard let url = URL(string: urlString) else { - throw ArtifactBundleError.invalidURL(urlString) - } - - let data = try Data(contentsOf: url) - try data.write(to: URL(fileURLWithPath: destination)) - } - - private func makeExecutable(path: String) throws { - let attributes = [FileAttributeKey.posixPermissions: 0o755] - try fileManager.setAttributes(attributes, ofItemAtPath: path) - } - private func createInfoJSON(bundleDir: String, binaryPath: String, triple: String) throws { let artifact = Artifact( version: version, @@ -142,71 +127,6 @@ class ArtifactBundleBuilder { try jsonData.write(to: URL(fileURLWithPath: infoPath)) } - private func createZipFile(bundleDir: String, zipPath: String) throws { - // Remove existing ZIP file if it exists - if fileManager.fileExists(atPath: zipPath) { - try fileManager.removeItem(atPath: zipPath) - } - - let bundleDirURL = URL(fileURLWithPath: bundleDir) - let workDirURL = bundleDirURL.deletingLastPathComponent() - let bundleName = bundleDirURL.lastPathComponent - - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/zip") - process.arguments = ["-r", zipPath, bundleName] - process.currentDirectoryURL = workDirURL - - try process.run() - process.waitUntilExit() - - guard process.terminationStatus == 0 else { - throw ArtifactBundleError.zipCreationFailed - } - } - - /// Computes the SHA256 checksum of a file. - /// - /// If `usingSHA256Directly` is true, it uses Swift Crypto's SHA256 implementation. - /// This is to workaround https://github.com/swiftlang/swift-package-manager/issues/9219. - private func computeChecksum( - filePath: String, - usingSHA256Directly: Bool = false - ) throws -> String { - if usingSHA256Directly { - // Use swift-crypto's SHA256 implementation - let fileURL = URL(fileURLWithPath: filePath) - let data = try Data(contentsOf: fileURL) - let hash = SHA256.hash(data: data) - return hash.compactMap { String(format: "%02x", $0) }.joined() - } else { - // Use swift package compute-checksum command - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/swift") - process.arguments = ["package", "compute-checksum", filePath] - - let pipe = Pipe() - process.standardOutput = pipe - - try process.run() - process.waitUntilExit() - - guard process.terminationStatus == 0 else { - throw ArtifactBundleError.checksumComputationFailed - } - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8)?.trimmingCharacters( - in: .whitespacesAndNewlines) - - guard let checksum = output, !checksum.isEmpty else { - throw ArtifactBundleError.checksumComputationFailed - } - - return checksum - } - } - private func generateArtifactBundleIndex(bundleInfos: [BundleInfo]) throws { print("Generating tailwindcss.artifactbundleindex...") @@ -219,7 +139,7 @@ class ArtifactBundleBuilder { Bundle( fileName: bundleInfo.fileName, checksum: bundleInfo.checksum, - supportedTriples: [bundleInfo.triple] + supportedTriples: expandingTriple(bundleInfo.triple) ) } @@ -236,20 +156,3 @@ class ArtifactBundleBuilder { try jsonData.write(to: URL(fileURLWithPath: indexPath)) } } - -enum ArtifactBundleError: Error, LocalizedError { - case invalidURL(String) - case zipCreationFailed - case checksumComputationFailed - - var errorDescription: String? { - switch self { - case .invalidURL(let url): - return "Invalid URL: \(url)" - case .zipCreationFailed: - return "Failed to create ZIP file" - case .checksumComputationFailed: - return "Failed to compute checksum" - } - } -} From caa5c6651b394e2e7382f57c8f08820d83b7cf93 Mon Sep 17 00:00:00 2001 From: laosb Date: Sun, 5 Oct 2025 17:37:39 +0800 Subject: [PATCH 3/7] fix: expand triple for bundle info.json too. --- ...rtifactBundleBuilder+File Operations.swift | 41 +++++++++++++++++ ...rtifactBundleBuilder+computeChecksum.swift | 46 +++++++++++++++++++ ...rtifactBundleBuilder+expandingTriple.swift | 29 ++++++++++++ .../ArtifactBundleBuilder.swift | 2 +- .../ArtifactBundleError.swift | 18 ++++++++ 5 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+File Operations.swift create mode 100644 Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+computeChecksum.swift create mode 100644 Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+expandingTriple.swift create mode 100644 Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleError.swift diff --git a/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+File Operations.swift b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+File Operations.swift new file mode 100644 index 0000000..19f43fd --- /dev/null +++ b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+File Operations.swift @@ -0,0 +1,41 @@ +import Foundation + +extension ArtifactBundleBuilder { + func downloadFile(from urlString: String, to destination: String) throws { + guard let url = URL(string: urlString) else { + throw ArtifactBundleError.invalidURL(urlString) + } + + let data = try Data(contentsOf: url) + try data.write(to: URL(fileURLWithPath: destination)) + } + + func makeExecutable(path: String) throws { + let attributes = [FileAttributeKey.posixPermissions: 0o755] + try fileManager.setAttributes(attributes, ofItemAtPath: path) + } + + func createZipFile(bundleDir: String, zipPath: String) throws { + // Remove existing ZIP file if it exists + if fileManager.fileExists(atPath: zipPath) { + try fileManager.removeItem(atPath: zipPath) + } + + let bundleDirURL = URL(fileURLWithPath: bundleDir) + let workDirURL = bundleDirURL.deletingLastPathComponent() + let bundleName = bundleDirURL.lastPathComponent + + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/zip") + process.arguments = ["-r", zipPath, bundleName] + process.currentDirectoryURL = workDirURL + + try process.run() + process.waitUntilExit() + + guard process.terminationStatus == 0 else { + throw ArtifactBundleError.zipCreationFailed + } + } + +} diff --git a/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+computeChecksum.swift b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+computeChecksum.swift new file mode 100644 index 0000000..7a5f906 --- /dev/null +++ b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+computeChecksum.swift @@ -0,0 +1,46 @@ +import Crypto +import Foundation + +extension ArtifactBundleBuilder { + /// Computes the SHA256 checksum of a file. + /// + /// If `usingSHA256Directly` is true, it uses Swift Crypto's SHA256 implementation. + /// This is to workaround https://github.com/swiftlang/swift-package-manager/issues/9219. + func computeChecksum( + filePath: String, + usingSHA256Directly: Bool = false + ) throws -> String { + if usingSHA256Directly { + // Use swift-crypto's SHA256 implementation + let fileURL = URL(fileURLWithPath: filePath) + let data = try Data(contentsOf: fileURL) + let hash = SHA256.hash(data: data) + return hash.compactMap { String(format: "%02x", $0) }.joined() + } else { + // Use swift package compute-checksum command + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/swift") + process.arguments = ["package", "compute-checksum", filePath] + + let pipe = Pipe() + process.standardOutput = pipe + + try process.run() + process.waitUntilExit() + + guard process.terminationStatus == 0 else { + throw ArtifactBundleError.checksumComputationFailed + } + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8)?.trimmingCharacters( + in: .whitespacesAndNewlines) + + guard let checksum = output, !checksum.isEmpty else { + throw ArtifactBundleError.checksumComputationFailed + } + + return checksum + } + } +} diff --git a/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+expandingTriple.swift b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+expandingTriple.swift new file mode 100644 index 0000000..77146b1 --- /dev/null +++ b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder+expandingTriple.swift @@ -0,0 +1,29 @@ +extension ArtifactBundleBuilder { + /// Expands a list of triples into a stricter list of triples. + /// + /// To workaround https://github.com/swiftlang/swift-package-manager/issues/7362. + func expandingTriple(_ triple: String) -> [String] { + switch triple { + case "aarch64-apple-darwin": + [ + "aarch64-apple-darwin", + "arm64-apple-macosx12.0", + "arm64-apple-macosx13.0", + "arm64-apple-macosx14.0", + "arm64-apple-macosx15.0", + "arm64-apple-macosx26.0", + ] + case "x86_64-apple-darwin": + [ + "x86_64-apple-darwin", + "x86_64-apple-macosx12.0", + "x86_64-apple-macosx13.0", + "x86_64-apple-macosx14.0", + "x86_64-apple-macosx15.0", + "x86_64-apple-macosx26.0", + ] + // TODO: Does linux need more detailed triple variants? + default: [triple] + } + } +} diff --git a/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift index 043eae6..eef4027 100644 --- a/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift +++ b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleBuilder.swift @@ -110,7 +110,7 @@ class ArtifactBundleBuilder { version: version, type: "executable", variants: [ - ArtifactVariant(path: binaryPath, supportedTriples: [triple]) + ArtifactVariant(path: binaryPath, supportedTriples: expandingTriple(triple)) ] ) diff --git a/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleError.swift b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleError.swift new file mode 100644 index 0000000..4bb59e2 --- /dev/null +++ b/Sources/TailwindCSSCLIArtifactBundler/ArtifactBundleError.swift @@ -0,0 +1,18 @@ +import Foundation + +enum ArtifactBundleError: Error, LocalizedError { + case invalidURL(String) + case zipCreationFailed + case checksumComputationFailed + + var errorDescription: String? { + switch self { + case .invalidURL(let url): + return "Invalid URL: \(url)" + case .zipCreationFailed: + return "Failed to create ZIP file" + case .checksumComputationFailed: + return "Failed to compute checksum" + } + } +} From da0c9046ae4e2bf91d0383a7420caa995ba68946 Mon Sep 17 00:00:00 2001 From: laosb Date: Sun, 5 Oct 2025 17:44:50 +0800 Subject: [PATCH 4/7] fix: Update TailwindCSSCLI binary target to 1.1.0-test.4 --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 81ffc58..ea0e1a8 100644 --- a/Package.swift +++ b/Package.swift @@ -25,8 +25,8 @@ let package = Package( .binaryTarget( name: "TailwindCSSCLI", url: - "https://github.com/laosb/SwiftTailwind/releases/download/1.1.0-test.3+tw.4.1.14/tailwindcss.artifactbundleindex", - checksum: "3287be503b5d954d1946a110cf2a1d14d37f9941983afe55f1743ddb5b487392" + "https://github.com/laosb/SwiftTailwind/releases/download/1.1.0-test.4+tw.4.1.14/tailwindcss.artifactbundleindex", + checksum: "ec4df49e361db5fc3159c431e5661af2e1b22d8575c64ce0482a8e84cfd93d04" ), .target( name: "SwiftTailwindExample", From d637b95450b652bec18ca083796bc1c93fff8b48 Mon Sep 17 00:00:00 2001 From: laosb Date: Thu, 23 Oct 2025 20:01:04 +0800 Subject: [PATCH 5/7] chore: Add Swift 6.2 to CI matrix and cache packages --- .github/workflows/ci.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9386b4..5efff76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,13 +12,22 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - swift-version: ["6.1"] + swift-version: ["6.1", "6.2"] container: image: swift:${{ matrix.swift-version }} steps: - name: Checkout code uses: actions/checkout@v4 + - name: Cache Swift packages + uses: actions/cache@v4 + with: + path: .build + key: ${{ runner.os }}-spm-${{ matrix.swift-version }}-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm-${{ matrix.swift-version }}- + ${{ runner.os }}-spm- + - name: Build package run: swift build --verbose @@ -30,7 +39,7 @@ jobs: runs-on: macos-latest strategy: matrix: - swift-version: ["6.1"] + swift-version: ["6.1", "6.2"] steps: - name: Checkout code uses: actions/checkout@v4 @@ -40,6 +49,15 @@ jobs: with: swift-version: ${{ matrix.swift-version }} + - name: Cache Swift packages + uses: actions/cache@v4 + with: + path: .build + key: ${{ runner.os }}-spm-${{ matrix.swift-version }}-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm-${{ matrix.swift-version }}- + ${{ runner.os }}-spm- + - name: Build package run: swift build --verbose From b84662843be8409121fe1c2b5246c1b922951f49 Mon Sep 17 00:00:00 2001 From: laosb Date: Thu, 23 Oct 2025 20:12:16 +0800 Subject: [PATCH 6/7] feat: Add workflow to release TailwindCSS CLI artifacts --- .github/workflows/release-tailwindcss-cli.yml | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .github/workflows/release-tailwindcss-cli.yml diff --git a/.github/workflows/release-tailwindcss-cli.yml b/.github/workflows/release-tailwindcss-cli.yml new file mode 100644 index 0000000..245c8c8 --- /dev/null +++ b/.github/workflows/release-tailwindcss-cli.yml @@ -0,0 +1,104 @@ +name: Release TailwindCSS CLI Artifacts + +on: + workflow_dispatch: + inputs: + tailwind_version: + description: "TailwindCSS release version (e.g., v4.1.14)" + required: true + type: string + +jobs: + build-and-release: + name: Build and Release Artifacts + runs-on: macos-latest + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Swift + uses: swift-actions/setup-swift@v2 + with: + swift-version: "6.1" + + - name: Cache Swift packages + uses: actions/cache@v4 + with: + path: .build + key: ${{ runner.os }}-spm-release-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm-release- + ${{ runner.os }}-spm- + + - name: Validate version format + run: | + if [[ ! "${{ inputs.tailwind_version }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Version must be in format vX.Y.Z (e.g., v4.1.14)" + exit 1 + fi + echo "Version format is valid: ${{ inputs.tailwind_version }}" + + - name: Build artifact bundler + run: | + echo "Building TailwindCSSCLIArtifactBundler..." + swift build -c release --product TailwindCSSCLIArtifactBundler + + - name: Create artifact bundles + run: | + echo "Creating artifact bundles for TailwindCSS ${{ inputs.tailwind_version }}..." + mkdir -p artifacts + .build/release/TailwindCSSCLIArtifactBundler \ + --version "${{ inputs.tailwind_version }}" \ + --work-dir "${{ runner.temp }}/tailwindcss-bundles" \ + --output-dir artifacts + + - name: List generated artifacts + run: | + echo "Generated artifacts:" + ls -lh artifacts/ + + - name: Compute checksum + id: checksum + run: | + CHECKSUM=$(shasum -a 256 artifacts/tailwindcss.artifactbundleindex | awk '{print $1}') + echo "checksum=$CHECKSUM" >> $GITHUB_OUTPUT + echo "Artifact bundle index checksum: $CHECKSUM" + + - name: Create release + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 + with: + tag_name: TailwindCSSCLI-${{ inputs.tailwind_version }} + name: TailwindCSS CLI ${{ inputs.tailwind_version }} + body: | + ## TailwindCSS CLI Artifact Bundles + + This release contains Swift Package Manager artifact bundles for TailwindCSS CLI version `${{ inputs.tailwind_version }}`. + + ### Included Artifacts + - Linux x64 (`x86_64-unknown-linux-gnu`) + - macOS x64 (`x86_64-apple-darwin`) + - macOS ARM64 (`aarch64-apple-darwin`) + + ### Usage + Add the artifact bundle to your `Package.swift`: + ```swift + .binaryTarget( + name: "TailwindCSSCLI", + url: "https://github.com/${{ github.repository }}/releases/download/TailwindCSSCLI@${{ inputs.tailwind_version }}/tailwindcss.artifactbundleindex", + checksum: "${{ steps.checksum.outputs.checksum }}" + ) + ``` + + ### Checksum + ``` + ${{ steps.checksum.outputs.checksum }} + ``` + draft: false + prerelease: false + make_latest: false + files: | + artifacts/*.zip + artifacts/tailwindcss.artifactbundleindex From 37194f5d29105c5c95d45360ae1b2b75c7f51859 Mon Sep 17 00:00:00 2001 From: laosb Date: Thu, 23 Oct 2025 20:46:37 +0800 Subject: [PATCH 7/7] chore: Use explicit ./artifacts path in release workflow --- .github/workflows/release-tailwindcss-cli.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-tailwindcss-cli.yml b/.github/workflows/release-tailwindcss-cli.yml index 245c8c8..130cee9 100644 --- a/.github/workflows/release-tailwindcss-cli.yml +++ b/.github/workflows/release-tailwindcss-cli.yml @@ -49,11 +49,11 @@ jobs: - name: Create artifact bundles run: | echo "Creating artifact bundles for TailwindCSS ${{ inputs.tailwind_version }}..." - mkdir -p artifacts + mkdir -p ./artifacts .build/release/TailwindCSSCLIArtifactBundler \ --version "${{ inputs.tailwind_version }}" \ --work-dir "${{ runner.temp }}/tailwindcss-bundles" \ - --output-dir artifacts + --output-dir ./artifacts - name: List generated artifacts run: |