* test(#431): policy-shell-pinning linter — RED baseline (37 violations on origin/next) Adds scripts/workflow-policy.cjs: H1 shell-policy linter with POLICY map, VIOLATION enum, matrix expansion, effective-shell resolution order, and runPolicyLint({ workflowsDir }) entry point. Adds tests/policy-shell-pinning.test.cjs: 8 tests (baseline + 6 synthetic counter-tests). Synthetic tests 2–7 pass; baseline test is intentionally RED (37 violations: 28 in test.yml, 9 in install-smoke.yml — all macos/windows lanes using shell: bash instead of native zsh/pwsh). Adds js-yaml@4.1.1 as devDependency for YAML parsing. * fix(#431): switch ubuntu/windows lanes to native shells; extract bash-isms to Node Remove all explicit shell: bash pins from ubuntu-only jobs (changes, lint-tests, coverage, required-tests, smoke-unpacked) — ubuntu runner default is bash, which is both H1-compliant and the runner default, making the pin redundant. For the test and test-full mixed-OS jobs (ubuntu+windows, windows+macos): - Move bash-ism steps to shell-agnostic Node scripts: scripts/ci-guard-runner.cjs — RUNNER_ENVIRONMENT check scripts/ci-rebase-check.cjs — git fetch+merge PR base branch scripts/check-npm-integrity.cjs — Node port of check-npm-integrity.sh scripts/ci-prepare-test-scope.cjs — write .ci-selected-tests.txt scripts/ci-smoke-skip.cjs — set skip= output for full-only matrix entries - Remove shell: bash from simple npm/node command steps (runner default applies) This brings Windows violations from 19 to 0. Remaining 17 violations are all MACOS_MISSING_EXPLICIT_ZSH in mixed-OS matrix jobs (test-full: windows+macos, install-smoke smoke: ubuntu+macos) — these require job splitting to fix; see BLOCKER in PR description. * fix(#431): update workflow-shell-pinning test for H1 policy The old test required all Windows-targeting npm steps to pin shell: bash (to prevent pwsh stderr-swallow). Under H1, Windows runners must use pwsh (native, no pin needed) — shell: bash on Windows is now the violation, not the fix. Update findViolations() to flag npm steps with effectiveShell === 'bash' (rather than effectiveShell === null). Update synthetic tests to verify the H1-inverted semantics: defaults.run.shell: bash on Windows is now 2 violations, not 0. Update test name and assertion messages to describe the H1 constraint rather than the old missing-pin constraint. * fix(#431): extend policy linter to resolve matrix.shell expressions - expandRunsOn now captures all matrix.include row keys as realization context (os, node-version, shell, full_only, etc.) instead of only os - effectiveShell now accepts a realizationContext and resolves ${{ matrix.<key> }} expressions against it before checking policy - Unresolvable matrix key in shell expression emits UNRESOLVABLE_MATRIX - Add 3 new tests: positive (zsh+pwsh per row → 0 violations), counter (bash in macOS row → WRONG_SHELL_FOR_OS), counter (missing shell key → UNRESOLVABLE_MATRIX) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#431): apply matrix.shell pattern to test-full and smoke jobs (clears BLOCKER) test-full job (test.yml): - Add shell: pwsh/zsh per matrix.include row (windows-latest→pwsh, macos-latest→zsh) - Add job-level defaults.run.shell: ${{ matrix.shell }} - No step-level shell pins existed to remove smoke job (install-smoke.yml): - Add shell: bash/zsh per matrix.include row (ubuntu→bash, macos→zsh) - Add job-level defaults.run.shell: ${{ matrix.shell }} - No step-level shell pins existed to remove Policy linter now reports 0 violations across all workflow files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(#431): migrate .sh check scripts to .cjs; remove .sh originals - Add scripts/check-env.cjs: Node.js port of check-env.sh with identical exit codes (0/1/2), human-readable and --json output, --help flag, and all 5 checks (node-version, npm-version, lockfile-present, lockfile-sync, version-manager-pin) - Migrate all callers: - package.json check:env → node scripts/check-env.cjs - package.json check:integrity → node scripts/check-npm-integrity.cjs - scripts/ci-test-scope.cjs path strings → .cjs equivalents - .github/workflows/release.yml rc+finalize jobs → node .cjs (drop chmod+x) - .github/workflows/security-scan.yml → node .cjs (drop chmod+x) - tests/check-env.test.cjs → spawn node process.execPath [.cjs] - tests/npm-integrity-gate.test.cjs → spawn node process.execPath [.cjs] - Delete scripts/check-env.sh and scripts/check-npm-integrity.sh Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor(#431): update doc references from .sh to .cjs Update SECURITY.md and docs/contributing/bootstrap.md to reference the canonical Node invocation instead of the removed bash scripts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#431): use per-step shell:matrix.shell instead of defaults.run.shell (GHA compat) GHA does not reliably resolve matrix expressions inside defaults.run.shell. Per-step shell: always resolves correctly. Removed the defaults.run.shell block from the test-full job (test.yml) and the smoke job (install-smoke.yml), and added shell: \${{ matrix.shell }} directly on every run: step in both jobs. Codex finding: defaults.run.shell with matrix expressions is not a GHA-supported pattern; per-step shell: is the safe form. * fix(#431): policy linter validates every matrix.include row independently Removed runner-label-only dedup from expandRunsOn() in workflow-policy.cjs. The prior guard (if !realizations.find(r => r.runner === runner)) collapsed two macos-latest rows with different node-version/shell contexts into one, hiding the second row's policy violation. Each matrix.include row is a distinct CI realization with its own context; validating it twice is harmless but skipping it causes false negatives. Added counter-test (Test 8) in tests/policy-shell-pinning.test.cjs: two macos-latest rows (shell:zsh compliant + shell:bash violation) must produce exactly one WRONG_SHELL_FOR_OS violation on the second row. * fix(#431): remove dedup-by-runner in Cartesian matrix.<key> expansion (Codex round 3) The base-list path in expandRunsOn (matrix.<key> arrays, e.g. matrix.os) previously guarded each push with `if (!realizations.find(r => r.runner === runner))`, collapsing duplicate runner values into a single realization and hiding policy violations on later rows of a Cartesian matrix. Remove the guard unconditionally; each entry in the base-list array now produces its own realization, matching the same fix already applied to the matrix.include path. Add counter-test "Cartesian matrix os × shell — dedup must not collapse rows by runner alone": matrix.os: [macos-latest, macos-latest] + shell: ${{ matrix.shell }} now yields 2 realizations (not 1). Documents that Cartesian cross-product expansion (carrying all keys into realization context) is a separate follow-up; current violations are UNRESOLVABLE_MATRIX pending that work. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#431): remove 60s timeout regression on npm ci --dry-run (parity with check-env.sh) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#431): ci-rebase-check.cjs — return truthy sentinel on success (Codex round 4) run() used execFileSync with stdio:'inherit', which returns null on success. Caller checked `result !== null`, always false → every successful fetch fell through to "failed after 3 attempts" exit-1 path. Fix: run() now returns true on success, false on failure. Update caller from `result !== null` to `if (result)`. Adds tests/ci-rebase-check.test.cjs (5 tests) covering the sentinel contract and a local-bare-remote integration smoke that verifies the full fetch+merge path exits 0 when fetch succeeds. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: CI Rebase Check <ci@gsd-redux>
227 lines
6.4 KiB
Markdown
227 lines
6.4 KiB
Markdown
# Bootstrap your environment
|
|
|
|
This guide gets a new contributor from a fresh checkout to a passing baseline in one session.
|
|
|
|
Sources:
|
|
- npm engines field: https://docs.npmjs.com/cli/v10/configuring-npm/package-json#engines
|
|
- Reproducible builds: https://reproducible-builds.org/docs/source-tree/
|
|
- npm ci: https://docs.npmjs.com/cli/v10/commands/npm-ci
|
|
- Docker-based Linux verification: https://github.com/open-gsd/gsd-test-runner
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
### Node version manager (pick one)
|
|
|
|
| Tool | Install | Docs |
|
|
|---|---|---|
|
|
| **nvm** (recommended) | `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/HEAD/install.sh \| bash` | https://github.com/nvm-sh/nvm |
|
|
| **fnm** (fast, Rust) | `curl -fsSL https://fnm.vercel.app/install \| bash` | https://github.com/Schniz/fnm |
|
|
| **asdf** | `brew install asdf` then `asdf plugin add nodejs` | https://asdf-vm.com |
|
|
| **mise** | `curl https://mise.run \| sh` | https://mise.jdx.dev |
|
|
|
|
A version manager ensures you can switch Node versions per project without polluting your
|
|
global install. This project ships a `.nvmrc` file at the root — any of the tools above
|
|
will read it.
|
|
|
|
### Other required tools
|
|
|
|
- **gh** (GitHub CLI) — https://cli.github.com — used by contribution workflows and CI
|
|
- **git** — any recent version
|
|
|
|
---
|
|
|
|
## One-time setup
|
|
|
|
```bash
|
|
# 1. Clone
|
|
git clone https://github.com/open-gsd/get-shit-done-redux.git
|
|
cd get-shit-done-redux
|
|
|
|
# 2. Activate the pinned Node version
|
|
nvm use # nvm
|
|
# fnm use # fnm
|
|
# asdf install # asdf / mise
|
|
|
|
# 3. Verify the environment (see Validation below)
|
|
npm run check:env
|
|
|
|
# 4. Install dependencies (reproducible, lockfile-driven)
|
|
npm ci
|
|
```
|
|
|
|
`npm ci` is required over `npm install`. It installs exactly what `package-lock.json`
|
|
specifies and fails fast if the lockfile is out of sync — this is intentional.
|
|
See https://docs.npmjs.com/cli/v10/commands/npm-ci
|
|
|
|
---
|
|
|
|
## Daily commands
|
|
|
|
| Command | Purpose |
|
|
|---|---|
|
|
| `npm run check:env` | Validate your environment before running tests |
|
|
| `npm test` | Run the full test suite (unit + integration + security) |
|
|
| `npm run test:unit` | Unit tests only (fastest) |
|
|
| `npm run test:integration` | Integration tests |
|
|
| `npm run build:sdk` | Rebuild the SDK dist (required before first test run) |
|
|
|
|
> `npm run check:integrity` — available once [#114](https://github.com/open-gsd/get-shit-done-redux/issues/114) merges.
|
|
|
|
---
|
|
|
|
## Validation
|
|
|
|
Run the environment validator before any test or audit run:
|
|
|
|
```bash
|
|
npm run check:env
|
|
```
|
|
|
|
This runs `scripts/check-env.cjs` and reports pass/fail for each check:
|
|
|
|
| Check | What it verifies |
|
|
|---|---|
|
|
| `node-version` | Active Node satisfies `engines.node` (`>=22.0.0`) |
|
|
| `npm-version` | Active npm satisfies `engines.npm` (`>=10.0.0`) |
|
|
| `lockfile-present` | `package-lock.json` exists at root |
|
|
| `lockfile-sync` | `npm ci --dry-run` exits 0 (lockfile matches installed state) |
|
|
| `version-manager-pin` | Active Node major matches `.nvmrc` / `.node-version` / `.tool-versions` |
|
|
|
|
**Exit codes:**
|
|
- `0` — all checks passed, safe to proceed
|
|
- `1` — one or more checks failed — see the report
|
|
- `2` — tool error (e.g., `node` not found, corrupt `package.json`)
|
|
|
|
For structured output (useful in scripts):
|
|
|
|
```bash
|
|
npm run check:env -- --json
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### `node-version` FAIL — Node X does NOT satisfy `>=22.0.0`
|
|
|
|
**Cause:** The system Node is too old, or the version manager hasn't activated the correct version.
|
|
|
|
**Fix:**
|
|
```bash
|
|
nvm use # activates version from .nvmrc
|
|
node --version # confirm
|
|
```
|
|
|
|
If `nvm` reports the version is not installed:
|
|
```bash
|
|
nvm install # installs the version in .nvmrc
|
|
nvm use
|
|
```
|
|
|
|
---
|
|
|
|
### `npm-version` FAIL — npm X does NOT satisfy `>=10.0.0`
|
|
|
|
**Cause:** npm bundled with an old Node version.
|
|
|
|
**Fix:**
|
|
```bash
|
|
npm install -g npm@latest
|
|
npm --version
|
|
```
|
|
|
|
---
|
|
|
|
### `lockfile-present` FAIL — `package-lock.json` missing
|
|
|
|
**Cause:** The lockfile was deleted or was never generated.
|
|
|
|
**Fix:**
|
|
```bash
|
|
npm install # generates package-lock.json
|
|
```
|
|
|
|
Do NOT commit a regenerated lockfile without verifying no unexpected packages changed.
|
|
Run `git diff package-lock.json` to inspect the diff.
|
|
|
|
---
|
|
|
|
### `lockfile-sync` FAIL — `package-lock.json` is out of sync
|
|
|
|
**Cause:** `package.json` was edited (dependency added/changed) without updating the lockfile,
|
|
or the lockfile was hand-edited.
|
|
|
|
**Fix:**
|
|
```bash
|
|
npm ci # restores node_modules to match lockfile exactly
|
|
# or, if the sync failure is intentional (you updated package.json):
|
|
npm install # updates lockfile to match package.json
|
|
```
|
|
|
|
---
|
|
|
|
### `version-manager-pin` FAIL — Active Node major does not match `.nvmrc`
|
|
|
|
**Cause:** The shell is using a globally-installed Node rather than the version manager's
|
|
activation. Common on fresh shell sessions.
|
|
|
|
**Fix:**
|
|
```bash
|
|
nvm use # re-activate from .nvmrc
|
|
# or add to your shell profile:
|
|
# echo 'nvm use --silent' >> ~/.zshrc
|
|
```
|
|
|
|
---
|
|
|
|
### Tests fail with `Error: Cannot find module ...`
|
|
|
|
**Cause:** `node_modules` is missing or stale (common after a branch switch that changed `package.json`).
|
|
|
|
**Fix:**
|
|
```bash
|
|
npm ci # clean install from lockfile
|
|
npm run build:sdk
|
|
```
|
|
|
|
---
|
|
|
|
### Locale / encoding errors on non-UTF-8 systems
|
|
|
|
**Cause:** Some test fixtures contain non-ASCII characters. Node requires a UTF-8 locale.
|
|
|
|
**Fix:**
|
|
```bash
|
|
export LANG=en_US.UTF-8
|
|
export LC_ALL=en_US.UTF-8
|
|
```
|
|
|
|
Add these to your shell profile to make them permanent.
|
|
|
|
---
|
|
|
|
## Alternative: Docker via gsd-test-runner
|
|
|
|
For canonical Linux verification from a macOS dev box, use
|
|
[gsd-test-runner](https://github.com/open-gsd/gsd-test-runner).
|
|
|
|
This is the same path CI uses for cross-platform coverage. It is the authoritative way
|
|
to confirm your change passes on Linux before opening a PR:
|
|
|
|
```bash
|
|
# One-time: install gsd-test-runner (see repo README)
|
|
# Then, from the project root:
|
|
gsd-test-summary
|
|
```
|
|
|
|
`gsd-test-summary` runs the full suite in a Docker container and emits a concise
|
|
`Mac: N failed / Docker: N failed` summary.
|
|
|
|
- **Default rule (code changes):** both lines must show `0 failed` before a PR is opened.
|
|
- **Exception (ADR/doc-only PRs):** if the diff is documentation-only (for example `docs/adr/*.md`, `docs/**/*.md`, `README*.md`) and contains no executable-code or test changes, `gsd-test-summary` is optional.
|
|
|
|
When using the doc-only exception, note it explicitly in the PR body (for example:
|
|
"Doc-only PR; gsd-test-summary not required by docs-only exception in `docs/contributing/bootstrap.md`").
|