From 880e07e95be6e86688c3ece3254b5732117eef2a Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:34:53 +1100 Subject: [PATCH 1/2] feat(release): add sklt npm publish flow --- .github/workflows/npm-publish.yaml | 82 ++++++++++++++++++++++++++ .github/workflows/release-please.yaml | 1 - .mise.toml | 7 ++- README.md | 2 +- docs/distribution/npm.md | 33 +++++++++-- package.json | 4 +- src/cli.ts | 2 +- tests/cli.test.ts | 4 +- tests/distribution/npm-package.test.ts | 2 +- 9 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/npm-publish.yaml diff --git a/.github/workflows/npm-publish.yaml b/.github/workflows/npm-publish.yaml new file mode 100644 index 0000000..eda69a2 --- /dev/null +++ b/.github/workflows/npm-publish.yaml @@ -0,0 +1,82 @@ +name: npm Publish + +on: + release: + types: + - published + workflow_dispatch: + inputs: + ref: + description: Git ref to publish, usually a release tag like v1.0.0 + required: true + type: string + npm_tag: + description: npm dist-tag to publish under + required: false + default: latest + type: string + dry_run: + description: Run npm publish in dry-run mode without uploading + required: false + default: false + type: boolean + +permissions: + contents: read + +concurrency: + group: npm-publish-${{ github.event_name == 'release' && github.event.release.tag_name || inputs.ref }} + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + environment: npm-publish + + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + with: + ref: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.ref }} + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 + with: + node-version: 20 + registry-url: https://registry.npmjs.org + + - name: Setup mise + uses: jdx/mise-action@c37c93293d6b742fc901e1406b8f764f6fb19dac + + - name: Install dependencies + run: mise run install + + - name: Validate npm package + run: mise run npm-smoke + + - name: Check published version + id: package + shell: bash + run: | + set -euo pipefail + PACKAGE_NAME=$(node --print "require('./package.json').name") + PACKAGE_VERSION=$(node --print "require('./package.json').version") + if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version --registry=https://registry.npmjs.org >/dev/null 2>&1; then + echo "published=true" >> "$GITHUB_OUTPUT" + echo "${PACKAGE_NAME}@${PACKAGE_VERSION} is already published; skipping." + else + echo "published=false" >> "$GITHUB_OUTPUT" + fi + + - name: Dry run publish + if: github.event_name == 'workflow_dispatch' && inputs.dry_run + env: + NPM_DIST_TAG: ${{ inputs.npm_tag == 'latest' && 'dry-run' || inputs.npm_tag }} + run: mise run npm-publish-dry-run + + - name: Publish package + if: steps.package.outputs.published != 'true' && !(github.event_name == 'workflow_dispatch' && inputs.dry_run) + run: npm publish --access public --tag "${{ github.event_name == 'workflow_dispatch' && inputs.npm_tag || 'latest' }}" diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml index 8de027b..f12c73c 100644 --- a/.github/workflows/release-please.yaml +++ b/.github/workflows/release-please.yaml @@ -20,6 +20,5 @@ jobs: uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 with: token: ${{ secrets.RELEASE_PLEASE_TOKEN || github.token }} - command: manifest config-file: release-please-config.json manifest-file: .release-please-manifest.json diff --git a/.mise.toml b/.mise.toml index 3601cb9..2fcdb13 100644 --- a/.mise.toml +++ b/.mise.toml @@ -11,7 +11,7 @@ run = "bun install" run = "mise run install && bunx vitest run" [tasks.ci] -run = "mise run test" +run = "mise run test && mise run npm-publish-dry-run" [tasks.build] run = "mise run install && bun scripts/build-release.ts" @@ -32,4 +32,7 @@ run = "mise run build -- --targets=linux-x64-musl,linux-arm64-musl && docker bui run = "mise run install && bun scripts/build-npm-cli.ts" [tasks.npm-smoke] -run = "mise run build-npm && npm pack >/tmp/skillet-npm-pack.log && PACKAGE=$(tail -n 1 /tmp/skillet-npm-pack.log) && npx --yes --package \"./$PACKAGE\" skillet --help && REPO_DIR=\"$PWD\" && TMP_DIR=$(mktemp -d) && (cd \"$TMP_DIR\" && bun init -y >/dev/null 2>&1 && bun add \"$REPO_DIR/$PACKAGE\" >/dev/null && bunx --bun skillet --help) && rm -rf \"$TMP_DIR\" \"$PACKAGE\"" +run = "mise run build-npm && npm pack >/tmp/skillet-npm-pack.log && PACKAGE=$(tail -n 1 /tmp/skillet-npm-pack.log) && npx --yes --package \"./$PACKAGE\" sklt --help && REPO_DIR=\"$PWD\" && TMP_DIR=$(mktemp -d) && (cd \"$TMP_DIR\" && bun init -y >/dev/null 2>&1 && bun add \"$REPO_DIR/$PACKAGE\" >/dev/null && bunx --bun sklt --help) && rm -rf \"$TMP_DIR\" \"$PACKAGE\"" + +[tasks.npm-publish-dry-run] +run = "mise run build-npm && DIST_TAG=${NPM_DIST_TAG:-dry-run} && npm publish --dry-run --access public --tag \"$DIST_TAG\"" diff --git a/README.md b/README.md index ce2b4ba..a62294d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Skillet installs, discovers, and updates `SKILL.md`-based skills across supporte | Homebrew | `brew install skillet` | Configured | | Chocolatey | `choco install skillet` | Configured | | winget | `winget install skillet` | Configured | -| npm / npx | `npx skillet ...` | Configured | +| npm / npx | `npx sklt ...` | Configured | | Docker | `docker run ... skillet ...` | Configured | | Local dev | `mise run dev -- --help` | Available | diff --git a/docs/distribution/npm.md b/docs/distribution/npm.md index 0c010b9..1f00f56 100644 --- a/docs/distribution/npm.md +++ b/docs/distribution/npm.md @@ -2,13 +2,13 @@ Skillet publishes an npm package so users can run: -- `npx skillet ...` -- `bunx skillet ...` +- `npx sklt ...` +- `bunx sklt ...` ## Packaging Model - `src/cli.ts` is bundled to `dist/npm/cli.js` targeting Node. -- `package.json` `bin.skillet` points to `dist/npm/cli.js`. +- `package.json` exposes `sklt` pointing to `dist/npm/cli.js`. - `prepack` rebuilds the npm CLI bundle automatically. ## Local Validation @@ -16,8 +16,9 @@ Skillet publishes an npm package so users can run: ```bash mise run build-npm npm pack -npx --yes --package ./skillet-.tgz skillet --help -bunx --bun --package ./skillet-.tgz skillet --help +npx --yes --package ./sklt-.tgz sklt --help +bunx --bun --package ./sklt-.tgz sklt --help +mise run npm-publish-dry-run ``` ## Publish @@ -25,3 +26,25 @@ bunx --bun --package ./skillet-.tgz skillet --help ```bash npm publish --access public ``` + +## GitHub Actions Publish + +- Automatic publish runs from `.github/workflows/npm-publish.yaml` when a GitHub release is published. +- Manual publish is available through `workflow_dispatch`; provide an explicit git ref, usually a release tag like `v1.0.0`. +- Manual publish also supports a dry run so you can inspect the package contents without uploading to npm. +- `mise run ci` now includes the npm publish dry run so package publishing is validated in CI before release. +- The workflow uses the GitHub Actions environment `npm-publish`. +- Add `NPM_TOKEN` as an environment secret in `npm-publish` using the value from your local `.env`. +- GitHub Actions cannot read your local `.env` file directly. + +Manual dispatch inputs: + +- `ref`: tag, branch, or commit to publish +- `npm_tag`: npm dist-tag, defaults to `latest` +- `dry_run`: when true, runs `npm publish --dry-run` and skips the real publish step +- When `dry_run` is true, the workflow uses the provided `npm_tag`, or falls back to `dry-run` if you leave it at `latest`. + +Local command: + +- `mise run npm-publish-dry-run`: builds the npm CLI bundle and runs `npm publish --dry-run --tag dry-run` +- Override the dry-run tag if needed with `NPM_DIST_TAG= mise run npm-publish-dry-run` diff --git a/package.json b/package.json index 90ddeaa..20d3fe0 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "skillet", + "name": "sklt", "version": "0.0.0", "private": false, "type": "module", @@ -11,7 +11,7 @@ "README.md" ], "bin": { - "skillet": "dist/npm/cli.js" + "sklt": "dist/npm/cli.js" }, "scripts": { "dev": "bun src/cli.ts", diff --git a/src/cli.ts b/src/cli.ts index 9643d51..5090e73 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -39,7 +39,7 @@ const COMMAND_HELP: Record = { "generate-lock": "Generate skillet.lock.yaml", }; -const cli = cac("skillet"); +const cli = cac("sklt"); const GLOBAL_FLAGS = new Set(["-y", "--yes", "--verbose", "-v", "--version", "-h", "--help"]); cli diff --git a/tests/cli.test.ts b/tests/cli.test.ts index 150b452..b3f7bc4 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -25,7 +25,7 @@ describe("cli", () => { expect(result.status).toBe(0); expect(result.stdout).toContain("Usage:"); - expect(result.stdout).toContain("$ skillet find [...args]"); + expect(result.stdout).toContain("$ sklt find [...args]"); }); test("prints version from environment override", () => { @@ -37,7 +37,7 @@ describe("cli", () => { }); expect(result.status).toBe(0); - expect(result.stdout).toContain("skillet/1.2.3"); + expect(result.stdout).toContain("sklt/1.2.3"); }); test("returns clear error for unknown command", () => { diff --git a/tests/distribution/npm-package.test.ts b/tests/distribution/npm-package.test.ts index 173e887..c9f0743 100644 --- a/tests/distribution/npm-package.test.ts +++ b/tests/distribution/npm-package.test.ts @@ -17,6 +17,6 @@ describe("npm package bundle", () => { expect(result.status).toBe(0); expect(result.stdout).toContain("Usage:"); - expect(result.stdout).toContain("$ skillet [options]"); + expect(result.stdout).toContain("$ sklt [options]"); }); }); From e2126a1c560f60cbfffa6fdffe4d1b3bc84a4875 Mon Sep 17 00:00:00 2001 From: Johnny Huynh <27847622+johnnyhuy@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:39:46 +1100 Subject: [PATCH 2/2] fix(build): smoke test sklt artifacts --- scripts/smoke-artifact.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/smoke-artifact.ts b/scripts/smoke-artifact.ts index 113e750..98b5ba5 100644 --- a/scripts/smoke-artifact.ts +++ b/scripts/smoke-artifact.ts @@ -25,7 +25,7 @@ function main(): void { stdio: ["ignore", "pipe", "pipe"], }); - if (!output.includes("skillet/")) { + if (!output.includes("sklt/")) { throw new Error(`Unexpected smoke output: ${output}`); }