diff --git a/.changeset/icy-melons-tan.md b/.changeset/icy-melons-tan.md new file mode 100644 index 0000000..fb6228d --- /dev/null +++ b/.changeset/icy-melons-tan.md @@ -0,0 +1,5 @@ +--- +"cmdly": patch +--- + +Add postinstall to build for other platforms and arch other than linux-x64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 55d0939..99f51cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,22 +88,23 @@ jobs: uses: ./.github/actions/setup - name: Build binary - run: pnpm build:sea + run: pnpm build - name: Archive binary run: | mkdir -p release - cp "dist/${{ matrix.target }}/cmdly" "release/cmdly-${{ matrix.target }}" - tar -C release -czf "release/cmdly-${{ matrix.target }}.tar.gz" "cmdly-${{ matrix.target }}" - ${{ matrix.checksum_cmd }} "release/cmdly-${{ matrix.target }}.tar.gz" > "release/cmdly-${{ matrix.target }}.tar.gz.sha256" + ASSET_NAME="cmdly-v${{ needs.version.outputs.release_version }}-${{ matrix.target }}" + cp "dist/${{ matrix.target }}/cmdly" "release/${ASSET_NAME}" + tar -C release -czf "release/${ASSET_NAME}.tar.gz" "${ASSET_NAME}" + ${{ matrix.checksum_cmd }} "release/${ASSET_NAME}.tar.gz" > "release/${ASSET_NAME}.tar.gz.sha256" - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: cmdly-${{ matrix.target }} path: | - release/cmdly-${{ matrix.target }}.tar.gz - release/cmdly-${{ matrix.target }}.tar.gz.sha256 + release/cmdly-v${{ needs.version.outputs.release_version }}-${{ matrix.target }}.tar.gz + release/cmdly-v${{ needs.version.outputs.release_version }}-${{ matrix.target }}.tar.gz.sha256 release: name: Release assets diff --git a/package.json b/package.json index 62733b1..55e8ae7 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,14 @@ "url": "https://github.com/Armadillidiid/cmdly/issues" }, "author": "Emmanuel Isenah (https://emmanuelisenah.com)", + "bin": { + "cmdly": "./bin/cmdly" + }, + "files": [ + "bin/cmdly", + "dist", + "scripts/postinstall.mjs" + ], "repository": { "type": "git", "url": "https://github.com/Armadillidiid/cmdly" @@ -33,15 +41,16 @@ }, "scripts": { "dev": "node --import=tsx src/bin.ts --watch", - "build": "pnpm build:sea", - "build:sea": "node scripts/build-sea.mjs", - "verify:sea": "node scripts/verify-sea.mjs", + "build": "node scripts/build-sea.mjs", + "stage:bin": "node scripts/stage-default-bin.mjs", + "build:verify": "node scripts/verify-sea.mjs", + "prepack": "pnpm build && pnpm stage:bin", "prepare": "effect-language-service patch", - "clean": "rm -rf dist .sea", + "clean": "rm -rf dist", "typecheck": "tsc -b tsconfig.json", "test": "vitest run", "changeset-version": "changeset version", - "changeset-publish": "pnpm build && pnpm vitest && changeset tag && git push --tags", + "changeset-publish": "node scripts/publish.mjs", "format": "biome format --write", "lint": "biome lint --write", "check": "biome check --write --unsafe" diff --git a/scripts/postinstall.mjs b/scripts/postinstall.mjs new file mode 100644 index 0000000..2aa0263 --- /dev/null +++ b/scripts/postinstall.mjs @@ -0,0 +1,82 @@ +#!/usr/bin/env node + +import { + chmodSync, + copyFileSync, + existsSync, + mkdirSync, + renameSync, +} from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const packageRoot = join(__dirname, ".."); +const binDir = join(packageRoot, "bin"); +const binPath = join(binDir, "cmdly"); +const supportedTargets = ["linux-x64", "linux-arm64", "darwin-arm64"]; + +const detectTarget = () => { + if (process.platform === "linux" && process.arch === "x64") { + return "linux-x64"; + } + if (process.platform === "linux" && process.arch === "arm64") { + return "linux-arm64"; + } + if (process.platform === "darwin" && process.arch === "arm64") { + return "darwin-arm64"; + } + return null; +}; + +const installBinary = (sourcePath) => { + if (!existsSync(sourcePath)) { + throw new Error(`missing target binary in package: ${sourcePath}`); + } + + mkdirSync(binDir, { recursive: true }); + const tempTarget = join(binDir, ".cmdly.tmp"); + copyFileSync(sourcePath, tempTarget); + chmodSync(tempTarget, 0o755); + renameSync(tempTarget, binPath); +}; + +const main = () => { + const target = detectTarget(); + + if (!target) { + console.warn( + `[postinstall] skipping unsupported target ${process.platform}-${process.arch}; supported: ${supportedTargets.join(", ")}`, + ); + return; + } + + if (target === "linux-x64" && existsSync(binPath)) { + // The binary is already included in the package and bin field points to it + // No postinstall setup needed + console.log( + `[postinstall] skipping install for ${target} since binary already exists`, + ); + return; + } + + const sourcePath = join(packageRoot, "dist", target, "cmdly"); + installBinary(sourcePath); + + console.log(`[postinstall] installed ${target} binary`); +}; + +try { + main(); +} catch (error) { + const target = detectTarget(); + const message = error instanceof Error ? error.message : String(error); + + if (target) { + console.error(`[postinstall] native install failed for ${target}: ${message}`); + process.exit(1); + } + + console.warn(`[postinstall] native install failed on unsupported target: ${message}`); + process.exit(0); +} diff --git a/scripts/publish.mjs b/scripts/publish.mjs new file mode 100644 index 0000000..3f4bea2 --- /dev/null +++ b/scripts/publish.mjs @@ -0,0 +1,62 @@ +import { writeFileSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import pkg from "../package.json" with { type: "json" }; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const rootDir = resolve(__dirname, ".."); +const pkgPath = join(rootDir, "package.json"); + +const run = (cmd, args, opts) => { + const result = spawnSync(cmd, args, { + cwd: rootDir, + stdio: "inherit", + ...opts, + }); + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +}; + +const getNpmVersion = () => { + const result = spawnSync("npm", ["--version"], { + cwd: rootDir, + encoding: "utf8", + stdio: "pipe", + }); + return result.stdout.trim(); +}; + +// @changesets/cli currently only supports npm so we need to temporarily change our package manager +const npmVersion = getNpmVersion(); +const original = JSON.parse(JSON.stringify(pkg)); +const savedPackageManager = pkg.packageManager; +const savedDevEnginesPackageManager = JSON.parse( + JSON.stringify(pkg.devEngines?.packageManager), +); + +original.scripts.postinstall = "node scripts/postinstall.mjs"; +original.packageManager = `npm@${npmVersion}`; +if (original.devEngines) { + original.devEngines.packageManager = { + name: "npm", + version: npmVersion, + }; +} +writeFileSync(pkgPath, `${JSON.stringify(original, null, "\t")}\n`, "utf8"); + +run("pnpm", ["build"]); +run("pnpm", ["stage:bin"]); +run("pnpm", ["vitest"]); +run("changeset", ["publish"]); +run("changeset", ["tag"]); +run("git", ["push", "--tags"]); + +delete original.scripts.postinstall; +original.packageManager = savedPackageManager; +if (original.devEngines) { + original.devEngines.packageManager = savedDevEnginesPackageManager; +} +writeFileSync(pkgPath, `${JSON.stringify(original, null, "\t")}\n`, "utf8"); diff --git a/scripts/stage-default-bin.mjs b/scripts/stage-default-bin.mjs new file mode 100644 index 0000000..0be1764 --- /dev/null +++ b/scripts/stage-default-bin.mjs @@ -0,0 +1,18 @@ +import { chmodSync, copyFileSync, mkdirSync, rmSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const rootDir = resolve(__dirname, ".."); + +const source = join(rootDir, "dist", "linux-x64", "cmdly"); +const targetDir = join(rootDir, "bin"); +const target = join(targetDir, "cmdly"); + +mkdirSync(targetDir, { recursive: true }); +rmSync(target, { force: true }); +copyFileSync(source, target); +chmodSync(target, 0o755); + +console.log(`[stage:bin] staged ${source} -> ${target}`);