diff --git a/.changeset/publish-to-npmjs.md b/.changeset/publish-to-npmjs.md new file mode 100644 index 0000000..bade992 --- /dev/null +++ b/.changeset/publish-to-npmjs.md @@ -0,0 +1,13 @@ +--- +'@kin0992/tsconfig': minor +'@kin0992/oxc-config': minor +'@kin0992/vitest-config': minor +'@kin0992/skills': minor +--- + +Publish to the public npm registry (npmjs.org) with provenance. + +These packages now ship from `https://registry.npmjs.org` under public access +instead of GitHub Packages. Consumers no longer need a `read:packages` token or +a scoped `.npmrc` entry — a plain `pnpm add @kin0992/` works anonymously. +Existing GitHub Packages versions remain available but will not receive updates. diff --git a/.github/workflows/release-reusable.yml b/.github/workflows/release-reusable.yml index 14c1018..3af3517 100644 --- a/.github/workflows/release-reusable.yml +++ b/.github/workflows/release-reusable.yml @@ -4,14 +4,20 @@ on: workflow_call: inputs: npm-scope: - description: 'npm scope to authenticate (e.g. @kin0992).' - required: true + description: 'npm scope to authenticate (e.g. @kin0992). Leave empty for tag-only consumers that do not publish to a registry.' + required: false type: string + default: '' registry-url: description: 'npm registry to authenticate against.' required: false type: string - default: 'https://npm.pkg.github.com' + default: 'https://registry.npmjs.org' + provenance: + description: 'Publish with npm provenance (requires id-token: write and a public registry).' + required: false + type: boolean + default: true secrets: app_id: description: 'GitHub App client ID used to mint a token for git ops and publishing.' @@ -19,6 +25,9 @@ on: app_private_key: description: 'GitHub App private key (PEM).' required: true + npm_token: + description: 'Token for the target npm registry (e.g. an npmjs.org automation token). Required only when npm-scope is set.' + required: false permissions: contents: read @@ -31,8 +40,7 @@ jobs: permissions: contents: write pull-requests: write - packages: write - id-token: write + id-token: write # required for npm provenance steps: - name: Harden runner uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 @@ -55,21 +63,25 @@ jobs: - name: Setup uses: kin0992/dev-toolkit/.github/actions/setup@main + - name: Build + run: pnpm turbo run build + - name: Configure npm scope auth + if: ${{ inputs.npm-scope != '' }} env: NPM_SCOPE: ${{ inputs.npm-scope }} REGISTRY_URL: ${{ inputs.registry-url }} - NODE_AUTH_TOKEN: ${{ github.token }} run: | set -euo pipefail host="${REGISTRY_URL#https://}" host="${host#http://}" host="${host%/}" + # Write the token as an unexpanded ${NODE_AUTH_TOKEN} reference so the + # secret is never persisted to disk; npm/pnpm interpolate it from the + # environment at publish time. Runs after Build so no earlier pnpm + # invocation sees the unresolved placeholder. echo "${NPM_SCOPE}:registry=${REGISTRY_URL}" >> .npmrc - echo "//${host}/:_authToken=${NODE_AUTH_TOKEN}" >> .npmrc - - - name: Build - run: pnpm turbo run build + echo "//${host}/:_authToken=\${NODE_AUTH_TOKEN}" >> .npmrc - name: Create release PR or publish uses: changesets/action@63a615b9cd06ba9a3e6d13796c7fbcb080a60a0b # 1.8.0 @@ -80,5 +92,5 @@ jobs: title: 'Release new artifacts' env: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - NODE_AUTH_TOKEN: ${{ github.token }} - NPM_TOKEN: ${{ github.token }} + NODE_AUTH_TOKEN: ${{ secrets.npm_token }} + NPM_CONFIG_PROVENANCE: ${{ inputs.provenance }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dd07eaa..5aba130 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,8 +19,7 @@ jobs: permissions: contents: write pull-requests: write - packages: write - id-token: write + id-token: write # required for npm provenance uses: ./.github/workflows/release-reusable.yml with: npm-scope: '@kin0992' diff --git a/CONSUMING.md b/CONSUMING.md index 08f04c5..6e61610 100644 --- a/CONSUMING.md +++ b/CONSUMING.md @@ -4,7 +4,7 @@ 1. **Reusable GitHub workflows & composite actions** — referenced by path/ref. Pin to `main` for rolling updates or to a tag (e.g. `v1`) for stability. 2. **AI Skills marketplace** — install one plugin per category into Claude Code, Copilot CLI, or VS Code. No auth required. -3. **npm packages on GitHub Packages** — under the `@kin0992` scope. Auth required (GitHub Packages requires a PAT with `read:packages` even for packages from a public repo). +3. **npm packages on npmjs.org** — public packages under the `@kin0992` scope, published with provenance. No auth required to install. --- @@ -67,7 +67,12 @@ jobs: ### Release (Changesets + npm publish) -Consumers call the reusable workflow `release-reusable.yml` and pass their own npm scope. `release.yml` in this repo is a thin local entry-point — do not reference it from another repository. +Consumers call the reusable workflow `release-reusable.yml`. `release.yml` in this repo is a thin local entry-point — do not reference it from another repository. + +There are two modes: + +- **Publishing** — set `npm-scope` and provide the `npm_token` secret. The workflow authenticates to the registry and your `release` script publishes packages. +- **Tag-only** — omit `npm-scope` (and `npm_token`). The npm auth step is skipped entirely, so a repo whose `release` script only runs e.g. `changeset tag` needs no npm credentials. ```yaml name: Release @@ -80,32 +85,43 @@ jobs: permissions: contents: write pull-requests: write - packages: write - id-token: write + id-token: write # required for npm provenance uses: kin0992/dev-toolkit/.github/workflows/release-reusable.yml@main with: npm-scope: '@yourscope' - # registry-url defaults to https://npm.pkg.github.com; override for npmjs.org etc. - # registry-url: 'https://registry.npmjs.org' + # registry-url defaults to https://registry.npmjs.org + # provenance defaults to true (needs id-token: write and a public registry) secrets: app_id: ${{ secrets.APP_ID }} app_private_key: ${{ secrets.APP_PRIVATE_KEY }} + npm_token: ${{ secrets.NPM_TOKEN }} ``` **Inputs:** -| Input | Required | Default | Description | -| -------------- | -------- | ---------------------------- | ---------------------------------------------- | -| `npm-scope` | yes | — | npm scope to authenticate (e.g. `@yourscope`). | -| `registry-url` | no | `https://npm.pkg.github.com` | Registry the scope is authenticated against. | +| Input | Required | Default | Description | +| -------------- | -------- | ---------------------------- | ---------------------------------------------------------------- | +| `npm-scope` | no | `''` | npm scope to authenticate (e.g. `@yourscope`). Empty = tag-only. | +| `registry-url` | no | `https://registry.npmjs.org` | Registry the scope is authenticated against. | +| `provenance` | no | `true` | Publish with npm provenance (needs `id-token: write`). | + +**Secrets:** + +| Secret | Required | Description | +| ----------------- | -------- | --------------------------------------------------------------------- | +| `app_id` | yes | GitHub App client ID used to mint a token for git ops. | +| `app_private_key` | yes | GitHub App private key (PEM). | +| `npm_token` | no | Token for the target registry. Required only when `npm-scope` is set. | **Prerequisites:** - Your `package.json` must define `release` and `version-packages` scripts. -- The workflow always uses a **GitHub App token** for all git operations and publishing. This ensures that tags pushed by the release workflow trigger downstream workflows (e.g. `deploy.yml`), which `GITHUB_TOKEN` cannot do. +- The workflow uses a **GitHub App token** for all git operations. This ensures that tags pushed by the release workflow trigger downstream workflows (e.g. `deploy.yml`), which `GITHUB_TOKEN` cannot do. - Create a GitHub App with `contents: write` and `pull-requests: write` permissions on your repository. - Add `APP_ID` (the numeric App ID — used as `client-id`) and `APP_PRIVATE_KEY` (the PEM private key) as repository secrets. - Install the GitHub App on the repository. +- For publishing to npmjs.org, generate an **Automation** access token for an account that owns (or is a member of) the target scope and add it as the `NPM_TOKEN` repository secret. +- Provenance requires the repository to be **public** and the job to grant `id-token: write`. Each published package should set `"publishConfig": { "access": "public", "provenance": true }`. ### Security analysis (CodeQL + secret scan + pnpm audit) @@ -212,25 +228,12 @@ jobs: --- -## 2. npm packages from GitHub Packages - -### Authenticate - -Create or update `.npmrc` in the consumer repo: - -``` -@kin0992:registry=https://npm.pkg.github.com -//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN} -``` - -In CI, set `NODE_AUTH_TOKEN` to `${{ secrets.GITHUB_TOKEN }}` (the default token has `read:packages` scope when `permissions.packages: read` is set). - -`dev-toolkit`'s shared setup action also forwards a token to pnpm-related steps as -`NODE_AUTH_TOKEN`, defaulting to `github.token`. If you override job -permissions in a caller workflow, keep `packages: read` available or private -`@kin0992/*` installs will still fail. +## 2. npm packages from npmjs.org -Locally, generate a Personal Access Token with `read:packages` and put it in `~/.npmrc` or export it as `NODE_AUTH_TOKEN`. +The `@kin0992/*` packages are published publicly to npmjs.org, so **no +authentication or `.npmrc` configuration is required** to install them — in CI +or locally. They resolve from the default registry like any other public +package. ### Install configs @@ -285,8 +288,8 @@ export { default } from '@kin0992/vitest-config/node'; ### Install AI Skills (programmatic / non-Copilot tooling) -> **Note:** GitHub Packages requires authentication even for packages from a public repository. -> Marketplace install (below) has no auth requirement and is recommended for Claude Code / Copilot users. +> **Note:** For Claude Code / Copilot users, the marketplace install (below) is +> the simpler path — skills auto-load without any `node_modules` plumbing. ```sh pnpm add -D @kin0992/skills diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1024eaa..6fa842b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,8 +33,8 @@ pnpm changeset ``` Commit the generated file with your PR. On merge to `main`, the release -workflow opens a "Version Packages" PR; merging that PR publishes to GitHub -Packages. +workflow opens a "Version Packages" PR; merging that PR publishes the +`@kin0992/*` packages to the public npm registry (npmjs.org) with provenance. Changes scoped purely to `infra/`, workflows, docs, or the marketplace manifests do not need a changeset. diff --git a/README.md b/README.md index adbfbbb..a62fdff 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ pnpm format:check pnpm changeset ``` -Commit the generated file with your PR. On merge to `main`, the `release` workflow opens a "Version Packages" PR; merging that PR publishes to GitHub Packages. +Commit the generated file with your PR. On merge to `main`, the `release` workflow opens a "Version Packages" PR; merging that PR publishes the `@kin0992/*` packages to the public npm registry (npmjs.org) with [provenance](https://docs.npmjs.com/generating-provenance-statements). ## License diff --git a/infra/Pulumi.prod.yaml b/infra/Pulumi.prod.yaml index 8969618..e3c99be 100644 --- a/infra/Pulumi.prod.yaml +++ b/infra/Pulumi.prod.yaml @@ -2,3 +2,7 @@ config: devToolkit:repoName: dev-toolkit devToolkit:defaultBranch: main github:owner: kin0992 + devToolkit:npmToken: + secure: AAABAJBpCXqVz2KYKQtgTPB4oEUkgDOLc1AhEHGhogxUi8vnCc6VjyIyIR0ilZhWdozxWL2eByCJ+w8dW9o/YyJ8o2j/XADZ + devToolkit:pulumiAccessToken: + secure: AAABAFzzntAVe+s+2gBaaraLf9IrltK0s4ReAMCJ/FTm3/37kuSOyzOeZQPZR0GetzDO9Oj1A+i6qaJn53h6A0ct9lEmbIitL7Ap5Q== diff --git a/infra/index.ts b/infra/index.ts index 8b467cc..6cabb0d 100644 --- a/infra/index.ts +++ b/infra/index.ts @@ -3,6 +3,12 @@ import * as github from '@pulumi/github'; const cfg = new pulumi.Config('devToolkit'); const repoName = cfg.get('repoName') ?? 'dev-toolkit'; +// npmjs.org Automation token consumed by the release workflow to publish +// @kin0992/* packages with provenance. +const npmToken = cfg.requireSecret('npmToken'); +// Pulumi Cloud access token consumed by the IaC drift/deploy workflows to +// authenticate the Pulumi CLI in non-interactive CI runs. +const pulumiAccessToken = cfg.requireSecret('pulumiAccessToken'); export const repo = new github.Repository('dev-toolkit', { name: repoName, @@ -22,8 +28,6 @@ export const repo = new github.Repository('dev-toolkit', { squashMergeCommitMessage: 'PR_BODY', vulnerabilityAlerts: true, allowUpdateBranch: true, - mergeCommitTitle: 'PR_TITLE', - mergeCommitMessage: 'PR_BODY', topics: [ 'platform-engineering', 'github-actions', @@ -78,6 +82,18 @@ new github.RepositoryDependabotSecurityUpdates('dependabot-updates', { enabled: true, }); +new github.ActionsSecret('npm-token', { + repository: repo.name, + secretName: 'NPM_TOKEN', + value: npmToken, +}); + +new github.ActionsSecret('pulumi-access-token', { + repository: repo.name, + secretName: 'PULUMI_ACCESS_TOKEN', + value: pulumiAccessToken, +}); + // TODO: Move Private Vulnerability Reporting under Pulumi when // @pulumi/github exposes the resource (upstream Terraform gap). // Enabled out-of-band: PUT /repos/{owner}/{repo}/private-vulnerability-reporting. diff --git a/packages/oxc-config/package.json b/packages/oxc-config/package.json index 9d7864b..6ed5a5e 100644 --- a/packages/oxc-config/package.json +++ b/packages/oxc-config/package.json @@ -24,8 +24,8 @@ "./oxlint/testing": "./oxlint/testing.js" }, "publishConfig": { - "access": "restricted", - "registry": "https://npm.pkg.github.com" + "access": "public", + "provenance": true }, "scripts": { "format": "oxfmt", diff --git a/packages/skills/package.json b/packages/skills/package.json index 61e74c6..382c0b3 100644 --- a/packages/skills/package.json +++ b/packages/skills/package.json @@ -19,8 +19,8 @@ "./git/branch-name": "./src/git/branch-name/SKILL.md" }, "publishConfig": { - "access": "restricted", - "registry": "https://npm.pkg.github.com" + "access": "public", + "provenance": true }, "scripts": { "format": "oxfmt", diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json index 60afb50..82c6856 100644 --- a/packages/tsconfig/package.json +++ b/packages/tsconfig/package.json @@ -21,8 +21,8 @@ "./library": "./library.json" }, "publishConfig": { - "access": "restricted", - "registry": "https://npm.pkg.github.com" + "access": "public", + "provenance": true }, "scripts": { "format": "oxfmt", diff --git a/packages/vitest-config/package.json b/packages/vitest-config/package.json index 3af0ce2..3466c9c 100644 --- a/packages/vitest-config/package.json +++ b/packages/vitest-config/package.json @@ -17,8 +17,8 @@ "./node": "./node.js" }, "publishConfig": { - "access": "restricted", - "registry": "https://npm.pkg.github.com" + "access": "public", + "provenance": true }, "scripts": { "format": "oxfmt",