diff --git a/.github/npm-trusted-publishing.md b/.github/npm-trusted-publishing.md new file mode 100644 index 000000000..981913178 --- /dev/null +++ b/.github/npm-trusted-publishing.md @@ -0,0 +1,101 @@ +# npm Trusted Publishing (OIDC) + +Publish from GitHub Actions without a long-lived `NPM_TOKEN`. +npm mints a short-lived credential per workflow run via OIDC. + +Official docs: https://docs.npmjs.com/trusted-publishers/ + +## Prerequisites + +- You must be an **owner** (or have settings access) on each package below. +- Workflow that publishes: `.github/workflows/release.yaml` (filename is `release.yaml`). +- GitHub repo: `effect-app/libs` (public, GitHub-hosted runners). + +## Packages to configure + +Configure **each** publishable package (one trusted publisher per package): + +| Package | npm settings URL | +| --- | --- | +| `effect-app` | https://www.npmjs.com/package/effect-app/access | +| `@effect-app/infra` | https://www.npmjs.com/package/@effect-app/infra/access | +| `@effect-app/vue` | https://www.npmjs.com/package/@effect-app/vue/access | +| `@effect-app/vue-components` | https://www.npmjs.com/package/@effect-app/vue-components/access | +| `@effect-app/cli` | https://www.npmjs.com/package/@effect-app/cli/access | +| `@effect-app/eslint-codegen-model` | https://www.npmjs.com/package/@effect-app/eslint-codegen-model/access | +| `@effect-app/eslint-shared-config` | https://www.npmjs.com/package/@effect-app/eslint-shared-config/access | + +If the UI shows **Settings** / **Trusted Publisher** instead of `/access`, use that page for the same package. + +## Exact steps on npmjs.com (per package) + +Repeat for every package in the table: + +1. Open the package page → **Settings** (or the access URL above). +2. Find **Trusted Publisher**. +3. Under **Select your publisher**, choose **GitHub Actions**. +4. Fill in exactly: + - **Organization or user:** `effect-app` + - **Repository:** `libs` + - **Workflow filename:** `release.yaml` + (filename only, including `.yaml` — not `.yml`, not a path) + - **Environment name:** leave **empty** (unless you later add a GitHub Environment to the job) + - **Allowed actions:** select **`npm publish`** (required) +5. Save. + +npm does **not** validate the config on save — mismatches only show up at publish time (often as `E404`). + +## Order of operations (important) + +1. **Configure trusted publishers** for all packages above (npm side). +2. **Merge** the PR with package `repository` fields + this doc + `.github/release.oidc.yaml`. +3. **Apply the workflow** (maintainer, local credentials with `workflows` permission): + + ```bash + cp .github/release.oidc.yaml .github/workflows/release.yaml + git add .github/workflows/release.yaml && git commit -m "ci: enable npm OIDC trusted publishing" && git push + ``` + +4. On push to `main`, the Changesets workflow publishes any unpublished versions (e.g. `4.0.0-beta.299`). +5. Confirm success in the Actions log: look for + `No NPM_TOKEN found, but OIDC is available - using npm trusted publishing`. +6. After a successful OIDC publish, optionally harden each package: + - **Publishing access** → **Require two-factor authentication and disallow tokens** + - Revoke the old GitHub secret `NPM_TOKEN` and any automation tokens on npm. + +Do **not** set `NPM_TOKEN` on the publish step once OIDC is intended — `changesets/action` will use the long-lived token instead of OIDC when that env var is present. + +## GitHub workflow (apply once) + +The intended Changesets workflow lives at **`.github/release.oidc.yaml`**. + +A GitHub App without the `workflows` permission cannot update files under +`.github/workflows/`. A maintainer must apply it once: + +```bash +cp .github/release.oidc.yaml .github/workflows/release.yaml +git add .github/workflows/release.yaml +git commit -m "ci: enable npm OIDC trusted publishing in Changesets workflow" +git push +``` + +That workflow includes: + +- `permissions.id-token: write` +- npm CLI upgraded to latest (≥ `11.5.1` required for OIDC) +- Node `24.14` +- **No** `NPM_TOKEN` on the publish step +- Package `repository.url` points at `https://github.com/effect-app/libs.git` (set in package.json) + +## Troubleshooting + +| Symptom | Likely cause | +| --- | --- | +| `E404` / “could not be found or you do not have permission” | Trusted publisher mismatch (org/repo/workflow filename), or OIDC not used because `NPM_TOKEN` is still set | +| `ENEEDAUTH` / unable to authenticate | Missing `id-token: write`, old npm CLI, or self-hosted runner (not supported) | +| Only some packages publish | Trusted publisher missing on those packages | + +## Optional hardening + +- GitHub **Environment** (e.g. `npm`) on the `version` job with required reviewers; if used, put the same environment name in every package’s trusted publisher config. +- Keep a **read-only** granular npm token only if install needs private packages (not required for this public monorepo today). diff --git a/.github/release.oidc.yaml b/.github/release.oidc.yaml new file mode 100644 index 000000000..7a0426788 --- /dev/null +++ b/.github/release.oidc.yaml @@ -0,0 +1,79 @@ +# Intended contents of .github/workflows/release.yaml for npm Trusted Publishing (OIDC). +# Apply with: cp .github/release.oidc.yaml .github/workflows/release.yaml +# (Bot cannot push workflow file updates; a maintainer must apply this once.) +name: Changesets +on: + push: + branches: + - main +# id-token: write enables npm Trusted Publishing (OIDC). +# Do not pass NPM_TOKEN on the publish step — changesets/action prefers a +# long-lived token when set and will skip OIDC. +# See .github/npm-trusted-publishing.md for npmjs.com setup. +permissions: + contents: write + pull-requests: write + id-token: write +env: + CI: true + PNPM_CACHE_FOLDER: .pnpm-store +jobs: + ci: + uses: ./.github/workflows/ci.yml + + version: + needs: [ci] + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - name: checkout code repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 24.14 + registry-url: https://registry.npmjs.org + + # Trusted publishing requires npm CLI >= 11.5.1 + - name: Ensure npm supports OIDC trusted publishing + run: npm install -g npm@latest && npm --version + + - uses: pnpm/action-setup@v3 + name: Install pnpm + id: pnpm-install + with: + run_install: false + + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: install packages + run: pnpm install --frozen-lockfile + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: create and publish versions + uses: changesets/action@v1 + with: + version: pnpm ci:version + commit: "chore: update versions" + title: "chore: update versions" + publish: pnpm ci:publish + createGithubReleases: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 503abcfbe..7a0426788 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,11 +1,19 @@ +# Intended contents of .github/workflows/release.yaml for npm Trusted Publishing (OIDC). +# Apply with: cp .github/release.oidc.yaml .github/workflows/release.yaml +# (Bot cannot push workflow file updates; a maintainer must apply this once.) name: Changesets on: push: branches: - main +# id-token: write enables npm Trusted Publishing (OIDC). +# Do not pass NPM_TOKEN on the publish step — changesets/action prefers a +# long-lived token when set and will skip OIDC. +# See .github/npm-trusted-publishing.md for npmjs.com setup. permissions: contents: write pull-requests: write + id-token: write env: CI: true PNPM_CACHE_FOLDER: .pnpm-store @@ -27,6 +35,11 @@ jobs: uses: actions/setup-node@v4 with: node-version: 24.14 + registry-url: https://registry.npmjs.org + + # Trusted publishing requires npm CLI >= 11.5.1 + - name: Ensure npm supports OIDC trusted publishing + run: npm install -g npm@latest && npm --version - uses: pnpm/action-setup@v3 name: Install pnpm @@ -53,10 +66,6 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - # - name: setup pnpm config - # run: pnpm config set store-dir $PNPM_CACHE_FOLDER - name: create and publish versions uses: changesets/action@v1 @@ -68,4 +77,3 @@ jobs: createGithubReleases: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/packages/cli/package.json b/packages/cli/package.json index e72a3266a..4bb4a59d5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -3,6 +3,11 @@ "version": "2.1.0-beta.37", "license": "MIT", "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/effect-app/libs.git", + "directory": "packages/cli" + }, "bin": { "effect-app": "./bin.js", "effa": "./bin.js", diff --git a/packages/effect-app/package.json b/packages/effect-app/package.json index 6aa32e1e2..5a9195ebf 100644 --- a/packages/effect-app/package.json +++ b/packages/effect-app/package.json @@ -3,6 +3,11 @@ "version": "4.0.0-beta.299", "license": "MIT", "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/effect-app/libs.git", + "directory": "packages/effect-app" + }, "dependencies": { "@tsconfig/strictest": "^2.0.8", "date-fns": "^4.4.0", diff --git a/packages/eslint-codegen-model/package.json b/packages/eslint-codegen-model/package.json index 5b2132459..206eda85f 100644 --- a/packages/eslint-codegen-model/package.json +++ b/packages/eslint-codegen-model/package.json @@ -4,6 +4,11 @@ "sideEffects": false, "type": "module", "version": "2.0.0-beta.27", + "repository": { + "type": "git", + "url": "https://github.com/effect-app/libs.git", + "directory": "packages/eslint-codegen-model" + }, "bin": { "effect-app-codegen": "./dist/cli.js" }, diff --git a/packages/eslint-shared-config/package.json b/packages/eslint-shared-config/package.json index a77c8fe17..2ed478c30 100644 --- a/packages/eslint-shared-config/package.json +++ b/packages/eslint-shared-config/package.json @@ -5,6 +5,11 @@ "license": "MIT", "private": false, "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/effect-app/libs.git", + "directory": "packages/eslint-shared-config" + }, "sideEffects": false, "scripts": { "watch": "pnpm build -w", diff --git a/packages/infra/package.json b/packages/infra/package.json index 9c69d0e0e..32d499492 100644 --- a/packages/infra/package.json +++ b/packages/infra/package.json @@ -3,6 +3,11 @@ "version": "4.0.0-beta.299", "license": "MIT", "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/effect-app/libs.git", + "directory": "packages/infra" + }, "dependencies": { "@faker-js/faker": "^8.4.1", "effect-app": "workspace:*", diff --git a/packages/vue-components/package.json b/packages/vue-components/package.json index 0672d9116..185af25cc 100644 --- a/packages/vue-components/package.json +++ b/packages/vue-components/package.json @@ -1,6 +1,11 @@ { "name": "@effect-app/vue-components", "version": "4.0.0-beta.299", + "repository": { + "type": "git", + "url": "https://github.com/effect-app/libs.git", + "directory": "packages/vue-components" + }, "scripts": { "check": "vue-tsc", "build": "pnpm build:run", diff --git a/packages/vue/package.json b/packages/vue/package.json index 17e43c3f4..2528d7b77 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -3,7 +3,12 @@ "version": "4.0.0-beta.299", "license": "MIT", "type": "module", - "homepage": "https://github.com/effect-ts-app/libs/tree/main/packages/vue", + "repository": { + "type": "git", + "url": "https://github.com/effect-app/libs.git", + "directory": "packages/vue" + }, + "homepage": "https://github.com/effect-app/libs/tree/main/packages/vue", "dependencies": { "@formatjs/intl": "^4.1.12", "@tanstack/vue-query": "5.96.2",