@opengsd/gsd-core 1.3.1 → 1.4.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +23 -0
- package/GEMINI.md +53 -0
- package/agents/gsd-advisor-researcher.md +1 -20
- package/agents/gsd-ai-researcher.md +2 -21
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-domain-researcher.md +2 -21
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-eval-planner.md +1 -1
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-framework-selector.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-pattern-mapper.md +1 -1
- package/agents/gsd-phase-researcher.md +92 -166
- package/agents/gsd-planner.md +9 -36
- package/agents/gsd-project-researcher.md +62 -141
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.md +3 -22
- package/agents/gsd-user-profiler.md +1 -1
- package/agents/gsd-verifier.md +8 -2
- package/bin/install.js +1977 -339
- package/commands/gsd/autonomous.md +2 -0
- package/commands/gsd/execute-phase.md +2 -0
- package/commands/gsd/graphify.md +11 -6
- package/commands/gsd/import.md +6 -2
- package/commands/gsd/plan-phase.md +4 -2
- package/commands/gsd/progress.md +1 -0
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/update.md +3 -2
- package/gemini-extension.json +6 -0
- package/gsd-core/bin/check-latest-version.cjs +61 -6
- package/gsd-core/bin/gsd-tools.cjs +238 -32
- package/gsd-core/bin/lib/check-command-router.cjs +1 -0
- package/gsd-core/bin/lib/cli-exit.cjs +42 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +1 -1
- package/gsd-core/bin/lib/commands.cjs +5 -4
- package/gsd-core/bin/lib/config.cjs +28 -4
- package/gsd-core/bin/lib/core.cjs +72 -28
- package/gsd-core/bin/lib/graphify.cjs +2 -2
- package/gsd-core/bin/lib/init-command-router.cjs +2 -2
- package/gsd-core/bin/lib/init.cjs +19 -3
- package/gsd-core/bin/lib/install-profiles.cjs +58 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +1 -1
- package/gsd-core/bin/lib/intel.cjs +3 -20
- package/gsd-core/bin/lib/package-legitimacy.cjs +368 -0
- package/gsd-core/bin/lib/phase.cjs +3 -3
- package/gsd-core/bin/lib/research-provider.cjs +137 -0
- package/gsd-core/bin/lib/research-store.cjs +167 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +4 -19
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +67 -9
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +56 -0
- package/gsd-core/bin/lib/runtime-homes.cjs +32 -11
- package/gsd-core/bin/lib/security.cjs +73 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +9 -0
- package/gsd-core/bin/lib/surface.cjs +54 -11
- package/gsd-core/bin/lib/validate.cjs +2 -2
- package/gsd-core/bin/lib/verification-command-router.cjs +31 -0
- package/gsd-core/bin/lib/verification.cjs +193 -0
- package/gsd-core/bin/lib/verify.cjs +2 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -1
- package/gsd-core/bin/lib/worktree-base-ref.cjs +325 -0
- package/gsd-core/bin/lib/worktree-safety.cjs +31 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +8 -11
- package/gsd-core/references/planner-load-graph-context.md +36 -0
- package/gsd-core/references/planning-config.md +3 -1
- package/gsd-core/references/research-documentation-lookup.md +29 -0
- package/gsd-core/references/research-philosophy.md +29 -0
- package/gsd-core/references/research-verification-protocol.md +27 -0
- package/gsd-core/workflows/execute-phase.md +19 -8
- package/gsd-core/workflows/help/modes/full.md +4 -3
- package/gsd-core/workflows/ingest-docs.md +3 -2
- package/gsd-core/workflows/plan-phase.md +14 -10
- package/gsd-core/workflows/plan-review-convergence.md +3 -3
- package/gsd-core/workflows/review.md +24 -7
- package/gsd-core/workflows/ship.md +5 -8
- package/gsd-core/workflows/spec-phase.md +2 -1
- package/gsd-core/workflows/update.md +34 -6
- package/hooks/dist/gsd-config-reload.js +133 -0
- package/hooks/dist/gsd-context-monitor.js +1 -1
- package/hooks/dist/gsd-cursor-post-tool.js +75 -0
- package/hooks/dist/gsd-cursor-session-start.js +52 -0
- package/hooks/dist/gsd-workflow-guard.js +1 -0
- package/hooks/dist/gsd-worktree-path-guard.js +1 -1
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-config-reload.js +133 -0
- package/hooks/gsd-context-monitor.js +1 -1
- package/hooks/gsd-cursor-post-tool.js +75 -0
- package/hooks/gsd-cursor-session-start.js +52 -0
- package/hooks/gsd-workflow-guard.js +1 -0
- package/hooks/gsd-worktree-path-guard.js +1 -1
- package/hooks/hooks.json +69 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +8 -1
- package/scripts/affected-tests-lib.cjs +3 -2
- package/scripts/build-hooks.js +7 -0
- package/scripts/changeset/cli.cjs +226 -28
- package/scripts/changeset/lint.cjs +5 -4
- package/scripts/changeset/new.cjs +4 -4
- package/scripts/check-alias-drift.cjs +77 -71
- package/scripts/check-env.cjs +185 -179
- package/scripts/check-npm-integrity.cjs +115 -109
- package/scripts/ci-guard-runner.cjs +11 -5
- package/scripts/ci-prepare-test-scope.cjs +27 -22
- package/scripts/ci-rebase-check.cjs +46 -45
- package/scripts/ci-test-scope.cjs +126 -22
- package/scripts/diff-touches-shipped-paths.cjs +52 -44
- package/scripts/gen-inventory-manifest.cjs +38 -32
- package/scripts/gen-research-agents.cjs +276 -0
- package/scripts/issue-dedupe.cjs +278 -0
- package/scripts/lib/cli-exit.cjs +56 -0
- package/scripts/lint-command-contract.cjs +28 -22
- package/scripts/lint-descriptions.cjs +32 -28
- package/scripts/lint-docs-required.cjs +4 -4
- package/scripts/lint-legacy-dir-name.cjs +56 -52
- package/scripts/lint-pr-check-project-dir.cjs +3 -1
- package/scripts/lint-shell-command-projection-drift.cjs +27 -22
- package/scripts/lint-skill-deps.cjs +31 -26
- package/scripts/lint-test-file-count.allowlist.json +2 -0
- package/scripts/lint-test-file-count.cjs +5 -4
- package/scripts/mutation-matrix.cjs +6 -3
- package/scripts/prompt-injection-scan.sh +1 -1
- package/scripts/release-notes/discord-release-summary.cjs +373 -0
- package/scripts/release-notes/format-github-release-notes.cjs +8 -3
- package/scripts/release-tarball-smoke.cjs +6 -4
- package/scripts/research-profiles.cjs +149 -0
- package/scripts/run-affected-tests.cjs +2 -1
- package/scripts/run-cross-platform-tests.cjs +11 -7
- package/scripts/run-tests.cjs +8 -7
- package/scripts/strip-prose-atrefs.cjs +1 -1
- package/scripts/sync-manifest-versions.cjs +119 -0
- package/scripts/sync-runtime-launcher.cjs +0 -3
- package/scripts/verify-npm-publish.cjs +14 -26
package/scripts/check-env.cjs
CHANGED
|
@@ -27,67 +27,18 @@
|
|
|
27
27
|
|
|
28
28
|
const fs = require('fs');
|
|
29
29
|
const path = require('path');
|
|
30
|
-
const {
|
|
30
|
+
const { spawnSync } = require('child_process');
|
|
31
|
+
|
|
32
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
31
33
|
|
|
32
34
|
// On Windows, npm ships as npm.cmd (a batch wrapper); spawnSync without
|
|
33
35
|
// shell:true requires the exact filename including extension.
|
|
34
36
|
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
35
37
|
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
// Argument parsing
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
let jsonMode = false;
|
|
40
|
-
|
|
41
|
-
for (const arg of process.argv.slice(2)) {
|
|
42
|
-
if (arg === '--json') {
|
|
43
|
-
jsonMode = true;
|
|
44
|
-
} else if (arg === '--help' || arg === '-h') {
|
|
45
|
-
process.stdout.write(
|
|
46
|
-
'scripts/check-env.cjs — Environment parity validator for contributors (issue #117).\n' +
|
|
47
|
-
'\n' +
|
|
48
|
-
'Checks that the developer\'s environment matches project requirements before\n' +
|
|
49
|
-
'running tests or audits. Designed to catch mismatches early rather than\n' +
|
|
50
|
-
'through cryptic test failures.\n' +
|
|
51
|
-
'\n' +
|
|
52
|
-
'Exit codes:\n' +
|
|
53
|
-
' 0 All checks passed\n' +
|
|
54
|
-
' 1 One or more checks failed\n' +
|
|
55
|
-
' 2 Tool error (missing required tool, corrupt package.json, etc.)\n' +
|
|
56
|
-
'\n' +
|
|
57
|
-
'Usage:\n' +
|
|
58
|
-
' node scripts/check-env.cjs # Human-readable report\n' +
|
|
59
|
-
' node scripts/check-env.cjs --json # Structured JSON report\n' +
|
|
60
|
-
' node scripts/check-env.cjs --help # This message\n'
|
|
61
|
-
);
|
|
62
|
-
process.exit(0);
|
|
63
|
-
} else {
|
|
64
|
-
process.stderr.write(`Unknown option: ${arg}\n`);
|
|
65
|
-
process.exit(2);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Locate the project root (directory containing package.json)
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
const PROJECT_ROOT = process.cwd();
|
|
73
|
-
const PACKAGE_JSON = path.join(PROJECT_ROOT, 'package.json');
|
|
74
|
-
|
|
75
|
-
if (!fs.existsSync(PACKAGE_JSON)) {
|
|
76
|
-
process.stderr.write(`ERROR: package.json not found in ${PROJECT_ROOT}\n`);
|
|
77
|
-
process.exit(2);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
38
|
// ---------------------------------------------------------------------------
|
|
81
39
|
// Helpers
|
|
82
40
|
// ---------------------------------------------------------------------------
|
|
83
41
|
|
|
84
|
-
/** @type {Array<{name: string, status: 'pass'|'fail'|'skip', message: string}>} */
|
|
85
|
-
const checks = [];
|
|
86
|
-
|
|
87
|
-
function addCheck(name, status, message) {
|
|
88
|
-
checks.push({ name, status, message });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
42
|
/**
|
|
92
43
|
* Semver comparison: does `version` satisfy `constraint`?
|
|
93
44
|
* Constraint forms: >=X.Y.Z, >X.Y.Z, <=X.Y.Z, <X.Y.Z, =X.Y.Z, X.Y.Z
|
|
@@ -138,7 +89,7 @@ function satisfiesConstraint(version, constraint) {
|
|
|
138
89
|
* Returns the string value or empty string if absent.
|
|
139
90
|
* Uses './package.json' so Node resolves relative to CWD on all platforms.
|
|
140
91
|
*/
|
|
141
|
-
function pkgField(fieldPath) {
|
|
92
|
+
function pkgField(fieldPath, PROJECT_ROOT) {
|
|
142
93
|
try {
|
|
143
94
|
const pkg = JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, 'package.json'), 'utf8'));
|
|
144
95
|
let val = pkg;
|
|
@@ -152,155 +103,210 @@ function pkgField(fieldPath) {
|
|
|
152
103
|
}
|
|
153
104
|
}
|
|
154
105
|
|
|
155
|
-
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
let
|
|
160
|
-
try {
|
|
161
|
-
currentNode = process.version.replace(/^v/, '');
|
|
162
|
-
} catch { /* ignore */ }
|
|
106
|
+
function main() {
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Argument parsing
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
let jsonMode = false;
|
|
163
111
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
112
|
+
for (const arg of process.argv.slice(2)) {
|
|
113
|
+
if (arg === '--json') {
|
|
114
|
+
jsonMode = true;
|
|
115
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
116
|
+
process.stdout.write(
|
|
117
|
+
'scripts/check-env.cjs — Environment parity validator for contributors (issue #117).\n' +
|
|
118
|
+
'\n' +
|
|
119
|
+
'Checks that the developer\'s environment matches project requirements before\n' +
|
|
120
|
+
'running tests or audits. Designed to catch mismatches early rather than\n' +
|
|
121
|
+
'through cryptic test failures.\n' +
|
|
122
|
+
'\n' +
|
|
123
|
+
'Exit codes:\n' +
|
|
124
|
+
' 0 All checks passed\n' +
|
|
125
|
+
' 1 One or more checks failed\n' +
|
|
126
|
+
' 2 Tool error (missing required tool, corrupt package.json, etc.)\n' +
|
|
127
|
+
'\n' +
|
|
128
|
+
'Usage:\n' +
|
|
129
|
+
' node scripts/check-env.cjs # Human-readable report\n' +
|
|
130
|
+
' node scripts/check-env.cjs --json # Structured JSON report\n' +
|
|
131
|
+
' node scripts/check-env.cjs --help # This message\n'
|
|
132
|
+
);
|
|
133
|
+
return 0;
|
|
134
|
+
} else {
|
|
135
|
+
process.stderr.write(`Unknown option: ${arg}\n`);
|
|
136
|
+
throw new ExitError(2);
|
|
137
|
+
}
|
|
173
138
|
}
|
|
174
|
-
}
|
|
175
139
|
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
|
-
//
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Locate the project root (directory containing package.json)
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
const PROJECT_ROOT = process.cwd();
|
|
144
|
+
const PACKAGE_JSON = path.join(PROJECT_ROOT, 'package.json');
|
|
145
|
+
|
|
146
|
+
if (!fs.existsSync(PACKAGE_JSON)) {
|
|
147
|
+
process.stderr.write(`ERROR: package.json not found in ${PROJECT_ROOT}\n`);
|
|
148
|
+
throw new ExitError(2);
|
|
185
149
|
}
|
|
186
|
-
} catch { /* ignore */ }
|
|
187
150
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
addCheck(
|
|
192
|
-
|
|
193
|
-
if (satisfiesConstraint(currentNpm, enginesNpm)) {
|
|
194
|
-
addCheck('npm-version', 'pass', `npm ${currentNpm} satisfies ${enginesNpm}`);
|
|
195
|
-
} else {
|
|
196
|
-
addCheck('npm-version', 'fail', `npm ${currentNpm} does NOT satisfy engines.npm ${enginesNpm}`);
|
|
151
|
+
/** @type {Array<{name: string, status: 'pass'|'fail'|'skip', message: string}>} */
|
|
152
|
+
const checks = [];
|
|
153
|
+
|
|
154
|
+
function addCheck(name, status, message) {
|
|
155
|
+
checks.push({ name, status, message });
|
|
197
156
|
}
|
|
198
|
-
}
|
|
199
157
|
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
// Check
|
|
202
|
-
// ---------------------------------------------------------------------------
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Check 1: Node version vs engines.node
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
const enginesNode = pkgField('engines.node', PROJECT_ROOT);
|
|
162
|
+
let currentNode = '';
|
|
163
|
+
try {
|
|
164
|
+
currentNode = process.version.replace(/^v/, '');
|
|
165
|
+
} catch { /* ignore */ }
|
|
209
166
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
167
|
+
if (!currentNode) {
|
|
168
|
+
addCheck('node-version', 'fail', 'node binary not found on PATH');
|
|
169
|
+
} else if (!enginesNode) {
|
|
170
|
+
addCheck('node-version', 'fail', 'engines.node missing from package.json — add it (see D2 in docs/contributing/bootstrap.md)');
|
|
171
|
+
} else {
|
|
172
|
+
if (satisfiesConstraint(currentNode, enginesNode)) {
|
|
173
|
+
addCheck('node-version', 'pass', `Node ${currentNode} satisfies ${enginesNode}`);
|
|
174
|
+
} else {
|
|
175
|
+
addCheck('node-version', 'fail', `Node ${currentNode} does NOT satisfy engines.node ${enginesNode}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Check 2: npm version vs engines.npm (skip if field absent)
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
const enginesNpm = pkgField('engines.npm', PROJECT_ROOT);
|
|
183
|
+
let currentNpm = '';
|
|
214
184
|
try {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
185
|
+
const res = spawnSync(npmCmd, ['--version'], { encoding: 'utf8', timeout: 10_000, shell: process.platform === 'win32' });
|
|
186
|
+
if (res.status === 0 && res.stdout) {
|
|
187
|
+
currentNpm = res.stdout.trim();
|
|
188
|
+
}
|
|
189
|
+
} catch { /* ignore */ }
|
|
190
|
+
|
|
191
|
+
if (!enginesNpm) {
|
|
192
|
+
addCheck('npm-version', 'skip', 'engines.npm not set in package.json — skipping');
|
|
193
|
+
} else if (!currentNpm) {
|
|
194
|
+
addCheck('npm-version', 'fail', 'npm binary not found on PATH');
|
|
195
|
+
} else {
|
|
196
|
+
if (satisfiesConstraint(currentNpm, enginesNpm)) {
|
|
197
|
+
addCheck('npm-version', 'pass', `npm ${currentNpm} satisfies ${enginesNpm}`);
|
|
226
198
|
} else {
|
|
199
|
+
addCheck('npm-version', 'fail', `npm ${currentNpm} does NOT satisfy engines.npm ${enginesNpm}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Check 3: Lockfile presence
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
const LOCKFILE = path.join(PROJECT_ROOT, 'package-lock.json');
|
|
207
|
+
if (fs.existsSync(LOCKFILE)) {
|
|
208
|
+
addCheck('lockfile-present', 'pass', 'package-lock.json exists');
|
|
209
|
+
} else {
|
|
210
|
+
addCheck('lockfile-present', 'fail', "package-lock.json missing — run 'npm install' to generate it");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// Check 4: Lockfile sync (npm ci --dry-run)
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
if (fs.existsSync(LOCKFILE)) {
|
|
217
|
+
try {
|
|
218
|
+
// --ignore-scripts: this is a lockfile-vs-package.json sync check, not a
|
|
219
|
+
// build. Without it, npm would run the `prepare` lifecycle (build:lib via
|
|
220
|
+
// tsc) — which fails when check:env runs before deps are installed (tsc
|
|
221
|
+
// absent), misreporting an out-of-sync lockfile. ADR-457 build-at-publish.
|
|
222
|
+
const res = spawnSync(npmCmd, ['ci', '--dry-run', '--ignore-scripts'], {
|
|
223
|
+
cwd: PROJECT_ROOT,
|
|
224
|
+
encoding: 'utf8',
|
|
225
|
+
shell: process.platform === 'win32',
|
|
226
|
+
});
|
|
227
|
+
if (res.status === 0) {
|
|
228
|
+
addCheck('lockfile-sync', 'pass', 'package-lock.json is in sync with package.json');
|
|
229
|
+
} else {
|
|
230
|
+
addCheck('lockfile-sync', 'fail', "package-lock.json is out of sync — run 'npm ci' to restore");
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
227
233
|
addCheck('lockfile-sync', 'fail', "package-lock.json is out of sync — run 'npm ci' to restore");
|
|
228
234
|
}
|
|
229
|
-
}
|
|
230
|
-
addCheck('lockfile-sync', '
|
|
235
|
+
} else {
|
|
236
|
+
addCheck('lockfile-sync', 'skip', 'skipped — lockfile missing');
|
|
231
237
|
}
|
|
232
|
-
} else {
|
|
233
|
-
addCheck('lockfile-sync', 'skip', 'skipped — lockfile missing');
|
|
234
|
-
}
|
|
235
238
|
|
|
236
|
-
// ---------------------------------------------------------------------------
|
|
237
|
-
// Check 5: Version manager pin vs active Node
|
|
238
|
-
// Looks for .nvmrc, .node-version, or .tool-versions at project root.
|
|
239
|
-
// ---------------------------------------------------------------------------
|
|
240
|
-
const NVMRC = path.join(PROJECT_ROOT, '.nvmrc');
|
|
241
|
-
const NODE_VERSION_FILE = path.join(PROJECT_ROOT, '.node-version');
|
|
242
|
-
const TOOL_VERSIONS = path.join(PROJECT_ROOT, '.tool-versions');
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Check 5: Version manager pin vs active Node
|
|
241
|
+
// Looks for .nvmrc, .node-version, or .tool-versions at project root.
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
const NVMRC = path.join(PROJECT_ROOT, '.nvmrc');
|
|
244
|
+
const NODE_VERSION_FILE = path.join(PROJECT_ROOT, '.node-version');
|
|
245
|
+
const TOOL_VERSIONS = path.join(PROJECT_ROOT, '.tool-versions');
|
|
243
246
|
|
|
244
|
-
let pinnedMajor = '';
|
|
245
|
-
let pinSource = '';
|
|
247
|
+
let pinnedMajor = '';
|
|
248
|
+
let pinSource = '';
|
|
246
249
|
|
|
247
|
-
if (fs.existsSync(NVMRC)) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
} else if (fs.existsSync(NODE_VERSION_FILE)) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
} else if (fs.existsSync(TOOL_VERSIONS)) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
250
|
+
if (fs.existsSync(NVMRC)) {
|
|
251
|
+
const content = fs.readFileSync(NVMRC, 'utf8').split('\n')[0].trim().replace(/^v/, '');
|
|
252
|
+
pinnedMajor = content.split('.')[0];
|
|
253
|
+
pinSource = '.nvmrc';
|
|
254
|
+
} else if (fs.existsSync(NODE_VERSION_FILE)) {
|
|
255
|
+
const content = fs.readFileSync(NODE_VERSION_FILE, 'utf8').split('\n')[0].trim().replace(/^v/, '');
|
|
256
|
+
pinnedMajor = content.split('.')[0];
|
|
257
|
+
pinSource = '.node-version';
|
|
258
|
+
} else if (fs.existsSync(TOOL_VERSIONS)) {
|
|
259
|
+
const lines = fs.readFileSync(TOOL_VERSIONS, 'utf8').split('\n');
|
|
260
|
+
const nodeLine = lines.find(l => /^nodejs\s+/.test(l));
|
|
261
|
+
if (nodeLine) {
|
|
262
|
+
const ver = nodeLine.split(/\s+/)[1] || '';
|
|
263
|
+
pinnedMajor = ver.replace(/^v/, '').split('.')[0];
|
|
264
|
+
pinSource = '.tool-versions';
|
|
265
|
+
}
|
|
262
266
|
}
|
|
263
|
-
}
|
|
264
267
|
|
|
265
|
-
if (!pinnedMajor) {
|
|
266
|
-
|
|
267
|
-
} else if (process.env.CI === 'true') {
|
|
268
|
-
|
|
269
|
-
} else {
|
|
270
|
-
const activeMajor = process.version.replace(/^v/, '').split('.')[0];
|
|
271
|
-
if (activeMajor === pinnedMajor) {
|
|
272
|
-
addCheck('version-manager-pin', 'pass', `Active Node major (${activeMajor}) matches ${pinSource} pin (${pinnedMajor})`);
|
|
268
|
+
if (!pinnedMajor) {
|
|
269
|
+
addCheck('version-manager-pin', 'skip', 'no .nvmrc, .node-version, or .tool-versions found — skipping');
|
|
270
|
+
} else if (process.env.CI === 'true') {
|
|
271
|
+
addCheck('version-manager-pin', 'skip', 'CI=true — version-manager pin check skipped (matrix tests multiple Node majors)');
|
|
273
272
|
} else {
|
|
274
|
-
|
|
273
|
+
const activeMajor = process.version.replace(/^v/, '').split('.')[0];
|
|
274
|
+
if (activeMajor === pinnedMajor) {
|
|
275
|
+
addCheck('version-manager-pin', 'pass', `Active Node major (${activeMajor}) matches ${pinSource} pin (${pinnedMajor})`);
|
|
276
|
+
} else {
|
|
277
|
+
addCheck('version-manager-pin', 'fail', `Active Node major (${activeMajor}) does NOT match ${pinSource} pin (${pinnedMajor}) — run 'nvm use' or equivalent`);
|
|
278
|
+
}
|
|
275
279
|
}
|
|
276
|
-
}
|
|
277
280
|
|
|
278
|
-
// ---------------------------------------------------------------------------
|
|
279
|
-
// Output
|
|
280
|
-
// ---------------------------------------------------------------------------
|
|
281
|
-
const overallPass = checks.every(c => c.status !== 'fail');
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// Output
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
const overallPass = checks.every(c => c.status !== 'fail');
|
|
282
285
|
|
|
283
|
-
if (jsonMode) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
} else {
|
|
291
|
-
// Human-readable report
|
|
292
|
-
process.stdout.write('=== Environment Check ===\n');
|
|
293
|
-
for (const { name, status, message } of checks) {
|
|
294
|
-
const icon = status === 'pass' ? '[PASS]' : status === 'fail' ? '[FAIL]' : '[SKIP]';
|
|
295
|
-
const namePadded = name.padEnd(25);
|
|
296
|
-
process.stdout.write(` ${icon} ${namePadded} ${message}\n`);
|
|
297
|
-
}
|
|
298
|
-
process.stdout.write('\n');
|
|
299
|
-
if (overallPass) {
|
|
300
|
-
process.stdout.write('Result: ALL CHECKS PASSED\n');
|
|
286
|
+
if (jsonMode) {
|
|
287
|
+
// Structured JSON: {pass: bool, checks: [{name, status, message}]}
|
|
288
|
+
const out = {
|
|
289
|
+
pass: overallPass,
|
|
290
|
+
checks: checks.map(c => ({ name: c.name, status: c.status, message: c.message })),
|
|
291
|
+
};
|
|
292
|
+
process.stdout.write(JSON.stringify(out, null, 2) + '\n');
|
|
301
293
|
} else {
|
|
302
|
-
|
|
294
|
+
// Human-readable report
|
|
295
|
+
process.stdout.write('=== Environment Check ===\n');
|
|
296
|
+
for (const { name, status, message } of checks) {
|
|
297
|
+
const icon = status === 'pass' ? '[PASS]' : status === 'fail' ? '[FAIL]' : '[SKIP]';
|
|
298
|
+
const namePadded = name.padEnd(25);
|
|
299
|
+
process.stdout.write(` ${icon} ${namePadded} ${message}\n`);
|
|
300
|
+
}
|
|
301
|
+
process.stdout.write('\n');
|
|
302
|
+
if (overallPass) {
|
|
303
|
+
process.stdout.write('Result: ALL CHECKS PASSED\n');
|
|
304
|
+
} else {
|
|
305
|
+
process.stdout.write('Result: ONE OR MORE CHECKS FAILED — see above\n');
|
|
306
|
+
}
|
|
303
307
|
}
|
|
308
|
+
|
|
309
|
+
return overallPass ? 0 : 1;
|
|
304
310
|
}
|
|
305
311
|
|
|
306
|
-
|
|
312
|
+
runMain(main);
|