From dc82a28e23cfb2a4f72a04ef3df7386df46a093a Mon Sep 17 00:00:00 2001 From: lemys lopez Date: Thu, 18 Jun 2026 15:28:55 -0500 Subject: [PATCH 1/4] fix(ci): Switch to npm OIDC trusted publishing for secure releases --- .github/workflows/release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 40b8d81..73c68bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ on: permissions: contents: write # @semantic-release/github: create the GitHub Release and git tag + id-token: write # Required for npm OIDC trusted publishing issues: write # @semantic-release/github: comment on released issues pull-requests: write # @semantic-release/github: comment on released pull requests @@ -24,6 +25,7 @@ jobs: with: node-version: '22' cache: 'npm' + registry-url: 'https://registry.npmjs.org' - name: Show toolchain versions run: node -v && npm -v @@ -40,6 +42,6 @@ jobs: run: npx semantic-release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + # No NPM_TOKEN needed when using OIDC trusted publishing + # NODE_AUTH_TOKEN is not required; npm CLI will use OIDC automatically NPM_CONFIG_REGISTRY: https://registry.npmjs.org From 1f7b3e492d35a7450263d15a744ddf42ccd72197 Mon Sep 17 00:00:00 2001 From: lemys lopez Date: Fri, 19 Jun 2026 08:29:26 -0500 Subject: [PATCH 2/4] feat(release): scope package to @kamansoft org and fix CI/CD pipeline - Rename package to @kamansoft/vite-plugin-flatwave-react (npm org scope) - Update all internal references: e2e tests, example workspace, vite.config - Fix release.yml: add CI prerequisite job, concurrency guard, npm upgrade to latest (required for OIDC), remove conflicting registry-url from setup-node, wire NPM_TOKEN to @semantic-release/npm - Fix ci.yml: remove redundant push-to-main trigger (release.yml re-runs CI internally before releasing) - Package @kamansoft/vite-plugin-flatwave-react@0.1.0 published to npm --- .github/workflows/ci.yml | 8 +- .github/workflows/release.yml | 77 ++++++++++++++++--- e2e/example.test.ts | 2 +- examples/basic-react-site/package.json | 2 +- examples/basic-react-site/vite.config.ts | 2 +- package-lock.json | 13 ++-- package.json | 2 +- .../vite-plugin-flatwave-react/package.json | 2 +- 8 files changed, 84 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b88d901..297162b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,11 @@ name: CI Validation on: pull_request: branches: [main] - push: - branches: [main] + # push: branches: [main] is intentionally removed. + # The release.yml workflow contains its own CI gate job that runs first, + # so running CI twice on every merge is redundant. Branch protection rules + # (required status checks) enforce that this job passes before any PR can + # be merged, which is the primary quality gate. permissions: contents: read @@ -38,7 +41,6 @@ jobs: HUSKY: 0 test-matrix: - if: github.event_name == 'pull_request' runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 73c68bd..f669488 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,27 +5,75 @@ on: branches: [main] workflow_dispatch: -permissions: - contents: write # @semantic-release/github: create the GitHub Release and git tag - id-token: write # Required for npm OIDC trusted publishing - issues: write # @semantic-release/github: comment on released issues - pull-requests: write # @semantic-release/github: comment on released pull requests +# Prevent simultaneous releases from racing each other. +# cancel-in-progress: false ensures an in-flight release is never killed mid-run. +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false jobs: + # ─── 1. CI gate ──────────────────────────────────────────────────────────── + # Re-run the full validation suite on main before releasing. + # Branch protection (required status checks on PRs) is the primary guard, + # but this job provides a hard stop inside the release workflow itself. + ci: + name: CI Validation + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + env: + HUSKY: 0 + + - name: Run full validation + run: npm run validate + env: + HUSKY: 0 + + # ─── 2. Release & publish ────────────────────────────────────────────────── release: + name: Release & Publish to npm + needs: [ci] # only runs after CI passes runs-on: ubuntu-latest + permissions: + contents: write # create the GitHub Release and git tag + id-token: write # npm OIDC trusted publishing + package provenance + issues: write # let semantic-release comment on released issues + pull-requests: write # let semantic-release comment on released PRs + steps: - name: Checkout uses: actions/checkout@v4 with: - fetch-depth: 0 # full history + tags so semantic-release can analyze commits + fetch-depth: 0 # full history + all tags — required by semantic-release - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '22' cache: 'npm' - registry-url: 'https://registry.npmjs.org' + # IMPORTANT: registry-url is intentionally NOT set here. + # When registry-url is present, setup-node writes an .npmrc that + # references ${NODE_AUTH_TOKEN}. That file conflicts with + # @semantic-release/npm's own auth setup and causes + # EINVALIDNPMTOKEN errors even with a valid token. + # Reference: https://semantic-release.gitbook.io/semantic-release/recipes/ci-configurations/github-actions + + - name: Upgrade npm to latest + # npm >=11.5.1 is required for OIDC trusted publishing. + # Node 22 ships with npm 10.x, so we upgrade before proceeding. + run: npm install -g npm@latest - name: Show toolchain versions run: node -v && npm -v @@ -42,6 +90,15 @@ jobs: run: npx semantic-release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # No NPM_TOKEN needed when using OIDC trusted publishing - # NODE_AUTH_TOKEN is not required; npm CLI will use OIDC automatically - NPM_CONFIG_REGISTRY: https://registry.npmjs.org + # NPM_TOKEN is read directly by @semantic-release/npm to create a + # temporary .npmrc for authentication. This is separate from + # NODE_AUTH_TOKEN (which is used by the npm CLI when registry-url + # is set in setup-node — we don't use that pattern here). + # + # Future upgrade path — npm OIDC trusted publishing: + # Once the trusted publisher is configured on npmjs.com for this + # package (see docs/ci-cd-release-automation.md), NPM_TOKEN can + # be removed from this env block entirely. The id-token: write + # permission above will cause npm >=11.5.1 to authenticate via + # OIDC automatically, with no long-lived secret needed. + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/e2e/example.test.ts b/e2e/example.test.ts index 0ff6c20..495da49 100644 --- a/e2e/example.test.ts +++ b/e2e/example.test.ts @@ -4,7 +4,7 @@ import path from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; const root = process.cwd(); -const pluginWorkspace = 'vite-plugin-flatwave-react'; +const pluginWorkspace = '@kamansoft/vite-plugin-flatwave-react'; const exampleWorkspace = '@flatwave/example-basic-react-site'; const exampleDist = path.resolve(root, 'examples/basic-react-site/dist'); let serve: ChildProcessWithoutNullStreams | undefined; diff --git a/examples/basic-react-site/package.json b/examples/basic-react-site/package.json index 44bbc9d..ec9674b 100644 --- a/examples/basic-react-site/package.json +++ b/examples/basic-react-site/package.json @@ -13,7 +13,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-markdown": "^9.0.3", - "vite-plugin-flatwave-react": "file:../../packages/vite-plugin-flatwave-react" + "@kamansoft/vite-plugin-flatwave-react": "file:../../packages/vite-plugin-flatwave-react" }, "devDependencies": { "@types/react": "^18.3.12", diff --git a/examples/basic-react-site/vite.config.ts b/examples/basic-react-site/vite.config.ts index 67928e1..41d43cd 100644 --- a/examples/basic-react-site/vite.config.ts +++ b/examples/basic-react-site/vite.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'node:path'; -import { flatwaveContent } from 'vite-plugin-flatwave-react'; +import { flatwaveContent } from '@kamansoft/vite-plugin-flatwave-react'; export default defineConfig({ plugins: [ diff --git a/package-lock.json b/package-lock.json index e37adfc..dfe1f24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,11 +34,11 @@ "name": "@flatwave/example-basic-react-site", "version": "0.1.0", "dependencies": { + "@kamansoft/vite-plugin-flatwave-react": "file:../../packages/vite-plugin-flatwave-react", "@vitejs/plugin-react": "^4.3.4", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-markdown": "^9.0.3", - "vite-plugin-flatwave-react": "file:../../packages/vite-plugin-flatwave-react" + "react-markdown": "^9.0.3" }, "devDependencies": { "@types/react": "^18.3.12", @@ -1806,6 +1806,10 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kamansoft/vite-plugin-flatwave-react": { + "resolved": "packages/vite-plugin-flatwave-react", + "link": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -14573,10 +14577,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite-plugin-flatwave-react": { - "resolved": "packages/vite-plugin-flatwave-react", - "link": true - }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -15368,6 +15368,7 @@ } }, "packages/vite-plugin-flatwave-react": { + "name": "@kamansoft/vite-plugin-flatwave-react", "version": "0.1.0", "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 657e8d6..bab5157 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "examples/*" ], "scripts": { - "build:plugin": "npm run build -w vite-plugin-flatwave-react", + "build:plugin": "npm run build -w @kamansoft/vite-plugin-flatwave-react", "build:example": "npm run build -w @flatwave/example-basic-react-site", "build": "npm run build:plugin && npm run build:example", "validate:example": "node packages/vite-plugin-flatwave-react/dist/cli/validate.js --content-dir examples/basic-react-site/src/content --locales es,pt --default-locale es --components-dir examples/basic-react-site/src/components", diff --git a/packages/vite-plugin-flatwave-react/package.json b/packages/vite-plugin-flatwave-react/package.json index a6de8ec..f44bad1 100644 --- a/packages/vite-plugin-flatwave-react/package.json +++ b/packages/vite-plugin-flatwave-react/package.json @@ -1,5 +1,5 @@ { - "name": "vite-plugin-flatwave-react", + "name": "@kamansoft/vite-plugin-flatwave-react", "version": "0.1.0", "description": "Vite content plugin for Markdown-driven, i18n-aware static React sites.", "type": "module", From f3a3b5839636ec7c860656ec269ae290bdac8788 Mon Sep 17 00:00:00 2001 From: lemys lopez Date: Fri, 19 Jun 2026 08:31:10 -0500 Subject: [PATCH 3/4] fix(ci): format release.yml to satisfy prettier check --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f669488..d69853f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,16 +47,16 @@ jobs: needs: [ci] # only runs after CI passes runs-on: ubuntu-latest permissions: - contents: write # create the GitHub Release and git tag - id-token: write # npm OIDC trusted publishing + package provenance - issues: write # let semantic-release comment on released issues + contents: write # create the GitHub Release and git tag + id-token: write # npm OIDC trusted publishing + package provenance + issues: write # let semantic-release comment on released issues pull-requests: write # let semantic-release comment on released PRs steps: - name: Checkout uses: actions/checkout@v4 with: - fetch-depth: 0 # full history + all tags — required by semantic-release + fetch-depth: 0 # full history + all tags — required by semantic-release - name: Set up Node.js uses: actions/setup-node@v4 From ccf89009619e3510a7a1edf2644c3ff8f0f6d477 Mon Sep 17 00:00:00 2001 From: lemys lopez Date: Fri, 19 Jun 2026 08:32:54 -0500 Subject: [PATCH 4/4] docs: add CI/CD release automation guide --- docs/ci-cd-release-automation.md | 378 +++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 docs/ci-cd-release-automation.md diff --git a/docs/ci-cd-release-automation.md b/docs/ci-cd-release-automation.md new file mode 100644 index 0000000..77fffcb --- /dev/null +++ b/docs/ci-cd-release-automation.md @@ -0,0 +1,378 @@ +# CI/CD & Release Automation + +## Overview + +This document describes the complete release automation pipeline for +`@kamansoft/vite-plugin-flatwave-react`, including every change made, the +reason for each decision, and the required npmjs.com configuration for the +optional (but recommended) OIDC trusted publishing upgrade. + +--- + +## Developer workflow + +``` +feature branch → PR (title validated + CI) → merge to main → automatic release & npm publish +``` + +1. Create a feature branch (e.g. `feat/my-feature`). +2. Open a PR targeting `main`. + - **PR Title Validation** checks the title matches Conventional Commits format. + - **CI Validation** runs the full suite: format, lint, type-check, build, test. +3. Branch protection requires both checks to pass before merge is allowed. +4. On merge, **Release** workflow fires automatically: + - Re-runs CI as a hard gate. + - Runs `semantic-release`, which analyses commit messages, bumps the version, + publishes `@kamansoft/vite-plugin-flatwave-react` to npmjs.com, and creates + a GitHub Release with a changelog. + +### Conventional Commits — version bump rules + +| Commit prefix | Version bump | +| ------------------------------------------- | ----------------------- | +| `fix:` | patch — `0.1.0 → 0.1.1` | +| `feat:` | minor — `0.1.0 → 0.2.0` | +| `feat!:` or `BREAKING CHANGE:` footer | major — `0.1.0 → 1.0.0` | +| `chore:`, `docs:`, `style:`, `test:`, `ci:` | no release triggered | + +--- + +## Changes made + +### 1. Package renamed to `@kamansoft/vite-plugin-flatwave-react` + +**Why:** npm organisations own _scoped_ packages (`@org/package-name`). An +unscoped package cannot be "owned" by an organisation — it must be scoped. + +**Files changed:** + +| File | Change | +| -------------------------------------------------- | --------------------------------------------------------------------------------- | +| `packages/vite-plugin-flatwave-react/package.json` | `name` → `@kamansoft/vite-plugin-flatwave-react` | +| `package.json` (root) | `build:plugin` script workspace flag → `-w @kamansoft/vite-plugin-flatwave-react` | +| `examples/basic-react-site/package.json` | dependency key renamed to match new package name | +| `examples/basic-react-site/vite.config.ts` | import updated to `from '@kamansoft/vite-plugin-flatwave-react'` | +| `e2e/example.test.ts` | `pluginWorkspace` constant updated to `@kamansoft/vite-plugin-flatwave-react` | +| `package-lock.json` | regenerated after `npm install` | + +**First publish — command executed:** + +```bash +# One-time manual publish (package did not exist yet; OIDC requires existing package) +# Authenticated as lemyskaman (owner of kamansoft npm org) +npm publish --access public \ + --userconfig /tmp/.npmrc-publish # temp file with automation token +# Result: + @kamansoft/vite-plugin-flatwave-react@0.1.0 +``` + +Published: https://www.npmjs.com/package/@kamansoft/vite-plugin-flatwave-react + +--- + +### 2. `release.yml` — root-cause fixes + +**Previous state:** the workflow had `NPM_TOKEN` set in GitHub Secrets (since +2026-06-17) but **never passed it to the `semantic-release` step** — it was +commented out under an incorrect assumption about OIDC working without any +configuration. Every release run ended with `403 Forbidden`. + +#### Fix A — pass `NPM_TOKEN` to `@semantic-release/npm` + +```yaml +# Before (broken) +- name: Release + run: npx semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_CONFIG_REGISTRY: https://registry.npmjs.org # ← had no effect + +# After (fixed) +- name: Release + run: npx semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # ← @semantic-release/npm reads this directly +``` + +`@semantic-release/npm` reads `NPM_TOKEN` to build a temporary `.npmrc` for +the publish step. It does **not** use `NODE_AUTH_TOKEN` (that is a convention +specific to `setup-node` + `registry-url`, which we no longer use — see Fix B). + +#### Fix B — remove `registry-url` from `setup-node` + +```yaml +# Before (caused auth conflicts) +- uses: actions/setup-node@v4 + with: + node-version: '22' + registry-url: 'https://registry.npmjs.org' # ← creates .npmrc with ${NODE_AUTH_TOKEN} + +# After (correct for semantic-release) +- uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + # registry-url omitted intentionally +``` + +When `registry-url` is present, `setup-node` writes an `.npmrc` containing +`//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}`. If `NODE_AUTH_TOKEN` +is unset (or set to an empty string), npm uses an empty auth token and fails +with `EINVALIDNPMTOKEN`. This file also interferes with `@semantic-release/npm`'s +own `.npmrc` management. The semantic-release documentation explicitly warns +against using `registry-url` in this context. + +Reference: https://semantic-release.gitbook.io/semantic-release/recipes/ci-configurations/github-actions + +#### Fix C — CI prerequisite job (`needs: [ci]`) + +```yaml +# Before: release job ran immediately in parallel with nothing +jobs: + release: + runs-on: ubuntu-latest + ... + +# After: release only runs if CI passes first +jobs: + ci: + name: CI Validation + runs-on: ubuntu-latest + steps: [checkout, install, validate] + + release: + name: Release & Publish to npm + needs: [ci] # ← hard dependency + ... +``` + +Without this, a broken commit merged to `main` could publish a broken package +before CI had a chance to catch the failure. + +#### Fix D — concurrency guard + +```yaml +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false +``` + +Prevents two simultaneous pushes to `main` (e.g. two PRs merged in quick +succession) from triggering overlapping releases. `cancel-in-progress: false` +ensures a running release is never interrupted mid-publish. + +#### Fix E — upgrade npm before releasing + +```yaml +- name: Upgrade npm to latest + run: npm install -g npm@latest +``` + +Node 22 ships with npm 10.x. npm ≥ 11.5.1 is required for OIDC trusted +publishing (see section below). Upgrading in CI guarantees future OIDC +readiness without changing the Node version requirement. + +--- + +### 3. `ci.yml` — remove redundant push-to-main trigger + +```yaml +# Before +on: + pull_request: + branches: [main] + push: + branches: [main] # ← ran CI twice on every merge + +# After +on: + pull_request: + branches: [main] +``` + +`release.yml` now runs its own CI job before releasing, so having `ci.yml` +also run on every push to `main` duplicated effort. The PR-time check (before +merge) is still the primary quality gate, enforced by branch protection. + +--- + +### 4. Branch protection rules (configured via `gh` CLI) + +```bash +gh api repos/kamansoft/vite-plugin-flatwave-react/branches/main/protection \ + --method PUT \ + --input - <<'EOF' +{ + "required_status_checks": { + "strict": true, + "contexts": ["validate", "test-matrix (22)", "test-matrix (24)", "validate-pr-title"] + }, + "enforce_admins": false, + "required_pull_request_reviews": { + "required_approving_review_count": 0, + "dismiss_stale_reviews": true + }, + "restrictions": null, + "allow_force_pushes": false, + "allow_deletions": false +} +EOF +``` + +| Rule | Effect | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `required_status_checks` | `validate`, `test-matrix (22)`, `test-matrix (24)` from `ci.yml` + `validate-pr-title` from `pr-title.yml` must all pass | +| `strict: true` | Branch must be up-to-date with `main` before merge | +| `required_pull_request_reviews` | PRs are required (0 approvals needed — increase as team grows) | +| `dismiss_stale_reviews` | New commits reset approval | +| `allow_force_pushes: false` | History cannot be rewritten | +| `allow_deletions: false` | `main` cannot be deleted | + +--- + +## GitHub Secrets required + +| Secret | Purpose | +| -------------- | ------------------------------------------------------------------------ | +| `NPM_TOKEN` | Automation token used by `@semantic-release/npm` to publish to npmjs.com | +| `GITHUB_TOKEN` | Auto-injected by GitHub Actions — no setup needed | + +The `NPM_TOKEN` secret was already present in the repository (created 2026-06-17). +It must be a **Granular Access Token** or **Automation Token** (prefix `npm_`). +Classic tokens (UUID format, e.g. `abcd1234-...`) were deprecated by npm on +2025-12-09 and will not work. + +### Verifying / recreating the npm token + +1. Go to https://www.npmjs.com/settings/~/tokens +2. Click **Generate New Token → Granular Access Token** +3. Set: + - **Expiration**: No expiration (or 1 year) + - **Packages and scopes**: `@kamansoft` → Read and write + - **Organizations**: `kamansoft` → Read and write +4. Copy the generated token +5. In GitHub: **Settings → Secrets and variables → Actions → New repository secret** + - Name: `NPM_TOKEN` + - Value: paste the token + +--- + +## Optional upgrade: npm OIDC trusted publishing (recommended) + +OIDC trusted publishing replaces the long-lived `NPM_TOKEN` with a short-lived +cryptographic identity issued by GitHub Actions at publish time. No static +secret is stored anywhere. + +**Prerequisite:** the package must already exist on npmjs.com ✓ (done above). + +### Step 1 — Configure on npmjs.com (browser) + +1. Go to: https://www.npmjs.com/package/@kamansoft/vite-plugin-flatwave-react/access +2. Scroll to **Trusted Publishers** section. +3. Click **Add a trusted publisher**. +4. Select **GitHub Actions**. +5. Fill in the form exactly as follows: + + | Field | Value | + | --------------------- | ---------------------------- | + | **Owner** | `kamansoft` | + | **Repository** | `vite-plugin-flatwave-react` | + | **Workflow filename** | `release.yml` | + | **Environment** | _(leave empty)_ | + +6. Check **Allow publish**. +7. Click **Save**. + +> All fields are case-sensitive and must match exactly. +> The workflow filename must not include the `.github/workflows/` path prefix. + +### Step 2 — Update `release.yml` to use OIDC (remove NPM_TOKEN) + +Once the trusted publisher is saved on npmjs.com, update the Release step: + +```yaml +# Remove NPM_TOKEN from the env block: +- name: Release + run: npx semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # NPM_TOKEN removed — OIDC handles auth via id-token: write permission +``` + +The `npm install -g npm@latest` step (already present) ensures npm ≥ 11.5.1, +which detects the GitHub Actions OIDC environment and exchanges the short-lived +token automatically. + +### Step 3 — Delete the NPM_TOKEN secret (optional) + +After confirming a successful OIDC-based release, you can remove the +`NPM_TOKEN` secret from GitHub: +**Settings → Secrets and variables → Actions → NPM_TOKEN → Delete**. + +--- + +## Workflow diagram + +``` +PR opened / pushed + │ + ├── PR Title Validation (pr-title.yml) + │ validates: Conventional Commits format (feat/fix/chore/...) + │ + └── CI Validation (ci.yml) + validates: format-check, lint, type-check, build, test + matrix: Node 22, Node 24 + +PR merge to main (blocked until both checks pass) + │ + └── Release (release.yml) + │ + ├── CI Validation job (re-run as hard gate) + │ same validation suite as PR + │ + └── Release & Publish job (needs: ci) + ├── Upgrade npm to latest + ├── Build plugin + └── npx semantic-release + ├── Analyses commits since last release + ├── Determines next version (semver) + ├── Updates package.json version + ├── Publishes @kamansoft/vite-plugin-flatwave-react to npmjs.com + ├── Creates git tag (e.g. v0.2.0) + └── Creates GitHub Release with changelog +``` + +--- + +## Troubleshooting + +### Release workflow fails with `403 Forbidden` on npm publish + +- The `NPM_TOKEN` secret is missing, expired, or a deprecated Classic Token. +- Recreate a Granular Access Token (see **GitHub Secrets** section above) and + update the `NPM_TOKEN` secret in GitHub. + +### Release workflow fails with `EINVALIDNPMTOKEN` + +- The `registry-url` option is set in the `setup-node` step. +- Remove `registry-url` from `setup-node` in `release.yml`. The + `@semantic-release/npm` plugin manages its own `.npmrc` — `registry-url` + creates a conflicting file. + +### semantic-release finds no commits to release + +- Only commits with `feat:`, `fix:`, or `BREAKING CHANGE:` trigger a release. +- `chore:`, `docs:`, `ci:`, `style:` commits merge cleanly but produce no new version. + +### PR is blocked — required checks not appearing + +- Required checks only appear after the first run of the workflow on that branch. +- Push a commit to the PR branch to trigger CI and populate the status checks. + +### OIDC publish fails with `Unable to authenticate` (`ENEEDAUTH`) + +- The workflow filename in the npmjs.com trusted publisher config must match + **exactly** (case-sensitive, including `.yml` extension, no path prefix). +- The `id-token: write` permission must be present in the `release` job. +- Only GitHub-hosted runners are supported for OIDC trusted publishing. + Self-hosted runners will not work.