Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/npm-publish.yaml
Original file line number Diff line number Diff line change
@@ -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' }}"
1 change: 0 additions & 1 deletion .github/workflows/release-please.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 5 additions & 2 deletions .mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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\""
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

Expand Down
33 changes: 28 additions & 5 deletions docs/distribution/npm.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,49 @@

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

```bash
mise run build-npm
npm pack
npx --yes --package ./skillet-<version>.tgz skillet --help
bunx --bun --package ./skillet-<version>.tgz skillet --help
npx --yes --package ./sklt-<version>.tgz sklt --help
bunx --bun --package ./sklt-<version>.tgz sklt --help
mise run npm-publish-dry-run
```

## Publish

```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=<tag> mise run npm-publish-dry-run`
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "skillet",
"name": "sklt",
"version": "0.0.0",
"private": false,
"type": "module",
Expand All @@ -11,7 +11,7 @@
"README.md"
],
"bin": {
"skillet": "dist/npm/cli.js"
"sklt": "dist/npm/cli.js"
},
"scripts": {
"dev": "bun src/cli.ts",
Expand Down
2 changes: 1 addition & 1 deletion scripts/smoke-artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}

Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const COMMAND_HELP: Record<CommandName, string> = {
"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
Expand Down
4 changes: 2 additions & 2 deletions tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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", () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/distribution/npm-package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ describe("npm package bundle", () => {

expect(result.status).toBe(0);
expect(result.stdout).toContain("Usage:");
expect(result.stdout).toContain("$ skillet <command> [options]");
expect(result.stdout).toContain("$ sklt <command> [options]");
});
});