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
13 changes: 13 additions & 0 deletions .changeset/publish-to-npmjs.md
Original file line number Diff line number Diff line change
@@ -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/<pkg>` works anonymously.
Existing GitHub Packages versions remain available but will not receive updates.
36 changes: 24 additions & 12 deletions .github/workflows/release-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,30 @@ 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.'
required: true
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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 }}
3 changes: 1 addition & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
65 changes: 34 additions & 31 deletions CONSUMING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand Down Expand Up @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions infra/Pulumi.prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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==
20 changes: 18 additions & 2 deletions infra/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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',
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions packages/oxc-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions packages/skills/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions packages/tsconfig/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"./library": "./library.json"
},
"publishConfig": {
"access": "restricted",
"registry": "https://npm.pkg.github.com"
"access": "public",
"provenance": true
},
"scripts": {
"format": "oxfmt",
Expand Down
4 changes: 2 additions & 2 deletions packages/vitest-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"./node": "./node.js"
},
"publishConfig": {
"access": "restricted",
"registry": "https://npm.pkg.github.com"
"access": "public",
"provenance": true
},
"scripts": {
"format": "oxfmt",
Expand Down
Loading