diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27065f6..3870f7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,23 @@ jobs: - name: Check out repository uses: actions/checkout@v6 + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.13.1 + + - name: Set up Node + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run package verification + run: pnpm run release:check + - name: Validate required top-level files shell: bash run: | diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 311edb2..26558c6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -30,7 +30,7 @@ jobs: - name: Set up pnpm uses: pnpm/action-setup@v4 with: - version: 9 + version: 10.13.1 - name: Set up Node uses: actions/setup-node@v6 diff --git a/.github/workflows/release-dry-run.yml b/.github/workflows/release-dry-run.yml index c0e211d..9f86604 100644 --- a/.github/workflows/release-dry-run.yml +++ b/.github/workflows/release-dry-run.yml @@ -28,7 +28,7 @@ jobs: node-version: 22 - uses: pnpm/action-setup@v4 with: - version: 10 + version: 10.13.1 run_install: false - name: Install dependencies run: pnpm install --frozen-lockfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e883a5..a3305f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: registry-url: https://registry.npmjs.org - uses: pnpm/action-setup@v4 with: - version: 10 + version: 10.13.1 run_install: false - name: Install dependencies run: pnpm install --frozen-lockfile diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d4fbd08..0e9831b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,6 +71,15 @@ Keep the review pack factual. It should say what changed, how it was verified, w Every contribution needs verification appropriate to the change. +Use Node.js 22 and the exact pnpm version declared by `packageManager` in +`package.json`. Enable Corepack, then install dependencies non-interactively: + +```sh +corepack enable +CI=true corepack pnpm install --frozen-lockfile +pnpm run release:check +``` + Use the smallest relevant verification first: - Documentation-only changes: review the rendered Markdown or inspect the diff. diff --git a/docs/release-process.md b/docs/release-process.md index dd5d730..436f453 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -69,6 +69,11 @@ uses generated release notes and maintainers have reviewed them. Choose the smallest verification that gives confidence for the release: +- Use Node.js 22 and the exact pnpm version in `package.json#packageManager`; + Corepack and CI workflows use this pin rather than a floating pnpm major. +- Run `CI=true corepack pnpm install --frozen-lockfile` from a clean checkout. +- Run `pnpm run release:check`, which also verifies the pnpm pin, required + esbuild build-script policy, and workflow version alignment. - Documentation-only release: markdown review, link check if available, and spell or formatting checks when configured. - Library or package release: targeted tests, typecheck, lint, build, and diff --git a/package.json b/package.json index 2dbb7dc..72a0f38 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "@rogerchappel/stackforge", "version": "0.1.0", "private": true, + "packageManager": "pnpm@10.13.1", "description": "Agent-friendly software project scaffolding CLI for apps, APIs, CLIs, and OSS packages.", "type": "module", "bin": { @@ -12,11 +13,12 @@ "build": "tsc", "dev": "tsx src/index.ts", "check": "tsc --noEmit", + "package-manager:check": "node scripts/check-package-manager.mjs", "docs:check": "node scripts/check-docs.mjs", "start": "node dist/index.js", "check:templates": "pnpm build && node scripts/check-template-registry.mjs", "smoke:init": "bash scripts/smoke-init.sh", - "release:check": "pnpm run docs:check && pnpm run build && pnpm test && pnpm run smoke && pnpm run package:smoke", + "release:check": "pnpm run package-manager:check && pnpm run docs:check && pnpm run build && pnpm test && pnpm run smoke && pnpm run package:smoke", "test": "pnpm run build && node scripts/check-template-registry.mjs", "smoke": "pnpm run smoke:init", "package:smoke": "pnpm run build && node scripts/package-smoke.mjs" @@ -32,6 +34,11 @@ "engines": { "node": ">=20" }, + "pnpm": { + "onlyBuiltDependencies": [ + "esbuild" + ] + }, "repository": { "type": "git", "url": "git+https://github.com/rogerchappel/stackforge.git" diff --git a/scripts/check-package-manager.mjs b/scripts/check-package-manager.mjs new file mode 100644 index 0000000..3f67a2b --- /dev/null +++ b/scripts/check-package-manager.mjs @@ -0,0 +1,45 @@ +import { readFileSync, readdirSync } from 'node:fs'; +import { join } from 'node:path'; + +const root = process.cwd(); +const packageJson = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')); +const match = /^pnpm@(\d+\.\d+\.\d+)$/.exec(packageJson.packageManager ?? ''); + +if (!match) { + throw new Error('package.json packageManager must pin an exact pnpm version'); +} + +if (!packageJson.pnpm?.onlyBuiltDependencies?.includes('esbuild')) { + throw new Error('package.json must allow the required esbuild install script'); +} + +const expectedVersion = match[1]; +const workflowDir = join(root, '.github', 'workflows'); +const workflowFiles = readdirSync(workflowDir).filter((file) => file.endsWith('.yml')); +let setupCount = 0; + +for (const file of workflowFiles) { + const lines = readFileSync(join(workflowDir, file), 'utf8').split('\n'); + + for (let index = 0; index < lines.length; index += 1) { + if (!lines[index].includes('uses: pnpm/action-setup@')) { + continue; + } + + setupCount += 1; + const block = lines.slice(index + 1, index + 6).join('\n'); + const version = /^\s+version:\s*['"]?([^'"\s]+)['"]?\s*$/m.exec(block)?.[1]; + + if (version !== expectedVersion) { + throw new Error( + `${file} pnpm/action-setup version ${version ?? '(missing)'} must match packageManager ${expectedVersion}`, + ); + } + } +} + +if (setupCount === 0) { + throw new Error('Expected at least one pnpm/action-setup workflow'); +} + +console.log(`Validated pnpm ${expectedVersion}, esbuild build policy, and ${setupCount} workflow setup(s).`);