@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
|
@@ -16,62 +16,7 @@
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let ignoreExtraneous = false;
|
|
22
|
-
|
|
23
|
-
for (const arg of process.argv.slice(2)) {
|
|
24
|
-
if (arg === '--ignore-extraneous') {
|
|
25
|
-
ignoreExtraneous = true;
|
|
26
|
-
} else if (arg === '--help' || arg === '-h') {
|
|
27
|
-
process.stdout.write(
|
|
28
|
-
'Usage: node scripts/check-npm-integrity.cjs [--ignore-extraneous]\n'
|
|
29
|
-
);
|
|
30
|
-
process.exit(0);
|
|
31
|
-
} else {
|
|
32
|
-
process.stderr.write(`ERROR: Unknown argument: ${arg}\n`);
|
|
33
|
-
process.exit(2);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ---- Locate lockfile --------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
const lockfilePath = path.join(process.cwd(), 'package-lock.json');
|
|
40
|
-
|
|
41
|
-
if (!fs.existsSync(lockfilePath)) {
|
|
42
|
-
process.stderr.write(`ERROR: package-lock.json not found in ${process.cwd()}\n`);
|
|
43
|
-
process.exit(2);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ---- Parse lockfile ---------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
let lock;
|
|
49
|
-
try {
|
|
50
|
-
lock = JSON.parse(fs.readFileSync(lockfilePath, 'utf-8'));
|
|
51
|
-
} catch (e) {
|
|
52
|
-
process.stderr.write(`ERROR: Failed to parse package-lock.json: ${e.message}\n`);
|
|
53
|
-
process.exit(2);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const lockVersion = lock.lockfileVersion || 1;
|
|
57
|
-
if (lockVersion < 2) {
|
|
58
|
-
process.stderr.write(
|
|
59
|
-
`ERROR: package-lock.json lockfileVersion ${lockVersion} is not supported. ` +
|
|
60
|
-
'Run `npm install` to upgrade to v3.\n'
|
|
61
|
-
);
|
|
62
|
-
process.exit(2);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const packages = lock.packages || {};
|
|
66
|
-
const rootEntry = packages[''] || {};
|
|
67
|
-
|
|
68
|
-
// Collect declared dependency ranges from root entry.
|
|
69
|
-
const declaredRanges = {};
|
|
70
|
-
for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {
|
|
71
|
-
for (const [name, range] of Object.entries(rootEntry[field] || {})) {
|
|
72
|
-
if (!declaredRanges[name]) declaredRanges[name] = range;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
19
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
75
20
|
|
|
76
21
|
// ---- Minimal semver satisfies -----------------------------------------------
|
|
77
22
|
|
|
@@ -132,78 +77,139 @@ function satisfies(installed, range) {
|
|
|
132
77
|
return installed === range;
|
|
133
78
|
}
|
|
134
79
|
|
|
135
|
-
|
|
80
|
+
function main() {
|
|
81
|
+
// ---- Argument parsing -------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
let ignoreExtraneous = false;
|
|
84
|
+
|
|
85
|
+
for (const arg of process.argv.slice(2)) {
|
|
86
|
+
if (arg === '--ignore-extraneous') {
|
|
87
|
+
ignoreExtraneous = true;
|
|
88
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
89
|
+
process.stdout.write(
|
|
90
|
+
'Usage: node scripts/check-npm-integrity.cjs [--ignore-extraneous]\n'
|
|
91
|
+
);
|
|
92
|
+
return 0;
|
|
93
|
+
} else {
|
|
94
|
+
process.stderr.write(`ERROR: Unknown argument: ${arg}\n`);
|
|
95
|
+
throw new ExitError(2);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
136
98
|
|
|
137
|
-
|
|
138
|
-
const missings = [];
|
|
139
|
-
const extraneousFound = [];
|
|
99
|
+
// ---- Locate lockfile --------------------------------------------------------
|
|
140
100
|
|
|
141
|
-
|
|
142
|
-
if (!key.startsWith('node_modules/')) continue;
|
|
143
|
-
const rest = key.slice('node_modules/'.length);
|
|
144
|
-
const isScoped = rest[0] === '@';
|
|
145
|
-
const slashCount = (rest.match(/\//g) || []).length;
|
|
146
|
-
if (isScoped && slashCount > 1) continue;
|
|
147
|
-
if (!isScoped && slashCount > 0) continue;
|
|
101
|
+
const lockfilePath = path.join(process.cwd(), 'package-lock.json');
|
|
148
102
|
|
|
149
|
-
|
|
150
|
-
|
|
103
|
+
if (!fs.existsSync(lockfilePath)) {
|
|
104
|
+
process.stderr.write(`ERROR: package-lock.json not found in ${process.cwd()}\n`);
|
|
105
|
+
throw new ExitError(2);
|
|
106
|
+
}
|
|
151
107
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
108
|
+
// ---- Parse lockfile ---------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
let lock;
|
|
111
|
+
try {
|
|
112
|
+
lock = JSON.parse(fs.readFileSync(lockfilePath, 'utf-8'));
|
|
113
|
+
} catch (e) {
|
|
114
|
+
process.stderr.write(`ERROR: Failed to parse package-lock.json: ${e.message}\n`);
|
|
115
|
+
throw new ExitError(2);
|
|
155
116
|
}
|
|
156
117
|
|
|
157
|
-
|
|
118
|
+
const lockVersion = lock.lockfileVersion || 1;
|
|
119
|
+
if (lockVersion < 2) {
|
|
120
|
+
process.stderr.write(
|
|
121
|
+
`ERROR: package-lock.json lockfileVersion ${lockVersion} is not supported. ` +
|
|
122
|
+
'Run `npm install` to upgrade to v3.\n'
|
|
123
|
+
);
|
|
124
|
+
throw new ExitError(2);
|
|
125
|
+
}
|
|
158
126
|
|
|
159
|
-
|
|
160
|
-
|
|
127
|
+
const packages = lock.packages || {};
|
|
128
|
+
const rootEntry = packages[''] || {};
|
|
129
|
+
|
|
130
|
+
// Collect declared dependency ranges from root entry.
|
|
131
|
+
const declaredRanges = {};
|
|
132
|
+
for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {
|
|
133
|
+
for (const [name, range] of Object.entries(rootEntry[field] || {})) {
|
|
134
|
+
if (!declaredRanges[name]) declaredRanges[name] = range;
|
|
135
|
+
}
|
|
161
136
|
}
|
|
162
|
-
}
|
|
163
137
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
138
|
+
// ---- Walk packages map ------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
const invalids = [];
|
|
141
|
+
const missings = [];
|
|
142
|
+
const extraneousFound = [];
|
|
143
|
+
|
|
144
|
+
for (const [key, entry] of Object.entries(packages)) {
|
|
145
|
+
if (!key.startsWith('node_modules/')) continue;
|
|
146
|
+
const rest = key.slice('node_modules/'.length);
|
|
147
|
+
const isScoped = rest[0] === '@';
|
|
148
|
+
const slashCount = (rest.match(/\//g) || []).length;
|
|
149
|
+
if (isScoped && slashCount > 1) continue;
|
|
150
|
+
if (!isScoped && slashCount > 0) continue;
|
|
151
|
+
|
|
152
|
+
const pkgName = rest;
|
|
153
|
+
const installedVersion = entry.version || '';
|
|
154
|
+
|
|
155
|
+
if (entry.extraneous) {
|
|
156
|
+
extraneousFound.push({ name: pkgName, version: installedVersion });
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!declaredRanges[pkgName]) continue;
|
|
161
|
+
|
|
162
|
+
if (!satisfies(installedVersion, declaredRanges[pkgName])) {
|
|
163
|
+
invalids.push({ name: pkgName, version: installedVersion, declared: declaredRanges[pkgName] });
|
|
164
|
+
}
|
|
167
165
|
}
|
|
168
|
-
}
|
|
169
166
|
|
|
170
|
-
|
|
167
|
+
for (const name of Object.keys(declaredRanges)) {
|
|
168
|
+
if (!packages[`node_modules/${name}`]) {
|
|
169
|
+
missings.push({ name, required: declaredRanges[name] });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
171
172
|
|
|
172
|
-
|
|
173
|
-
const failMissing = missings.length > 0;
|
|
174
|
-
const failExtra = !ignoreExtraneous && extraneousFound.length > 0;
|
|
173
|
+
// ---- Verdict ----------------------------------------------------------------
|
|
175
174
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
175
|
+
const failInvalid = invalids.length > 0;
|
|
176
|
+
const failMissing = missings.length > 0;
|
|
177
|
+
const failExtra = !ignoreExtraneous && extraneousFound.length > 0;
|
|
180
178
|
|
|
181
|
-
|
|
179
|
+
if (!failInvalid && !failMissing && !failExtra) {
|
|
180
|
+
process.stderr.write('check-npm-integrity.cjs: clean\n');
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
182
183
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
lines.push(
|
|
184
|
+
const lines = ['FAIL: dependency integrity drift detected', ''];
|
|
185
|
+
|
|
186
|
+
if (failInvalid) {
|
|
187
|
+
lines.push(' INVALID (installed version does not satisfy declared range):');
|
|
188
|
+
for (const { name, declared, version } of invalids) {
|
|
189
|
+
lines.push(` ${name}: declared=${declared} installed=${version}`);
|
|
190
|
+
}
|
|
191
|
+
lines.push('');
|
|
187
192
|
}
|
|
188
|
-
lines.push('');
|
|
189
|
-
}
|
|
190
193
|
|
|
191
|
-
if (failMissing) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
if (failMissing) {
|
|
195
|
+
lines.push(' MISSING (declared but absent from lockfile packages map):');
|
|
196
|
+
for (const { name, required } of missings) {
|
|
197
|
+
lines.push(` ${name}@${required}`);
|
|
198
|
+
}
|
|
199
|
+
lines.push('');
|
|
195
200
|
}
|
|
196
|
-
lines.push('');
|
|
197
|
-
}
|
|
198
201
|
|
|
199
|
-
if (failExtra) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
202
|
+
if (failExtra) {
|
|
203
|
+
lines.push(' EXTRANEOUS (in lockfile but not declared as a dependency):');
|
|
204
|
+
for (const { name, version } of extraneousFound) {
|
|
205
|
+
lines.push(` ${name}@${version}`);
|
|
206
|
+
}
|
|
207
|
+
lines.push('');
|
|
203
208
|
}
|
|
204
|
-
|
|
209
|
+
|
|
210
|
+
lines.push('Remediation: rm -rf node_modules && npm ci');
|
|
211
|
+
process.stderr.write(lines.join('\n') + '\n');
|
|
212
|
+
throw new ExitError(1);
|
|
205
213
|
}
|
|
206
214
|
|
|
207
|
-
|
|
208
|
-
process.stderr.write(lines.join('\n') + '\n');
|
|
209
|
-
process.exit(1);
|
|
215
|
+
runMain(main);
|
|
@@ -6,11 +6,17 @@
|
|
|
6
6
|
// Exit 0 = github-hosted runner confirmed.
|
|
7
7
|
// Exit 1 = not a github-hosted runner (emits GitHub Actions error annotation).
|
|
8
8
|
|
|
9
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
10
|
+
|
|
9
11
|
const env = process.env.RUNNER_ENVIRONMENT || '';
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
function main() {
|
|
14
|
+
if (env !== 'github-hosted') {
|
|
15
|
+
throw new ExitError(
|
|
16
|
+
1,
|
|
17
|
+
`::error::Expected github-hosted runner. RUNNER_ENVIRONMENT=${env || 'unset'}`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
16
20
|
}
|
|
21
|
+
|
|
22
|
+
runMain(main);
|
|
@@ -14,33 +14,38 @@
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
|
|
17
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
18
|
+
|
|
17
19
|
const scope = process.env.TEST_SCOPE || '';
|
|
18
20
|
const targeted = process.env.TARGETED_TESTS || '';
|
|
19
21
|
const windows = process.env.WINDOWS_TESTS || '';
|
|
20
22
|
|
|
21
23
|
const FALLBACK = 'tests/command-contract.test.cjs tests/commands.test.cjs tests/core.test.cjs tests/package-manifest.test.cjs';
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Trim and fall back to default set if empty.
|
|
34
|
-
if (!selected.trim()) {
|
|
35
|
-
|
|
25
|
+
function main() {
|
|
26
|
+
let selected;
|
|
27
|
+
if (scope === 'windows') {
|
|
28
|
+
selected = windows;
|
|
29
|
+
} else if (scope === 'targeted') {
|
|
30
|
+
selected = targeted;
|
|
31
|
+
} else {
|
|
32
|
+
throw new ExitError(1, `::error::Unknown test scope: ${scope}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Trim and fall back to default set if empty.
|
|
36
|
+
if (!selected.trim()) {
|
|
37
|
+
selected = FALLBACK;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Split on whitespace, filter blanks, join with newlines.
|
|
41
|
+
const lines = selected.split(/\s+/).filter(Boolean);
|
|
42
|
+
const content = lines.join('\n') + '\n';
|
|
43
|
+
|
|
44
|
+
const outPath = path.join(process.cwd(), '.ci-selected-tests.txt');
|
|
45
|
+
fs.writeFileSync(outPath, content, 'utf-8');
|
|
46
|
+
|
|
47
|
+
process.stdout.write('Scoped tests:\n');
|
|
48
|
+
process.stdout.write(content);
|
|
36
49
|
}
|
|
37
50
|
|
|
38
|
-
|
|
39
|
-
const lines = selected.split(/\s+/).filter(Boolean);
|
|
40
|
-
const content = lines.join('\n') + '\n';
|
|
41
|
-
|
|
42
|
-
const outPath = path.join(process.cwd(), '.ci-selected-tests.txt');
|
|
43
|
-
fs.writeFileSync(outPath, content, 'utf-8');
|
|
44
|
-
|
|
45
|
-
process.stdout.write('Scoped tests:\n');
|
|
46
|
-
process.stdout.write(content);
|
|
51
|
+
runMain(main);
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
// Exit 0 = merged cleanly (or merge was a no-op).
|
|
12
12
|
// Exit 1 = merge conflict or fetch failure.
|
|
13
13
|
|
|
14
|
-
const { execFileSync
|
|
15
|
-
|
|
14
|
+
const { execFileSync } = require('child_process');
|
|
15
|
+
|
|
16
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
16
17
|
|
|
17
18
|
function run(cmd, args, opts) {
|
|
18
19
|
try {
|
|
@@ -27,8 +28,7 @@ function runOrThrow(cmd, args, label) {
|
|
|
27
28
|
try {
|
|
28
29
|
execFileSync(cmd, args, { stdio: 'inherit' });
|
|
29
30
|
} catch (e) {
|
|
30
|
-
|
|
31
|
-
process.exit(1);
|
|
31
|
+
throw new ExitError(1, `::error::${label} failed`);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -36,50 +36,51 @@ const token = process.env.GITHUB_TOKEN || '';
|
|
|
36
36
|
const baseBranch = process.env.GITHUB_BASE_REF || 'main';
|
|
37
37
|
const repo = process.env.GITHUB_REPOSITORY || '';
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
runOrThrow('git', ['config', 'user.
|
|
42
|
-
|
|
43
|
-
// Set authenticated remote URL.
|
|
44
|
-
if (token && repo) {
|
|
45
|
-
runOrThrow(
|
|
46
|
-
'git',
|
|
47
|
-
['remote', 'set-url', 'origin', `https://x-access-token:${token}@github.com/${repo}.git`],
|
|
48
|
-
'git remote set-url'
|
|
49
|
-
);
|
|
50
|
-
}
|
|
39
|
+
function main() {
|
|
40
|
+
// Configure git identity (needed for merge commit).
|
|
41
|
+
runOrThrow('git', ['config', 'user.email', 'ci@gsd-redux'], 'git config user.email');
|
|
42
|
+
runOrThrow('git', ['config', 'user.name', 'CI Rebase Check'], 'git config user.name');
|
|
51
43
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
// Set authenticated remote URL.
|
|
45
|
+
if (token && repo) {
|
|
46
|
+
runOrThrow(
|
|
47
|
+
'git',
|
|
48
|
+
['remote', 'set-url', 'origin', `https://x-access-token:${token}@github.com/${repo}.git`],
|
|
49
|
+
'git remote set-url'
|
|
50
|
+
);
|
|
59
51
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
|
|
53
|
+
// Fetch base branch with retry.
|
|
54
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
55
|
+
const result = run('git', ['fetch', 'origin', baseBranch]);
|
|
56
|
+
if (result) {
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
if (attempt === 3) {
|
|
60
|
+
throw new ExitError(1, `::error::git fetch origin ${baseBranch} failed after 3 attempts.`);
|
|
61
|
+
}
|
|
62
|
+
// Wait before retry: attempt * 4 seconds.
|
|
63
|
+
const waitMs = attempt * 4000;
|
|
64
|
+
const deadline = Date.now() + waitMs;
|
|
65
|
+
while (Date.now() < deadline) { /* busy wait, acceptable in CI */ }
|
|
63
66
|
}
|
|
64
|
-
// Wait before retry: attempt * 4 seconds.
|
|
65
|
-
const waitMs = attempt * 4000;
|
|
66
|
-
const deadline = Date.now() + waitMs;
|
|
67
|
-
while (Date.now() < deadline) { /* busy wait, acceptable in CI */ }
|
|
68
|
-
}
|
|
69
67
|
|
|
70
|
-
// Attempt merge.
|
|
71
|
-
try {
|
|
72
|
-
execFileSync('git', ['merge', '--no-edit', '--no-ff', `origin/${baseBranch}`], { stdio: 'inherit' });
|
|
73
|
-
} catch (e) {
|
|
74
|
-
process.stderr.write(
|
|
75
|
-
`::error::This PR cannot cleanly merge origin/${baseBranch}. Rebase your branch onto current ${baseBranch} and push again.\n`
|
|
76
|
-
);
|
|
77
|
-
process.stderr.write('::error::Conflicting files:\n');
|
|
68
|
+
// Attempt merge.
|
|
78
69
|
try {
|
|
79
|
-
execFileSync('git', ['
|
|
80
|
-
} catch (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
70
|
+
execFileSync('git', ['merge', '--no-edit', '--no-ff', `origin/${baseBranch}`], { stdio: 'inherit' });
|
|
71
|
+
} catch (e) {
|
|
72
|
+
process.stderr.write(
|
|
73
|
+
`::error::This PR cannot cleanly merge origin/${baseBranch}. Rebase your branch onto current ${baseBranch} and push again.\n`
|
|
74
|
+
);
|
|
75
|
+
process.stderr.write('::error::Conflicting files:\n');
|
|
76
|
+
try {
|
|
77
|
+
execFileSync('git', ['diff', '--name-only', '--diff-filter=U'], { stdio: 'inherit' });
|
|
78
|
+
} catch (_) { /* ignore */ }
|
|
79
|
+
try {
|
|
80
|
+
execFileSync('git', ['merge', '--abort'], { stdio: 'inherit' });
|
|
81
|
+
} catch (_) { /* ignore */ }
|
|
82
|
+
throw new ExitError(1);
|
|
83
|
+
}
|
|
85
84
|
}
|
|
85
|
+
|
|
86
|
+
runMain(main);
|