diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml new file mode 100644 index 000000000..6511f5d02 --- /dev/null +++ b/.github/workflows/build-binaries.yml @@ -0,0 +1,79 @@ +name: Build Binaries + +on: + workflow_call: + inputs: + version: + required: true + type: string + ref: + required: true + type: string + artifact-prefix: + required: true + type: string + description: "Prefix for artifact names (e.g. 'clerk', 'clerk-canary', 'clerk-snapshot')" + +jobs: + build: + runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + - uses: oven-sh/setup-bun@v2 + - run: bun install --frozen-lockfile + + - name: Build all targets + env: + CLI_VERSION: ${{ inputs.version }} + run: bun run scripts/build.ts --version="${CLI_VERSION}" + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-darwin-arm64 + path: dist/artifacts/darwin-arm64/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-darwin-x64 + path: dist/artifacts/darwin-x64/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-linux-arm64 + path: dist/artifacts/linux-arm64/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-linux-arm64-musl + path: dist/artifacts/linux-arm64-musl/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-linux-x64 + path: dist/artifacts/linux-x64/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-linux-x64-musl + path: dist/artifacts/linux-x64-musl/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-win32-arm64 + path: dist/artifacts/win32-arm64/clerk.exe + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-win32-x64 + path: dist/artifacts/win32-x64/clerk.exe + retention-days: 1 diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml new file mode 100644 index 000000000..58f177d61 --- /dev/null +++ b/.github/workflows/smoke-test.yml @@ -0,0 +1,96 @@ +name: Smoke Test + +on: + workflow_call: + inputs: + version: + required: true + type: string + artifact-prefix: + required: true + type: string + preset: + required: true + type: string + description: > + Matrix preset name. One of: stable (all 7 testable targets), + canary (darwin-arm64 + linux-x64 + linux-x64-musl), or + snapshot (linux-x64 only). + +jobs: + resolve-matrix: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + matrix: ${{ steps.resolve.outputs.matrix }} + steps: + - id: resolve + run: | + case "${{ inputs.preset }}" in + stable) + # All testable targets. win32-arm64 is excluded — no GitHub-hosted + # ARM Windows runner available. Build-time format verification + # confirms it is a valid PE32+/Aarch64 binary. + matrix='[ + {"target":"darwin-arm64","runner":"macos-latest","bin":"./clerk"}, + {"target":"darwin-x64","runner":"macos-15-large","bin":"./clerk"}, + {"target":"linux-arm64","runner":"ubuntu-24.04-arm","bin":"./clerk"}, + {"target":"linux-arm64-musl","runner":"ubuntu-24.04-arm","bin":"./clerk","docker":"alpine"}, + {"target":"linux-x64","runner":"ubuntu-latest","bin":"./clerk"}, + {"target":"linux-x64-musl","runner":"ubuntu-latest","bin":"./clerk","docker":"alpine"}, + {"target":"win32-x64","runner":"windows-latest","bin":"./clerk.exe"} + ]' + ;; + canary) + # Subset for speed/cost. darwin-arm64 + linux-x64 cover the two + # most common dev platforms; musl is included since it runs on the + # same ubuntu-latest runner. + matrix='[ + {"target":"darwin-arm64","runner":"macos-latest","bin":"./clerk"}, + {"target":"linux-x64","runner":"ubuntu-latest","bin":"./clerk"}, + {"target":"linux-x64-musl","runner":"ubuntu-latest","bin":"./clerk","docker":"alpine"} + ]' + ;; + snapshot) + matrix='[{"target":"linux-x64","runner":"ubuntu-latest","bin":"./clerk"}]' + ;; + *) + echo "::error::Unknown preset: ${{ inputs.preset }}" + exit 1 + ;; + esac + { + echo "matrix<> "$GITHUB_OUTPUT" + + smoke-test: + needs: resolve-matrix + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(needs.resolve-matrix.outputs.matrix) }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 10 + steps: + - uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-${{ matrix.target }} + - name: Make executable + if: runner.os != 'Windows' + run: chmod +x ${{ matrix.bin }} + - name: Smoke test + shell: bash + run: | + expected="${{ inputs.version }}" + if [ -n "${{ matrix.docker }}" ]; then + actual=$(docker run --rm -v "$PWD:/work" ${{ matrix.docker }} /work/${{ matrix.bin }} --version) + else + actual=$(${{ matrix.bin }} --version) + fi + echo "Binary reports $actual (expected $expected)" + if [ "$actual" != "$expected" ]; then + echo "::error::Version mismatch: expected $expected, got $actual" + exit 1 + fi diff --git a/.gitignore b/.gitignore index a14702c40..db941ea17 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Finder (MacOS) folder config .DS_Store + +# Bun compile artifacts +*.bun-build diff --git a/package.json b/package.json index 418ecac12..82c9f5015 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,11 @@ "build": "bun run --filter @clerk/cli-core build", "dev": "bun run --filter @clerk/cli-core dev", "test": "bun run --filter @clerk/cli-core test", - "lint": "bun run --filter @clerk/cli-core lint", - "format": "bun run --filter @clerk/cli-core format", - "format:check": "bun run --filter @clerk/cli-core format:check", + "lint": "bun run --filter @clerk/cli-core lint && oxlint scripts/", + "format": "bun run --filter @clerk/cli-core format && oxfmt --write scripts/", + "format:check": "bun run --filter @clerk/cli-core format:check && oxfmt --check scripts/", "build:compile": "bun run --filter @clerk/cli-core build:compile", + "build:compile:all": "bun run scripts/build.ts", "start": "bun run build:compile && ./packages/cli-core/dist/clerk", "prepare": "git config core.hooksPath .hooks" }, diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 000000000..871efbfa6 --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,76 @@ +import { mkdir } from "node:fs/promises"; +import { join } from "node:path"; +import { parseArgs } from "node:util"; +import { targets } from "./releaser/targets.ts"; + +const { values } = parseArgs({ + args: Bun.argv.slice(2), + options: { + target: { type: "string" }, + version: { type: "string", default: "0.0.0-dev" }, + }, +}); + +const targetFilter = values.target; +const version = values.version!; + +const selectedTargets = targetFilter + ? targets.filter((t) => t.bunTarget === targetFilter || t.name === targetFilter) + : targets; + +if (selectedTargets.length === 0) { + throw new Error( + `Unknown target: ${targetFilter}\nAvailable targets: ${targets.map((t) => t.bunTarget).join(", ")}`, + ); +} + +console.log(`Building ${selectedTargets.length} target(s) with version ${version}\n`); + +let failed = false; +for (const target of selectedTargets) { + const outDir = join("dist", "artifacts", target.name); + const outFile = join(outDir, `clerk${target.ext}`); + + await mkdir(outDir, { recursive: true }); + + console.log(`Building ${target.name} (${target.bunTarget})...`); + const buildResult = Bun.spawnSync( + [ + "bun", + "build", + "--compile", + "--no-compile-autoload-dotenv", + `--target=${target.bunTarget}`, + `--define`, + `CLI_VERSION="${version}"`, + "./packages/cli-core/src/cli.ts", + "--outfile", + outFile, + ], + { stdio: ["ignore", "pipe", "pipe"] }, + ); + + if (buildResult.exitCode !== 0) { + console.error(` FAIL: ${buildResult.stderr.toString().trim()}`); + failed = true; + continue; + } + + // Verify binary format + const fileResult = Bun.spawnSync(["file", outFile], { stdio: ["ignore", "pipe", "pipe"] }); + const fileOutput = fileResult.stdout.toString(); + if (!target.verifyPattern.test(fileOutput)) { + console.error(` FAIL: binary format mismatch for ${target.name}`); + console.error(` file output: ${fileOutput.trim()}`); + failed = true; + continue; + } + + console.log(` OK: ${outFile}`); +} + +if (failed) { + throw new Error("Some targets failed to build."); +} + +console.log("\nAll targets built successfully."); diff --git a/scripts/releaser/targets.ts b/scripts/releaser/targets.ts new file mode 100644 index 000000000..4c24fc0dd --- /dev/null +++ b/scripts/releaser/targets.ts @@ -0,0 +1,87 @@ +export interface Target { + name: string; + bunTarget: string; + os: string; + cpu: string; + libc?: string; + ext: string; + /** Regex to verify the compiled binary format via `file` output. */ + verifyPattern: RegExp; +} + +// Target names use Node.js ${process.platform}-${process.arch} convention so the wrapper +// shim (packages/cli/bin/clerk) can derive package names without a lookup table. +// Used by scripts/build.ts and scripts/releaser/index.ts. +export const targets: Target[] = [ + { + name: "darwin-arm64", + bunTarget: "bun-darwin-arm64", + os: "darwin", + cpu: "arm64", + ext: "", + verifyPattern: /Mach-O.*arm64/, + }, + { + name: "darwin-x64", + bunTarget: "bun-darwin-x64", + os: "darwin", + cpu: "x64", + ext: "", + verifyPattern: /Mach-O.*x86_64/, + }, + { + name: "linux-arm64", + bunTarget: "bun-linux-arm64", + os: "linux", + cpu: "arm64", + libc: "glibc", + ext: "", + verifyPattern: /ELF.*ARM aarch64/, + }, + { + name: "linux-arm64-musl", + bunTarget: "bun-linux-arm64-musl", + os: "linux", + cpu: "arm64", + libc: "musl", + ext: "", + verifyPattern: /ELF.*ARM aarch64/, + }, + { + name: "linux-x64", + bunTarget: "bun-linux-x64", + os: "linux", + cpu: "x64", + libc: "glibc", + ext: "", + verifyPattern: /ELF.*x86-64/, + }, + { + name: "linux-x64-musl", + bunTarget: "bun-linux-x64-musl", + os: "linux", + cpu: "x64", + libc: "musl", + ext: "", + verifyPattern: /ELF.*x86-64/, + }, + { + name: "win32-arm64", + bunTarget: "bun-windows-arm64", + os: "win32", + cpu: "arm64", + ext: ".exe", + verifyPattern: /PE32\+.*Aarch64/, + }, + { + name: "win32-x64", + bunTarget: "bun-windows-x64", + os: "win32", + cpu: "x64", + ext: ".exe", + verifyPattern: /PE32\+.*x86-64/, + }, +]; + +export const SCOPE = "@clerk"; +export const PKG_PREFIX = "cli";