From 13b3d215d4786b60c0ad49580fc5a15f29cccef9 Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah Date: Sun, 10 May 2026 14:02:06 +0100 Subject: [PATCH 01/18] chore: upgrade pnpm version --- package.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 311dca3..891d5f1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,14 @@ "name": "cmdly", "version": "0.1.0", "type": "module", - "packageManager": "pnpm@10.14.0", + "packageManager": "pnpm@11.0.9", + "devEngines": { + "packageManager": { + "name": "pnpm", + "version": "11.0.9", + "onFail": "download" + } + }, "license": "MIT", "description": "", "keywords": [ From 03be5843a7e5dfcc7e3a806a0e2bce260f86761c Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah Date: Sun, 10 May 2026 14:03:21 +0100 Subject: [PATCH 02/18] feat: update --- .github/actions/setup/action.yml | 2 +- .github/workflows/release.yml | 68 +++++++++++++++++-- .gitignore | 2 + README.md | 36 +++++++++- package.json | 21 +++--- pnpm-lock.yaml | 41 +++++++---- scripts/build-sea.mjs | 113 +++++++++++++++++++++++++++++++ scripts/inject-constant.js | 24 ------- scripts/verify-sea.mjs | 43 ++++++++++++ src/constants.ts | 2 +- 10 files changed, 294 insertions(+), 58 deletions(-) create mode 100644 scripts/build-sea.mjs delete mode 100644 scripts/inject-constant.js create mode 100644 scripts/verify-sea.mjs diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 8ee2557..35692db 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -7,7 +7,7 @@ runs: - name: Install node uses: actions/setup-node@v4 with: - node-version: "23" + node-version: "26" registry-url: 'https://registry.npmjs.org' - name: ⚙️ Enable Corepack diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9dadef4..8068aa4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,14 +3,17 @@ on: push: branches: - main + tags: + - "*" concurrency: ${{ github.workflow }}-${{ github.ref }} permissions: contents: write pull-requests: write id-token: write jobs: - release: - name: Release + version: + name: Version + if: github.ref_type == 'branch' runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -24,10 +27,7 @@ jobs: - name: Build run: pnpm build - - name: Update npm to latest version # Trusted publishing requires version 11.5.1 or later. - run: npm install -g npm@latest - - - name: Create Release Pull Request or Publish to npm + - name: Create Release Pull Request or Tag Release id: changesets uses: changesets/action@v1 with: @@ -37,3 +37,59 @@ jobs: title: "chore: version packages" env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + + binaries-linux: + name: Binary linux-x64 + if: github.ref_type == 'tag' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + uses: ./.github/actions/setup + + - name: Build linux-x64 binary + run: pnpm build:sea:linux-x64 + + - name: Archive linux-x64 + run: | + mkdir -p release + cp dist-app/linux-x64/cmdly release/cmdly-linux-x64 + tar -C release -czf release/cmdly-linux-x64.tar.gz cmdly-linux-x64 + sha256sum release/cmdly-linux-x64.tar.gz > release/cmdly-linux-x64.tar.gz.sha256 + + - name: Upload linux-x64 assets + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1 || gh release create "${GITHUB_REF_NAME}" --generate-notes || true + gh release upload "${GITHUB_REF_NAME}" release/cmdly-linux-x64.tar.gz release/cmdly-linux-x64.tar.gz.sha256 --clobber + + binaries-darwin-arm64: + name: Binary darwin-arm64 + if: github.ref_type == 'tag' + runs-on: macos-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + uses: ./.github/actions/setup + + - name: Build darwin-arm64 binary + run: pnpm build:sea:darwin-arm64 + + - name: Archive darwin-arm64 + run: | + mkdir -p release + cp dist-app/darwin-arm64/cmdly release/cmdly-darwin-arm64 + tar -C release -czf release/cmdly-darwin-arm64.tar.gz cmdly-darwin-arm64 + shasum -a 256 release/cmdly-darwin-arm64.tar.gz > release/cmdly-darwin-arm64.tar.gz.sha256 + + - name: Upload darwin-arm64 assets + env: + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + run: | + gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1 || gh release create "${GITHUB_REF_NAME}" --generate-notes || true + gh release upload "${GITHUB_REF_NAME}" release/cmdly-darwin-arm64.tar.gz release/cmdly-darwin-arm64.tar.gz.sha256 --clobber diff --git a/.gitignore b/.gitignore index 01885e3..24351b2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ yarn-error.log tmp/ build/ dist/ +dist-app/ +.sea/ .direnv/ .env .env.local diff --git a/README.md b/README.md index 018eb02..bac0425 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,44 @@ https://github.com/user-attachments/assets/d3dda9c8-e202-4d7a-bbc8-bac9b32f7ac7 ## Installation +Download prebuilt binary from GitHub Releases. + +### Linux x64 + +```bash +curl -fsSL -o /tmp/cmdly-linux-x64.tar.gz https://github.com/Armadillidiid/cmdly/releases/latest/download/cmdly-linux-x64.tar.gz +tar -xzf /tmp/cmdly-linux-x64.tar.gz -C /tmp +install -m 755 /tmp/cmdly-linux-x64 ~/.local/bin/cmdly +``` + +### macOS arm64 + ```bash -npm install -g cmdly +curl -fsSL -o /tmp/cmdly-darwin-arm64.tar.gz https://github.com/Armadillidiid/cmdly/releases/latest/download/cmdly-darwin-arm64.tar.gz +tar -xzf /tmp/cmdly-darwin-arm64.tar.gz -C /tmp +install -m 755 /tmp/cmdly-darwin-arm64 ~/.local/bin/cmdly ``` +Ensure `~/.local/bin` is on `PATH`. + +### Verify checksum (optional) + +```bash +curl -fsSL -O https://github.com/Armadillidiid/cmdly/releases/latest/download/cmdly-linux-x64.tar.gz +curl -fsSL -O https://github.com/Armadillidiid/cmdly/releases/latest/download/cmdly-linux-x64.tar.gz.sha256 +sha256sum -c cmdly-linux-x64.tar.gz.sha256 +``` + +### Supported targets + +- `linux-x64` +- `darwin-arm64` + +### Not supported in v1 + +- Windows +- Intel macOS (`darwin-x64`) + ## Quick Start 1. **Configure your AI provider:** diff --git a/package.json b/package.json index 891d5f1..eaed3ef 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,6 @@ "url": "https://github.com/Armadillidiid/cmdly/issues" }, "author": "Emmanuel Isenah (https://emmanuelisenah.com)", - "bin": { - "cmdly": "./dist/index.js" - }, - "files": [ - "dist" - ], "repository": { "type": "git", "url": "https://github.com/Armadillidiid/cmdly" @@ -35,18 +29,21 @@ "access": "public" }, "engines": { - "node": ">=23" + "node": ">=26" }, "scripts": { "dev": "node --import=tsx src/bin.ts --watch", - "build": "ncc build src/bin.ts -o dist -m", - "postbuild": "node scripts/inject-constant.js", + "build": "pnpm build:sea", + "build:sea": "node scripts/build-sea.mjs", + "build:sea:linux-x64": "node scripts/build-sea.mjs --target linux-x64", + "build:sea:darwin-arm64": "node scripts/build-sea.mjs --target darwin-arm64", + "verify:sea": "node scripts/verify-sea.mjs", "prepare": "effect-language-service patch", - "clean": "rm -rf dist/*", + "clean": "rm -rf dist dist-app .sea", "typecheck": "tsc -b tsconfig.json", "test": "vitest run", "changeset-version": "changeset version", - "changeset-publish": "pnpm build && pnpm vitest && changeset publish && changeset tag && git push --tags", + "changeset-publish": "pnpm build && pnpm vitest && changeset tag && git push --tags", "format": "biome format --write", "lint": "biome lint --write", "check": "biome check --write --unsafe" @@ -62,7 +59,7 @@ "@effect/vitest": "^0.27.0", "@types/copy-paste": "^2.1.0", "@types/node": "^24.10.1", - "@vercel/ncc": "^0.38.4", + "esbuild": "^0.27.0", "effect": "^3.19.9", "tsx": "^4.21.0", "typescript": "^5.9.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a1fab2..c0b883a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,7 +47,7 @@ importers: version: 0.72.1(@effect/platform@0.93.6(effect@3.19.9))(@effect/printer-ansi@0.47.0(@effect/typeclass@0.38.0(effect@3.19.9))(effect@3.19.9))(@effect/printer@0.47.0(@effect/typeclass@0.38.0(effect@3.19.9))(effect@3.19.9))(effect@3.19.9) '@effect/language-service': specifier: latest - version: 0.60.0 + version: 0.85.1 '@effect/platform': specifier: ^0.93.6 version: 0.93.6(effect@3.19.9) @@ -63,12 +63,12 @@ importers: '@types/node': specifier: ^24.10.1 version: 24.10.2 - '@vercel/ncc': - specifier: ^0.38.4 - version: 0.38.4 effect: specifier: ^3.19.9 version: 3.19.9 + esbuild: + specifier: ^0.27.0 + version: 0.27.1 tsx: specifier: ^4.21.0 version: 4.21.0 @@ -147,24 +147,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@2.3.8': resolution: {integrity: sha512-Uo1OJnIkJgSgF+USx970fsM/drtPcQ39I+JO+Fjsaa9ZdCN1oysQmy6oAGbyESlouz+rzEckLTF6DS7cWse95g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@2.3.8': resolution: {integrity: sha512-YGLkqU91r1276uwSjiUD/xaVikdxgV1QpsicT0bIA1TaieM6E5ibMZeSyjQ/izBn4tKQthUSsVZacmoJfa3pDA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@2.3.8': resolution: {integrity: sha512-QDPMD5bQz6qOVb3kiBui0zKZXASLo0NIQ9JVJio5RveBEFgDgsvJFUvZIbMbUZT3T00M/1wdzwWXk4GIh0KaAw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@2.3.8': resolution: {integrity: sha512-H4IoCHvL1fXKDrTALeTKMiE7GGWFAraDwBYFquE/L/5r1927Te0mYIGseXi4F+lrrwhSWbSGt5qPFswNoBaCxg==} @@ -269,8 +273,8 @@ packages: lmdb: optional: true - '@effect/language-service@0.60.0': - resolution: {integrity: sha512-elJDWHG5Naq3OkilPt9ZRn56JfSA3MhXUIlDx9RWJeScHm96kZ+HkZ3eFBxqROzXwD6Q2DTtFctFwOM0+QLZEA==} + '@effect/language-service@0.85.1': + resolution: {integrity: sha512-EXnJjIy6zQ3nUO/MZ+ynWUb8B895KZPotd1++oTs9JjDkplwM7cb6zo8Zq2zU6piwq+KflO7amXbEfj1UMpHkw==} hasBin: true '@effect/platform-node-shared@0.56.0': @@ -745,36 +749,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -832,56 +842,67 @@ packages: resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.3': resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.3': resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.3': resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.3': resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.53.3': resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.53.3': resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.3': resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.3': resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.3': resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.3': resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.53.3': resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} @@ -966,10 +987,6 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vercel/ncc@0.38.4': - resolution: {integrity: sha512-8LwjnlP39s08C08J5NstzriPvW1SP8Zfpp1BvC2sI35kPeZnHfxVkCwu4/+Wodgnd60UtT1n8K8zw+Mp7J9JmQ==} - hasBin: true - '@vercel/oidc@3.0.5': resolution: {integrity: sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw==} engines: {node: '>= 20'} @@ -1984,7 +2001,7 @@ snapshots: effect: 3.19.9 uuid: 11.1.0 - '@effect/language-service@0.60.0': {} + '@effect/language-service@0.85.1': {} '@effect/platform-node-shared@0.56.0(@effect/cluster@0.55.0(@effect/platform@0.93.6(effect@3.19.9))(@effect/rpc@0.72.2(@effect/platform@0.93.6(effect@3.19.9))(effect@3.19.9))(@effect/sql@0.48.6(@effect/experimental@0.57.11(@effect/platform@0.93.6(effect@3.19.9))(effect@3.19.9))(@effect/platform@0.93.6(effect@3.19.9))(effect@3.19.9))(@effect/workflow@0.15.1(@effect/experimental@0.57.11(@effect/platform@0.93.6(effect@3.19.9))(effect@3.19.9))(@effect/platform@0.93.6(effect@3.19.9))(@effect/rpc@0.72.2(@effect/platform@0.93.6(effect@3.19.9))(effect@3.19.9))(effect@3.19.9))(effect@3.19.9))(@effect/platform@0.93.6(effect@3.19.9))(@effect/rpc@0.72.2(@effect/platform@0.93.6(effect@3.19.9))(effect@3.19.9))(@effect/sql@0.48.6(@effect/experimental@0.57.11(@effect/platform@0.93.6(effect@3.19.9))(effect@3.19.9))(@effect/platform@0.93.6(effect@3.19.9))(effect@3.19.9))(effect@3.19.9)': dependencies: @@ -2472,8 +2489,6 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vercel/ncc@0.38.4': {} - '@vercel/oidc@3.0.5': {} '@vitest/expect@4.0.15': diff --git a/scripts/build-sea.mjs b/scripts/build-sea.mjs new file mode 100644 index 0000000..5d99de9 --- /dev/null +++ b/scripts/build-sea.mjs @@ -0,0 +1,113 @@ +import { mkdirSync, rmSync, chmodSync } from "node:fs"; +import { writeFile } from "node:fs/promises"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; +import { build } from "esbuild"; +import pkg from "../package.json" with { type: "json" }; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const rootDir = resolve(__dirname, ".."); + +const supportedTargets = new Set(["linux-x64", "darwin-arm64"]); + +const hostTarget = () => { + const platform = process.platform; + const arch = process.arch; + if (platform === "linux" && arch === "x64") { + return "linux-x64"; + } + if (platform === "darwin" && arch === "arm64") { + return "darwin-arm64"; + } + return null; +}; + +const parseTarget = () => { + const idx = process.argv.indexOf("--target"); + if (idx === -1) { + return null; + } + const target = process.argv[idx + 1]; + if (!target) { + throw new Error("Missing value for --target"); + } + return target; +}; + +const targetArg = parseTarget(); +const detectedHostTarget = hostTarget(); + +if (targetArg && !supportedTargets.has(targetArg)) { + throw new Error(`Unsupported target: ${targetArg}`); +} + +if (!detectedHostTarget) { + throw new Error( + `Unsupported host platform ${process.platform}-${process.arch}. Supported host targets: linux-x64, darwin-arm64.`, + ); +} + +const target = targetArg ?? detectedHostTarget; + +if (target !== detectedHostTarget) { + throw new Error( + `Target ${target} does not match host ${detectedHostTarget}. Build this target on a matching runner.`, + ); +} + +if (Number.parseInt(process.versions.node.split(".")[0], 10) !== 26) { + throw new Error( + `Node 26.x required for SEA build, found ${process.version}. Use Node 26.x.`, + ); +} + +const seaDir = join(rootDir, ".sea"); +const bundlePath = join(seaDir, "index.mjs"); +const seaConfigPath = join(seaDir, `sea-config.${target}.json`); +const outDir = join(rootDir, "dist-app", target); +const outFile = join(outDir, "cmdly"); + +rmSync(seaDir, { recursive: true, force: true }); +mkdirSync(seaDir, { recursive: true }); +mkdirSync(outDir, { recursive: true }); + +await build({ + entryPoints: [join(rootDir, "src", "bin.ts")], + outfile: bundlePath, + bundle: true, + format: "esm", + platform: "node", + target: "node26", + tsconfig: join(rootDir, "tsconfig.json"), + logLevel: "info", + define: { + __VERSION__: JSON.stringify(pkg.version), + __NAME__: JSON.stringify(pkg.name), + }, +}); + +const seaConfig = { + main: bundlePath, + mainFormat: "module", + output: outFile, + disableExperimentalSEAWarning: true, + useSnapshot: false, + useCodeCache: false, + execArgvExtension: "env", +}; + +await writeFile(seaConfigPath, `${JSON.stringify(seaConfig, null, 2)}\n`, "utf8"); + +const result = spawnSync(process.execPath, ["--build-sea", seaConfigPath], { + cwd: rootDir, + stdio: "inherit", +}); + +if (result.status !== 0) { + process.exit(result.status ?? 1); +} + +chmodSync(outFile, 0o755); +console.log(`SEA binary built: ${outFile}`); diff --git a/scripts/inject-constant.js b/scripts/inject-constant.js deleted file mode 100644 index e05345d..0000000 --- a/scripts/inject-constant.js +++ /dev/null @@ -1,24 +0,0 @@ -import { readFileSync, writeFileSync } from "node:fs"; -import { join, dirname } 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 = join(__dirname, ".."); - -const distFile = join(rootDir, "dist/index.js"); - -try { - let content = readFileSync(distFile, "utf-8"); - - // Replace __VERSION__ and __NAME__ with actual values - content = content.replace(/__VERSION__/g, `"${pkg.version}"`); - content = content.replace(/__NAME__/g, `"${pkg.name}"`); - - writeFileSync(distFile, content, "utf-8"); - console.log(`✓ Injected version ${pkg.version} and name ${pkg.name}`); -} catch (error) { - console.error("Failed to inject version:", error); - process.exit(1); -} diff --git a/scripts/verify-sea.mjs b/scripts/verify-sea.mjs new file mode 100644 index 0000000..2fe6114 --- /dev/null +++ b/scripts/verify-sea.mjs @@ -0,0 +1,43 @@ +import { spawnSync } from "node:child_process"; +import { existsSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const rootDir = resolve(__filename, "..", ".."); + +const detectTarget = () => { + if (process.platform === "linux" && process.arch === "x64") { + return "linux-x64"; + } + if (process.platform === "darwin" && process.arch === "arm64") { + return "darwin-arm64"; + } + return null; +}; + +const target = detectTarget(); + +if (!target) { + throw new Error( + `Unsupported host ${process.platform}-${process.arch}. Supported: linux-x64, darwin-arm64`, + ); +} + +const binary = join(rootDir, "dist-app", target, "cmdly"); + +if (!existsSync(binary)) { + throw new Error(`SEA binary not found: ${binary}`); +} + +const run = (args) => { + const result = spawnSync(binary, args, { stdio: "inherit" }); + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +}; + +run(["--help"]); +run(["--version"]); + +console.log(`SEA verification passed for ${target}`); diff --git a/src/constants.ts b/src/constants.ts index 8c0eb21..7737753 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,7 +5,7 @@ declare const __NAME__: string; const VERSION: string = typeof __VERSION__ !== "undefined" ? __VERSION__ : "0.0.0-dev"; -const NAME: string = typeof __NAME__ !== "undefined" ? __NAME__ : "unknown"; +const NAME: string = typeof __NAME__ !== "undefined" ? __NAME__ : "cmdly"; const CONFIG_DIRECTORY = `~/.config/${NAME}`; const CONFIG_FILENAME = `${NAME}.json`; From a449917e2ed6690262a062bc55aa3e3dda39d52e Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah Date: Sun, 10 May 2026 14:22:21 +0100 Subject: [PATCH 03/18] feat: update --- .github/workflows/release.yml | 4 +- .gitignore | 1 - .opencode/plans/1778415295647-swift-island.md | 101 ++++++++++++++++++ package.json | 2 +- pnpm-workspace.yaml | 4 + scripts/build-sea.mjs | 8 +- scripts/verify-sea.mjs | 2 +- 7 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 .opencode/plans/1778415295647-swift-island.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8068aa4..3b1ec78 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -55,7 +55,7 @@ jobs: - name: Archive linux-x64 run: | mkdir -p release - cp dist-app/linux-x64/cmdly release/cmdly-linux-x64 + cp dist/linux-x64/cmdly release/cmdly-linux-x64 tar -C release -czf release/cmdly-linux-x64.tar.gz cmdly-linux-x64 sha256sum release/cmdly-linux-x64.tar.gz > release/cmdly-linux-x64.tar.gz.sha256 @@ -83,7 +83,7 @@ jobs: - name: Archive darwin-arm64 run: | mkdir -p release - cp dist-app/darwin-arm64/cmdly release/cmdly-darwin-arm64 + cp dist/darwin-arm64/cmdly release/cmdly-darwin-arm64 tar -C release -czf release/cmdly-darwin-arm64.tar.gz cmdly-darwin-arm64 shasum -a 256 release/cmdly-darwin-arm64.tar.gz > release/cmdly-darwin-arm64.tar.gz.sha256 diff --git a/.gitignore b/.gitignore index 24351b2..c2c8c70 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ yarn-error.log tmp/ build/ dist/ -dist-app/ .sea/ .direnv/ .env diff --git a/.opencode/plans/1778415295647-swift-island.md b/.opencode/plans/1778415295647-swift-island.md new file mode 100644 index 0000000..efebdac --- /dev/null +++ b/.opencode/plans/1778415295647-swift-island.md @@ -0,0 +1,101 @@ +# Node SEA Cutover Plan + +## Goal +Replace `ncc` packaging with Node SEA. Full cutover now. Primary distribution: GitHub Release binaries only. No Windows in v1. Unsiged binaries in v1. + +## Confirmed Decisions +- Distribution: GitHub binaries only. +- Cutover: full cutover now (remove npm JS bin flow). +- Targets v1: Linux + macOS ARM64, exclude Windows. +- Intel Mac: exclude `darwin-x64` for now (known upstream SEA risk). +- Runtime pin: latest Node 26.x for SEA build. +- Signing: none in v1. + +## Implementation Steps +1. Update build + package metadata (`package.json`) +- Remove `ncc` build flow and `postbuild` dependency chain. +- Remove npm `bin` mapping to `dist/index.js` and `files: ["dist"]` packaging assumptions. +- Add SEA-focused scripts, example shape: + - `build:bundle` -> produce single JS entry artifact for SEA input. + - `build:sea` -> generate SEA executables for `linux-x64` and `darwin-arm64` with pinned Node 26.x. + - `build` -> run SEA build pipeline. + - `verify:sea` -> smoke-run each built binary. +- Remove `@vercel/ncc` devDependency. + +2. Add SEA build orchestrator + config +- Add `scripts/build-sea.mjs` to: + - create temporary SEA config JSON per target, + - call Node SEA generation (`node --build-sea ...`), + - emit binaries into deterministic output dir (example `dist-app//cmdly`). +- Use `mainFormat: "module"` if bundle stays ESM; if bundler emits CJS, set `mainFormat: "commonjs"`. +- Set `useCodeCache: false` for cross-target safety. +- Set `useSnapshot: false` in v1. +- Keep runtime FS behavior unchanged (config/credential/cache paths already under user home). + +3. Replace constant injection approach +- Remove dependency on `scripts/inject-constant.js` mutating built output. +- Inject app name/version during bundle generation step (define/env/templating), so SEA input file already finalized. +- Verify `src/constants.ts` still resolves placeholders correctly in non-release/dev mode. + +4. Update release workflow (`.github/workflows/release.yml`) +- Keep changesets versioning/publish logic only if still needed for tags/changelog. +- Replace release build step with SEA build steps. +- Add matrix or explicit jobs for: + - `linux-x64` build on Ubuntu, + - `darwin-arm64` build on macOS runner. +- Upload binaries + checksums as GitHub Release assets. +- Ensure release notes include target list and install instructions. + +5. Update docs (`README.md`) +- Remove npm global install as primary install path. +- Add binary install/usage instructions by platform: + - download from GitHub Releases, + - `chmod +x` on Unix, + - add to `PATH` or move to user bin dir. +- Add known limitations section: + - no Windows support in v1, + - no Intel Mac binary in v1. + +6. Cleanup obsolete paths +- Delete `scripts/inject-constant.js` after replacement done. +- Remove any `dist/` assumptions in scripts/docs. +- Keep local dev command (`node --import=tsx src/bin.ts --watch`) unchanged. + +## Critical Files To Modify +- `package.json` +- `.github/workflows/release.yml` +- `README.md` +- `src/constants.ts` +- `scripts/build-sea.mjs` (new) +- `scripts/inject-constant.js` (delete) + +## Verification Plan +1. Build artifacts +- Run `pnpm build`. +- Confirm outputs exist for: + - `dist-app/linux-x64/cmdly` + - `dist-app/darwin-arm64/cmdly` + +2. Binary smoke tests +- `./dist-app/linux-x64/cmdly --help` +- `./dist-app/linux-x64/cmdly --version` +- Run representative command path that triggers: + - model fetch/cache (`src/utils/models.ts` path), + - action handling (`run`/`copy`/`explain` from `src/utils/actions.ts`). + +3. Runtime filesystem behavior +- Execute with isolated HOME (`HOME=$(mktemp -d)`) and verify config + credential files create in user dirs. + +4. CI/release validation +- On main push, ensure release workflow builds both targets and attaches binaries/checksums. +- Validate download-and-run from generated GitHub Release assets. + +## Risks + Mitigations +- ESM/CJS SEA entry mismatch -> lock bundle output format and set matching `mainFormat`. +- Cross-target instability with code cache/snapshot -> keep both disabled in v1. +- macOS ARM build requirements -> run mac target build on macOS runner. +- Distribution breakage from full cutover -> add pre-release dry run before first public release. + +## Rollback +- Reintroduce previous JS packaging scripts (`ncc` + `dist/index.js` bin) via single revert commit. +- Keep release tags/changesets flow independent so binary pipeline failure does not block code release hotfix. diff --git a/package.json b/package.json index eaed3ef..3e4ea4c 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "build:sea:darwin-arm64": "node scripts/build-sea.mjs --target darwin-arm64", "verify:sea": "node scripts/verify-sea.mjs", "prepare": "effect-language-service patch", - "clean": "rm -rf dist dist-app .sea", + "clean": "rm -rf dist .sea", "typecheck": "tsc -b tsconfig.json", "test": "vitest run", "changeset-version": "changeset version", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b0012d5..f9d7f31 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,7 @@ +allowBuilds: + '@parcel/watcher': true + esbuild: true + msgpackr-extract: true onlyBuiltDependencies: - '@parcel/watcher' - esbuild diff --git a/scripts/build-sea.mjs b/scripts/build-sea.mjs index 5d99de9..fd71504 100644 --- a/scripts/build-sea.mjs +++ b/scripts/build-sea.mjs @@ -64,9 +64,9 @@ if (Number.parseInt(process.versions.node.split(".")[0], 10) !== 26) { } const seaDir = join(rootDir, ".sea"); -const bundlePath = join(seaDir, "index.mjs"); +const bundlePath = join(seaDir, "index.cjs"); const seaConfigPath = join(seaDir, `sea-config.${target}.json`); -const outDir = join(rootDir, "dist-app", target); +const outDir = join(rootDir, "dist", target); const outFile = join(outDir, "cmdly"); rmSync(seaDir, { recursive: true, force: true }); @@ -77,7 +77,7 @@ await build({ entryPoints: [join(rootDir, "src", "bin.ts")], outfile: bundlePath, bundle: true, - format: "esm", + format: "cjs", platform: "node", target: "node26", tsconfig: join(rootDir, "tsconfig.json"), @@ -90,7 +90,7 @@ await build({ const seaConfig = { main: bundlePath, - mainFormat: "module", + mainFormat: "commonjs", output: outFile, disableExperimentalSEAWarning: true, useSnapshot: false, diff --git a/scripts/verify-sea.mjs b/scripts/verify-sea.mjs index 2fe6114..56d2bc7 100644 --- a/scripts/verify-sea.mjs +++ b/scripts/verify-sea.mjs @@ -24,7 +24,7 @@ if (!target) { ); } -const binary = join(rootDir, "dist-app", target, "cmdly"); +const binary = join(rootDir, "dist", target, "cmdly"); if (!existsSync(binary)) { throw new Error(`SEA binary not found: ${binary}`); From c55c54144d7d1b0670e2137fb6a542805d52fc1b Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah Date: Sun, 10 May 2026 15:12:49 +0100 Subject: [PATCH 04/18] feat: update --- .github/workflows/release.yml | 63 +++++++++++++---------------------- README.md | 7 ++++ package.json | 2 -- scripts/build-sea.mjs | 56 ++++++++++++++++++------------- scripts/verify-sea.mjs | 30 ++++++++++++++++- 5 files changed, 92 insertions(+), 66 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b1ec78..5e17715 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,58 +38,43 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - binaries-linux: - name: Binary linux-x64 + binaries: + name: Binary ${{ matrix.target }} if: github.ref_type == 'tag' - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v4 - - - name: Install dependencies - uses: ./.github/actions/setup - - - name: Build linux-x64 binary - run: pnpm build:sea:linux-x64 - - - name: Archive linux-x64 - run: | - mkdir -p release - cp dist/linux-x64/cmdly release/cmdly-linux-x64 - tar -C release -czf release/cmdly-linux-x64.tar.gz cmdly-linux-x64 - sha256sum release/cmdly-linux-x64.tar.gz > release/cmdly-linux-x64.tar.gz.sha256 - - - name: Upload linux-x64 assets - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - run: | - gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1 || gh release create "${GITHUB_REF_NAME}" --generate-notes || true - gh release upload "${GITHUB_REF_NAME}" release/cmdly-linux-x64.tar.gz release/cmdly-linux-x64.tar.gz.sha256 --clobber - - binaries-darwin-arm64: - name: Binary darwin-arm64 - if: github.ref_type == 'tag' - runs-on: macos-latest + runs-on: ${{ matrix.runs_on }} timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - target: linux-x64 + runs_on: ubuntu-latest + checksum_cmd: sha256sum + - target: linux-arm64 + runs_on: ubuntu-24.04-arm + checksum_cmd: sha256sum + - target: darwin-arm64 + runs_on: macos-latest + checksum_cmd: shasum -a 256 steps: - uses: actions/checkout@v4 - name: Install dependencies uses: ./.github/actions/setup - - name: Build darwin-arm64 binary - run: pnpm build:sea:darwin-arm64 + - name: Build binary + run: pnpm build:sea - - name: Archive darwin-arm64 + - name: Archive binary run: | mkdir -p release - cp dist/darwin-arm64/cmdly release/cmdly-darwin-arm64 - tar -C release -czf release/cmdly-darwin-arm64.tar.gz cmdly-darwin-arm64 - shasum -a 256 release/cmdly-darwin-arm64.tar.gz > release/cmdly-darwin-arm64.tar.gz.sha256 + 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" - - name: Upload darwin-arm64 assets + - name: Upload assets env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} run: | gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1 || gh release create "${GITHUB_REF_NAME}" --generate-notes || true - gh release upload "${GITHUB_REF_NAME}" release/cmdly-darwin-arm64.tar.gz release/cmdly-darwin-arm64.tar.gz.sha256 --clobber + gh release upload "${GITHUB_REF_NAME}" "release/cmdly-${{ matrix.target }}.tar.gz" "release/cmdly-${{ matrix.target }}.tar.gz.sha256" --clobber diff --git a/README.md b/README.md index bac0425..ebc71d7 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ sha256sum -c cmdly-linux-x64.tar.gz.sha256 ### Supported targets - `linux-x64` +- `linux-arm64` - `darwin-arm64` ### Not supported in v1 @@ -54,6 +55,12 @@ sha256sum -c cmdly-linux-x64.tar.gz.sha256 - Windows - Intel macOS (`darwin-x64`) +### SEA build notes (dev) + +- Configured targets: `darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64` +- Runtime excludes `darwin-x64` and prints warning with reason +- SEA entry is CommonJS (`format: "cjs"`, `mainFormat: "commonjs"`) because ESM bundle hit dynamic `require("node:assert")` path at runtime + ## Quick Start 1. **Configure your AI provider:** diff --git a/package.json b/package.json index 3e4ea4c..bf4100c 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,6 @@ "dev": "node --import=tsx src/bin.ts --watch", "build": "pnpm build:sea", "build:sea": "node scripts/build-sea.mjs", - "build:sea:linux-x64": "node scripts/build-sea.mjs --target linux-x64", - "build:sea:darwin-arm64": "node scripts/build-sea.mjs --target darwin-arm64", "verify:sea": "node scripts/verify-sea.mjs", "prepare": "effect-language-service patch", "clean": "rm -rf dist .sea", diff --git a/scripts/build-sea.mjs b/scripts/build-sea.mjs index fd71504..9010810 100644 --- a/scripts/build-sea.mjs +++ b/scripts/build-sea.mjs @@ -10,53 +10,61 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const rootDir = resolve(__dirname, ".."); -const supportedTargets = new Set(["linux-x64", "darwin-arm64"]); +const targets = ["darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64"]; +const excludedTargets = new Map([ + [ + "darwin-x64", + "Node SEA on Intel macOS is currently unstable (upstream issue); excluding target.", + ], +]); + +const enabledTargets = targets.filter((target) => { + const reason = excludedTargets.get(target); + if (!reason) { + return true; + } + console.warn(`[build:sea] Excluding ${target}: ${reason}`); + return false; +}); const hostTarget = () => { const platform = process.platform; const arch = process.arch; + if (platform === "linux" && arch === "arm64") { + return "linux-arm64"; + } if (platform === "linux" && arch === "x64") { return "linux-x64"; } + if (platform === "darwin" && arch === "x64") { + return "darwin-x64"; + } if (platform === "darwin" && arch === "arm64") { return "darwin-arm64"; } return null; }; - -const parseTarget = () => { - const idx = process.argv.indexOf("--target"); - if (idx === -1) { - return null; - } - const target = process.argv[idx + 1]; - if (!target) { - throw new Error("Missing value for --target"); - } - return target; -}; - -const targetArg = parseTarget(); const detectedHostTarget = hostTarget(); -if (targetArg && !supportedTargets.has(targetArg)) { - throw new Error(`Unsupported target: ${targetArg}`); -} - if (!detectedHostTarget) { throw new Error( - `Unsupported host platform ${process.platform}-${process.arch}. Supported host targets: linux-x64, darwin-arm64.`, + `Unsupported host platform ${process.platform}-${process.arch}. Configured targets: ${targets.join(", ")}.`, ); } -const target = targetArg ?? detectedHostTarget; - -if (target !== detectedHostTarget) { +if (!targets.includes(detectedHostTarget)) { throw new Error( - `Target ${target} does not match host ${detectedHostTarget}. Build this target on a matching runner.`, + `Host target ${detectedHostTarget} is not configured in targets list.`, ); } +if (!enabledTargets.includes(detectedHostTarget)) { + const reason = excludedTargets.get(detectedHostTarget) ?? "Excluded target."; + throw new Error(`Host target ${detectedHostTarget} is excluded. ${reason}`); +} + +const target = detectedHostTarget; + if (Number.parseInt(process.versions.node.split(".")[0], 10) !== 26) { throw new Error( `Node 26.x required for SEA build, found ${process.version}. Use Node 26.x.`, diff --git a/scripts/verify-sea.mjs b/scripts/verify-sea.mjs index 56d2bc7..d12f9a1 100644 --- a/scripts/verify-sea.mjs +++ b/scripts/verify-sea.mjs @@ -6,10 +6,33 @@ import { fileURLToPath } from "node:url"; const __filename = fileURLToPath(import.meta.url); const rootDir = resolve(__filename, "..", ".."); +const targets = ["darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64"]; +const excludedTargets = new Map([ + [ + "darwin-x64", + "Node SEA on Intel macOS is currently unstable (upstream issue); excluding target.", + ], +]); + +const enabledTargets = targets.filter((target) => { + const reason = excludedTargets.get(target); + if (!reason) { + return true; + } + console.warn(`[verify:sea] Excluding ${target}: ${reason}`); + return false; +}); + const detectTarget = () => { + if (process.platform === "linux" && process.arch === "arm64") { + return "linux-arm64"; + } if (process.platform === "linux" && process.arch === "x64") { return "linux-x64"; } + if (process.platform === "darwin" && process.arch === "x64") { + return "darwin-x64"; + } if (process.platform === "darwin" && process.arch === "arm64") { return "darwin-arm64"; } @@ -20,10 +43,15 @@ const target = detectTarget(); if (!target) { throw new Error( - `Unsupported host ${process.platform}-${process.arch}. Supported: linux-x64, darwin-arm64`, + `Unsupported host ${process.platform}-${process.arch}. Configured targets: ${targets.join(", ")}.`, ); } +if (!enabledTargets.includes(target)) { + const reason = excludedTargets.get(target) ?? "Excluded target."; + throw new Error(`Host target ${target} is excluded. ${reason}`); +} + const binary = join(rootDir, "dist", target, "cmdly"); if (!existsSync(binary)) { From f99bd2db10db489ee3069992b0cb47eca6cc37c4 Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah Date: Sun, 10 May 2026 15:31:59 +0100 Subject: [PATCH 05/18] feat: update --- .github/workflows/release.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5e17715..23ad2ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,8 +73,11 @@ jobs: ${{ matrix.checksum_cmd }} "release/cmdly-${{ matrix.target }}.tar.gz" > "release/cmdly-${{ matrix.target }}.tar.gz.sha256" - name: Upload assets + uses: softprops/action-gh-release@v3 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + release/cmdly-${{ matrix.target }}.tar.gz + release/cmdly-${{ matrix.target }}.tar.gz.sha256 env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - run: | - gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1 || gh release create "${GITHUB_REF_NAME}" --generate-notes || true - gh release upload "${GITHUB_REF_NAME}" "release/cmdly-${{ matrix.target }}.tar.gz" "release/cmdly-${{ matrix.target }}.tar.gz.sha256" --clobber + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From c572fda156dea0081978be96a5f8b0ce585c6ca9 Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah Date: Sun, 10 May 2026 16:06:02 +0100 Subject: [PATCH 06/18] WIP --- .github/workflows/release.yml | 74 ++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 23ad2ff..8ded50a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,8 +3,6 @@ on: push: branches: - main - tags: - - "*" concurrency: ${{ github.workflow }}-${{ github.ref }} permissions: contents: write @@ -13,9 +11,13 @@ permissions: jobs: version: name: Version - if: github.ref_type == 'branch' runs-on: ubuntu-latest timeout-minutes: 10 + outputs: + published: ${{ steps.changesets.outputs.published }} + published_packages: ${{ steps.changesets.outputs.publishedPackages }} + release_tag: ${{ steps.release_meta.outputs.tag }} + release_version: ${{ steps.release_meta.outputs.version }} steps: - uses: actions/checkout@v4 with: @@ -33,14 +35,42 @@ jobs: with: version: pnpm changeset-version publish: pnpm changeset-publish + createGithubReleases: false commit: "chore: version packages" title: "chore: version packages" env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Derive release metadata + id: release_meta + if: steps.changesets.outputs.published == 'true' + uses: actions/github-script@v7 + env: + PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} + with: + script: | + const raw = process.env.PUBLISHED_PACKAGES ?? "[]"; + let packages; + try { + packages = JSON.parse(raw); + } catch { + core.setFailed(`Invalid publishedPackages JSON: ${raw}`); + return; + } + + const match = packages.find((pkg) => pkg?.name === "cmdly"); + if (!match?.version) { + core.setFailed(`Could not find cmdly version in publishedPackages: ${raw}`); + return; + } + + core.setOutput("version", match.version); + core.setOutput("tag", `v${match.version}`); binaries: name: Binary ${{ matrix.target }} - if: github.ref_type == 'tag' + if: needs.version.outputs.published == 'true' + needs: version runs-on: ${{ matrix.runs_on }} timeout-minutes: 15 strategy: @@ -72,12 +102,38 @@ jobs: 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" - - name: Upload assets - uses: softprops/action-gh-release@v3 - if: startsWith(github.ref, 'refs/tags/') + - name: Upload artifacts + uses: actions/upload-artifact@v4 with: - files: | + name: cmdly-${{ matrix.target }} + path: | release/cmdly-${{ matrix.target }}.tar.gz release/cmdly-${{ matrix.target }}.tar.gz.sha256 + + release: + name: Release assets + needs: + - version + - binaries + if: needs.version.outputs.published == 'true' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: release-assets + merge-multiple: true + + - name: Upload assets to GitHub Release + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ needs.version.outputs.release_tag }} + name: ${{ needs.version.outputs.release_tag }} + files: | + release-assets/*.tar.gz + release-assets/*.tar.gz.sha256 + generate_release_notes: true + fail_on_unmatched_files: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 7ba8b159f89817f5fea86edb0e0b5fbebb2eefa8 Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah Date: Sun, 10 May 2026 16:20:27 +0100 Subject: [PATCH 07/18] feat --- .github/workflows/release.yml | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ded50a..75e3d4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: release_tag: ${{ steps.release_meta.outputs.tag }} release_version: ${{ steps.release_meta.outputs.version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 @@ -49,23 +49,15 @@ jobs: PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} with: script: | - const raw = process.env.PUBLISHED_PACKAGES ?? "[]"; - let packages; - try { - packages = JSON.parse(raw); - } catch { - core.setFailed(`Invalid publishedPackages JSON: ${raw}`); + const packages = JSON.parse(process.env.PUBLISHED_PACKAGES ?? "[]"); + const version = packages?.[0]?.version; + if (!version) { + core.setFailed(`Could not read version from publishedPackages: ${process.env.PUBLISHED_PACKAGES}`); return; } - const match = packages.find((pkg) => pkg?.name === "cmdly"); - if (!match?.version) { - core.setFailed(`Could not find cmdly version in publishedPackages: ${raw}`); - return; - } - - core.setOutput("version", match.version); - core.setOutput("tag", `v${match.version}`); + core.setOutput("version", version); + core.setOutput("tag", `v${version}`); binaries: name: Binary ${{ matrix.target }} @@ -87,7 +79,7 @@ jobs: runs_on: macos-latest checksum_cmd: shasum -a 256 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install dependencies uses: ./.github/actions/setup @@ -103,7 +95,7 @@ jobs: ${{ matrix.checksum_cmd }} "release/cmdly-${{ matrix.target }}.tar.gz" > "release/cmdly-${{ matrix.target }}.tar.gz.sha256" - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: cmdly-${{ matrix.target }} path: | @@ -120,7 +112,7 @@ jobs: timeout-minutes: 10 steps: - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: path: release-assets merge-multiple: true From efc286ead89ad2f3156890a26587f9c55a6d509c Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah Date: Mon, 11 May 2026 04:30:09 +0100 Subject: [PATCH 08/18] feat: update --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75e3d4e..b4630fc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,7 +44,7 @@ jobs: - name: Derive release metadata id: release_meta if: steps.changesets.outputs.published == 'true' - uses: actions/github-script@v7 + uses: actions/github-script@v9 env: PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }} with: From cb57c5bd48a01b9d2693fb1e850054b59ae9dd0a Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah Date: Mon, 11 May 2026 04:43:16 +0100 Subject: [PATCH 09/18] fix: resolve ts errors --- src/commands/configure.ts | 15 +++++++----- src/commands/explain.ts | 11 +++++---- src/commands/suggest.ts | 11 +++++---- src/services/ai.ts | 8 +++---- src/services/credentials.ts | 19 +++++++-------- src/utils/files.ts | 48 +++++++++++++++++++++++++++---------- 6 files changed, 69 insertions(+), 43 deletions(-) diff --git a/src/commands/configure.ts b/src/commands/configure.ts index 2fc9a6b..7ee0314 100644 --- a/src/commands/configure.ts +++ b/src/commands/configure.ts @@ -10,14 +10,17 @@ import type { Credentials, CredentialValue } from "@/types.js"; import { markCurrentChoice, stripCurrentMarker } from "@/utils/config.js"; import { fetchAndCacheModels, fetchProviderModels } from "@/utils/models.js"; -const layers = Layer.mergeAll( - GitHubOAuthService.Default, - CredentialsService.Default, - ConfigService.Default, +const baseLayer = Layer.mergeAll( NodeFileSystem.layer, NodePath.layer, + GitHubOAuthService.Default, ); +const programLayer = Layer.mergeAll( + CredentialsService.Default, + ConfigService.Default, +).pipe(Layer.provideMerge(baseLayer)); + const configureCommand = Command.make("configure", {}, () => Effect.gen(function* () { yield* Console.log( @@ -29,7 +32,7 @@ const configureCommand = Command.make("configure", {}, () => const currentConfig = yield* configService.config(); const credentialsService = yield* CredentialsService; const currentCredentials = yield* credentialsService.getCredentials.pipe( - Effect.catchTag("CredentialsError", () => Effect.succeed(undefined)), + Effect.catchTag("CredentialsError", () => Effect.void), ); const modelsData = yield* fetchAndCacheModels(); @@ -227,7 +230,7 @@ const configureCommand = Command.make("configure", {}, () => } yield* Console.log("\nConfiguration saved!\n"); - }).pipe(Effect.provide(layers)), + }).pipe(Effect.provide(programLayer)), ); export { configureCommand }; diff --git a/src/commands/explain.ts b/src/commands/explain.ts index b2ee286..60c831d 100644 --- a/src/commands/explain.ts +++ b/src/commands/explain.ts @@ -8,14 +8,17 @@ import { GitHubOAuthService } from "@/services/github-oauth.js"; import { highlightMarkdown } from "@/utils/highlight.js"; import { displayStream } from "@/utils/stream.js"; -const programLayer = Layer.mergeAll( +const baseLayer = Layer.mergeAll( + NodeFileSystem.layer, + NodePath.layer, GitHubOAuthService.Default, +); + +const programLayer = Layer.mergeAll( CredentialsService.Default, ConfigService.Default, AiService.Default, - NodeFileSystem.layer, - NodePath.layer, -); +).pipe(Layer.provideMerge(baseLayer)); const command = Args.optional(Args.text({ name: "command" })); diff --git a/src/commands/suggest.ts b/src/commands/suggest.ts index 7cb1452..9ec91db 100644 --- a/src/commands/suggest.ts +++ b/src/commands/suggest.ts @@ -12,14 +12,17 @@ import { handleAction } from "@/utils/actions.js"; import { highlightShell } from "@/utils/highlight.js"; import { displayStream } from "@/utils/stream.js"; -const programLayer = Layer.mergeAll( +const baseLayer = Layer.mergeAll( + NodeFileSystem.layer, + NodePath.layer, GitHubOAuthService.Default, +); + +const programLayer = Layer.mergeAll( CredentialsService.Default, ConfigService.Default, AiService.Default, - NodeFileSystem.layer, - NodePath.layer, -); +).pipe(Layer.provideMerge(baseLayer)); const targetChoices = ["shell", "git"] as const; diff --git a/src/services/ai.ts b/src/services/ai.ts index e01e1be..0c1666b 100644 --- a/src/services/ai.ts +++ b/src/services/ai.ts @@ -24,11 +24,9 @@ const aiService = Effect.gen(function* () { const apiKey = yield* credentials.getCrendtial(credentialKey); if (!apiKey) { - return yield* Effect.fail( - new AiServiceError({ - message: `No API key found for provider: ${providerName}`, - }), - ); + return yield* new AiServiceError({ + message: `No API key found for provider: ${providerName}`, + }); } const provider = yield* getProvider(providerName, apiKey.access); diff --git a/src/services/credentials.ts b/src/services/credentials.ts index f967dbf..790ec48 100644 --- a/src/services/credentials.ts +++ b/src/services/credentials.ts @@ -64,12 +64,10 @@ const getCredentials = Effect.gen(function* () { const credentials = yield* loadCredentials; if (!credentials) { - return yield* Effect.fail( - new CredentialsError({ - message: - "No credentials found. Please run 'configure' command to set up your credentials.", - }), - ); + return yield* new CredentialsError({ + message: + "No credentials found. Please run 'configure' command to set up your credentials.", + }); } return credentials; @@ -84,11 +82,9 @@ const getCredential = (provider: keyof CredentialsRecord) => const credential = credentials[provider]; if (!credential) { - return yield* Effect.fail( - new CredentialsError({ - message: `No credentials found for provider: ${provider}. Please run 'configure' command.`, - }), - ); + return yield* new CredentialsError({ + message: `No credentials found for provider: ${provider}. Please run 'configure' command.`, + }); } if ( @@ -144,6 +140,7 @@ const credentialsService = Effect.succeed({ saveCredentials(newCredentials), }); +/** @effect-expect-leaking FileSystem Path */ export class CredentialsService extends Effect.Service()( "CredentialsService", { diff --git a/src/utils/files.ts b/src/utils/files.ts index f4dc425..dff6bf1 100644 --- a/src/utils/files.ts +++ b/src/utils/files.ts @@ -53,16 +53,17 @@ export const readJsonFile = ( const content = yield* fs.readFileString(filePath); - const parsed = yield* Effect.try({ - try: () => JSON.parse(content) as unknown, - catch: (error) => - new FileSystemError({ - message: "Failed to parse JSON", - operation: "JSON.parse", - path: filePath, - cause: error, - }), - }); + const parsed = yield* Schema.decodeUnknown(Schema.parseJson())(content).pipe( + Effect.mapError( + (error) => + new FileSystemError({ + message: "Failed to parse JSON", + operation: "Schema.parseJson", + path: filePath, + cause: error, + }), + ), + ); return yield* Schema.decodeUnknown(schema)(parsed).pipe( Effect.mapError( @@ -84,12 +85,33 @@ export const writeJsonFile = ( filePath: string, data: unknown, mode = 0o644, -): Effect.Effect => +): Effect.Effect => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; - yield* fs.writeFileString(filePath, JSON.stringify(data, null, 2)); + const json = yield* Schema.encode(Schema.parseJson())(data).pipe( + Effect.mapError( + (error) => + new FileSystemError({ + message: "Failed to stringify JSON", + operation: "Schema.encode(parseJson)", + path: filePath, + cause: error, + }), + ), + ); + yield* fs.writeFileString(filePath, json); yield* setFilePermissions(filePath, mode); - }); + }).pipe( + Effect.mapError( + (error) => + new FileSystemError({ + message: "Failed to write JSON file", + operation: "writeJsonFile", + path: filePath, + cause: error, + }), + ), + ); /** * Set file permissions (Unix only) From 282a991b88c22ae0a2f0612a599f0570f610ac03 Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah <86387828+Armadillidiid@users.noreply.github.com> Date: Mon, 11 May 2026 05:28:08 +0100 Subject: [PATCH 10/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .opencode/plans/1778415295647-swift-island.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.opencode/plans/1778415295647-swift-island.md b/.opencode/plans/1778415295647-swift-island.md index efebdac..20a0612 100644 --- a/.opencode/plans/1778415295647-swift-island.md +++ b/.opencode/plans/1778415295647-swift-island.md @@ -1,7 +1,7 @@ # Node SEA Cutover Plan ## Goal -Replace `ncc` packaging with Node SEA. Full cutover now. Primary distribution: GitHub Release binaries only. No Windows in v1. Unsiged binaries in v1. +Replace `ncc` packaging with Node SEA. Full cutover now. Primary distribution: GitHub Release binaries only. No Windows in v1. Unsigned binaries in v1. ## Confirmed Decisions - Distribution: GitHub binaries only. From 84b2694d9c86d4d75948be168f0c8b36caa46e14 Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah Date: Mon, 11 May 2026 05:55:21 +0100 Subject: [PATCH 11/18] update --- .github/workflows/check.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index f4eb975..bae39b2 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install dependencies uses: ./.github/actions/setup types: @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install dependencies uses: ./.github/actions/setup - run: pnpm typecheck @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install dependencies uses: ./.github/actions/setup - run: pnpm test From 4a87cac0b1210777f28453b93a42eb2bd07b06ba Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah Date: Mon, 11 May 2026 05:59:35 +0100 Subject: [PATCH 12/18] reset README --- README.md | 43 +------------------------------------------ 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/README.md b/README.md index ebc71d7..018eb02 100644 --- a/README.md +++ b/README.md @@ -16,51 +16,10 @@ https://github.com/user-attachments/assets/d3dda9c8-e202-4d7a-bbc8-bac9b32f7ac7 ## Installation -Download prebuilt binary from GitHub Releases. - -### Linux x64 - ```bash -curl -fsSL -o /tmp/cmdly-linux-x64.tar.gz https://github.com/Armadillidiid/cmdly/releases/latest/download/cmdly-linux-x64.tar.gz -tar -xzf /tmp/cmdly-linux-x64.tar.gz -C /tmp -install -m 755 /tmp/cmdly-linux-x64 ~/.local/bin/cmdly +npm install -g cmdly ``` -### macOS arm64 - -```bash -curl -fsSL -o /tmp/cmdly-darwin-arm64.tar.gz https://github.com/Armadillidiid/cmdly/releases/latest/download/cmdly-darwin-arm64.tar.gz -tar -xzf /tmp/cmdly-darwin-arm64.tar.gz -C /tmp -install -m 755 /tmp/cmdly-darwin-arm64 ~/.local/bin/cmdly -``` - -Ensure `~/.local/bin` is on `PATH`. - -### Verify checksum (optional) - -```bash -curl -fsSL -O https://github.com/Armadillidiid/cmdly/releases/latest/download/cmdly-linux-x64.tar.gz -curl -fsSL -O https://github.com/Armadillidiid/cmdly/releases/latest/download/cmdly-linux-x64.tar.gz.sha256 -sha256sum -c cmdly-linux-x64.tar.gz.sha256 -``` - -### Supported targets - -- `linux-x64` -- `linux-arm64` -- `darwin-arm64` - -### Not supported in v1 - -- Windows -- Intel macOS (`darwin-x64`) - -### SEA build notes (dev) - -- Configured targets: `darwin-arm64`, `darwin-x64`, `linux-x64`, `linux-arm64` -- Runtime excludes `darwin-x64` and prints warning with reason -- SEA entry is CommonJS (`format: "cjs"`, `mainFormat: "commonjs"`) because ESM bundle hit dynamic `require("node:assert")` path at runtime - ## Quick Start 1. **Configure your AI provider:** From e433da09caa4ef2bc6303aa2c3195dee8736a423 Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah <86387828+Armadillidiid@users.noreply.github.com> Date: Mon, 11 May 2026 09:22:30 +0100 Subject: [PATCH 13/18] Update GITHUB_TOKEN to use GH_TOKEN secret --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4630fc..85db1a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,7 +39,7 @@ jobs: commit: "chore: version packages" title: "chore: version packages" env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - name: Derive release metadata id: release_meta From f5e49423bd9ac60e516ee1b203c5cf99640f0cb4 Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah <86387828+Armadillidiid@users.noreply.github.com> Date: Mon, 11 May 2026 09:42:20 +0100 Subject: [PATCH 14/18] Add npm update step in release workflow Added step to update npm to the latest version before building. --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85db1a7..55d0939 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,9 @@ jobs: - name: Build run: pnpm build + + - name: Update npm to latest version # Trusted publishing requires version 11.5.1 or later. + run: npm install -g npm@latest - name: Create Release Pull Request or Tag Release id: changesets From 47cb0902ea0973bfa6316c1173ee6e4b2409ea14 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 11:32:43 +0100 Subject: [PATCH 15/18] chore: add changeset for SEA build system and Effect layer fix (#7) Agent-Logs-Url: https://github.com/Armadillidiid/cmdly/sessions/947cf820-7da9-42b1-9e91-a7059500f90a Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Armadillidiid <86387828+Armadillidiid@users.noreply.github.com> --- .changeset/brave-lions-jump.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/brave-lions-jump.md diff --git a/.changeset/brave-lions-jump.md b/.changeset/brave-lions-jump.md new file mode 100644 index 0000000..f9b4b6f --- /dev/null +++ b/.changeset/brave-lions-jump.md @@ -0,0 +1,9 @@ +--- +"cmdly": minor +--- + +feat: replace ncc packaging with Node SEA standalone binaries + +Switched the build pipeline from `@vercel/ncc` to Node.js Single Executable Applications (SEA). Binaries are now produced for `linux-x64`, `linux-arm64`, and `darwin-arm64` targets (`darwin-x64` is excluded due to upstream instability). The release workflow uploads the resulting binaries and checksums as GitHub Release assets. + +Also fixes Effect layer dependency ordering in the `configure`, `explain`, and `suggest` commands so that `NodeFileSystem` and `NodePath` layers are provided before dependent services, resolving TypeScript errors at runtime layer composition. From 64b9a135848095a0a36943c5ecf539aab4ff3788 Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah <86387828+Armadillidiid@users.noreply.github.com> Date: Mon, 11 May 2026 12:55:52 +0100 Subject: [PATCH 16/18] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- scripts/build-sea.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/build-sea.mjs b/scripts/build-sea.mjs index 9010810..045709b 100644 --- a/scripts/build-sea.mjs +++ b/scripts/build-sea.mjs @@ -65,9 +65,10 @@ if (!enabledTargets.includes(detectedHostTarget)) { const target = detectedHostTarget; -if (Number.parseInt(process.versions.node.split(".")[0], 10) !== 26) { +const nodeMajor = Number.parseInt(process.versions.node.split(".")[0], 10); +if (nodeMajor < 26) { throw new Error( - `Node 26.x required for SEA build, found ${process.version}. Use Node 26.x.`, + `Node 26+ required for SEA build, found ${process.version}. Use Node 26 or newer.`, ); } From a073a31dfc24875fd5bd9b63e75ada1e8ccc80f3 Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah <86387828+Armadillidiid@users.noreply.github.com> Date: Mon, 11 May 2026 12:57:36 +0100 Subject: [PATCH 17/18] Update changeset-publish script to use publish command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bf4100c..c885f54 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "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": "pnpm build && pnpm vitest && changeset publish && changeset tag && git push --tags", "format": "biome format --write", "lint": "biome lint --write", "check": "biome check --write --unsafe" From ade76a9c1d100c4557bcead77f52ec8150fed7b7 Mon Sep 17 00:00:00 2001 From: Emmanuel Isenah <86387828+Armadillidiid@users.noreply.github.com> Date: Mon, 11 May 2026 12:59:45 +0100 Subject: [PATCH 18/18] Update package.json to include bin and files Added bin and files fields to package.json --- package.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.json b/package.json index c885f54..f97f03a 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,12 @@ "url": "https://github.com/Armadillidiid/cmdly/issues" }, "author": "Emmanuel Isenah (https://emmanuelisenah.com)", + "bin": { + "cmdly": "./dist/index.js" + }, + "files": [ + "dist" + ], "repository": { "type": "git", "url": "https://github.com/Armadillidiid/cmdly"