@orkestrel/scaffold 0.0.103 → 0.0.104
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/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +84 -1
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +21 -6
- package/dist/agents/skills/orkestrel-harden/scripts/titles.js +338 -0
- package/dist/agents/skills/orkestrel-publish/scripts/swap.js +3 -39
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +433 -47
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +2 -1
- package/dist/bin/main.js +16 -9
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +3 -3
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +2 -2
- package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
- package/dist/host/agents/skills/orkestrel-build/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +1 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +100 -1
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +27 -6
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +4 -4
- package/dist/host/agents/skills/orkestrel-falsify/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +8 -7
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +6 -6
- package/dist/host/agents/skills/orkestrel-harden/SKILL.md +6 -1
- package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +1700 -0
- package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -2
- package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +48 -0
- package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +4 -2
- package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +425 -0
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +9 -8
- package/dist/host/agents/skills/orkestrel-polish/SKILL.md +3 -3
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +1 -0
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +56 -13
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +6 -6
- package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +5 -49
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +646 -49
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +2 -1
- package/dist/host/agents/transports/codex.md +1 -1
- package/dist/host/claude/agents/orkestrel.md +54 -53
- package/dist/host/claude/agents/reviewer.md +4 -4
- package/dist/host/claude/rules/documentation.md +2 -1
- package/dist/host/claude/rules/patterns.md +1 -1
- package/dist/host/claude/rules/quality.md +2 -2
- package/dist/host/claude/rules/styles.md +1 -1
- package/dist/host/claude/rules/tests.md +34 -8
- package/dist/host/claude/rules/typescript.md +29 -0
- package/dist/host/claude/rules/workspace.md +32 -22
- package/dist/host/claude/rules/writing.md +34 -26
- package/dist/host/claude/settings.json +2 -0
- package/dist/host/claude/skills/orkestrel-debrief/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +1 -1
- package/dist/host/codex/agents/analyst.toml +3 -3
- package/dist/host/codex/agents/reviewer.toml +4 -4
- package/dist/host/configs/policy.ts +13 -2
- package/dist/host/dotfiles/editorconfig +0 -1
- package/dist/host/dotfiles/gitattributes +0 -1
- package/dist/host/dotfiles/gitignore +0 -1
- package/dist/host/dotfiles/oxlintrc.json +24 -0
- package/dist/host/guides/agent.md +1638 -598
- package/dist/host/guides/budget.md +89 -29
- package/dist/host/guides/form.md +477 -91
- package/dist/host/guides/mcp.md +14 -2
- package/dist/host/guides/ollama.md +97 -73
- package/dist/host/guides/probe.md +1 -1
- package/dist/host/guides/scaffold.md +158 -101
- package/dist/host/guides/study.md +2635 -0
- package/dist/host/guides/table.md +225 -15
- package/dist/host/guides/terminal.md +22 -20
- package/dist/host/guides/test.md +689 -255
- package/dist/host/manifest.json +87 -63
- package/dist/host/tests/config.test.ts +136 -82
- package/dist/host/tests/policy.test.ts +39 -1
- package/dist/host/tests/setupPolicy.ts +86 -28
- package/dist/src/core/index.cjs +1387 -611
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +35 -20
- package/dist/src/core/index.d.ts +35 -20
- package/dist/src/core/index.js +1385 -612
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +12 -12
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +12 -12
- package/dist/src/server/index.d.ts +12 -12
- package/dist/src/server/index.js +12 -12
- package/dist/src/server/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { copyFileSync, existsSync, lstatSync, readFileSync, mkdirSync, realpathSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
4
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { parseJsonRecord, readJsonObject, readMissingFlags, readOption, readString, runNpm, } from "../../orkestrel-dispatch/scripts/helpers.js";
|
|
6
|
+
import { parseJsonRecord, isRecord, normalizeSlashes, removeTree, scanLocalSpecs, readJsonObject, readMissingFlags, readOption, readString, runNpm, } from "../../orkestrel-dispatch/scripts/helpers.js";
|
|
6
7
|
const STEPS = [
|
|
7
8
|
'pin',
|
|
8
9
|
'commit',
|
|
@@ -11,6 +12,7 @@ const STEPS = [
|
|
|
11
12
|
'install',
|
|
12
13
|
'pins',
|
|
13
14
|
'format',
|
|
15
|
+
'record',
|
|
14
16
|
'gates',
|
|
15
17
|
'compare',
|
|
16
18
|
];
|
|
@@ -102,8 +104,8 @@ function describeFailure(result) {
|
|
|
102
104
|
if (note !== undefined)
|
|
103
105
|
return note;
|
|
104
106
|
return result.stderr
|
|
105
|
-
.trim()
|
|
106
107
|
.split(/\r\n|\n/)
|
|
108
|
+
.filter((line) => line.trim() !== '')
|
|
107
109
|
.slice(-3)
|
|
108
110
|
.join(' | ');
|
|
109
111
|
}
|
|
@@ -112,7 +114,7 @@ function readVerdict(result) {
|
|
|
112
114
|
}
|
|
113
115
|
// A judge reads the finished command and answers the exit and note its one log entry carries, so no
|
|
114
116
|
// entry is amended after it is appended.
|
|
115
|
-
function runStep(runner, step, file, args, cwd, judge = readVerdict) {
|
|
117
|
+
function runStep(runner, step, file, args, cwd, judge = readVerdict, input) {
|
|
116
118
|
const command = describeCommand(file, args);
|
|
117
119
|
if (runner.dry) {
|
|
118
120
|
runner.steps.push({ step, command, exit: undefined, durationMs: 0, note: 'dry run' });
|
|
@@ -124,10 +126,11 @@ function runStep(runner, step, file, args, cwd, judge = readVerdict) {
|
|
|
124
126
|
encoding: 'utf8',
|
|
125
127
|
windowsHide: true,
|
|
126
128
|
maxBuffer: 64 * 1024 * 1024,
|
|
129
|
+
...(input === undefined ? {} : { input }),
|
|
127
130
|
});
|
|
128
131
|
const durationMs = Date.now() - started;
|
|
129
132
|
const verdict = judge(result);
|
|
130
|
-
runner
|
|
133
|
+
appendResult(runner, step, command, result, durationMs, verdict);
|
|
131
134
|
return result;
|
|
132
135
|
}
|
|
133
136
|
// The audit exits 1 on the drift the pin step exists to close, so its exit is a reading here.
|
|
@@ -161,34 +164,361 @@ function judgeVerify(result, own) {
|
|
|
161
164
|
? { exit: 0, note: 'foreign-path findings are expected on the scaffold checkout' }
|
|
162
165
|
: readVerdict(result);
|
|
163
166
|
}
|
|
164
|
-
function runNpmStep(runner, step, args) {
|
|
167
|
+
function runNpmStep(runner, step, args, judge = readVerdict) {
|
|
165
168
|
const command = `npm ${args.join(' ')}`;
|
|
166
169
|
if (runner.dry) {
|
|
167
|
-
runner.steps.push({
|
|
170
|
+
runner.steps.push({
|
|
171
|
+
step,
|
|
172
|
+
command,
|
|
173
|
+
exit: undefined,
|
|
174
|
+
durationMs: 0,
|
|
175
|
+
note: runner.regenerate && args[0] === 'install'
|
|
176
|
+
? 'dry run (regenerates on ERESOLVE)'
|
|
177
|
+
: 'dry run',
|
|
178
|
+
});
|
|
168
179
|
return undefined;
|
|
169
180
|
}
|
|
170
181
|
const started = Date.now();
|
|
171
182
|
const result = runNpm(args, runner.target);
|
|
183
|
+
appendResult(runner, step, command, result, Date.now() - started, judge(result));
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
function writeJournal(runner, step, command, exit, stdout, stderr) {
|
|
187
|
+
if (runner.directory === undefined)
|
|
188
|
+
return {};
|
|
189
|
+
const path = join(runner.directory, `${String(runner.steps.length + 1).padStart(2, '0')}-${step}.log`);
|
|
190
|
+
try {
|
|
191
|
+
validateWavePath(runner.target, path);
|
|
192
|
+
mkdirSync(runner.directory, { recursive: true });
|
|
193
|
+
validateWavePath(runner.target, path);
|
|
194
|
+
writeFileSync(path, `command: ${command}\nexit: ${String(exit)}\nstdout:\n${stdout}\nstderr:\n${stderr}\n`);
|
|
195
|
+
return { path };
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
return { failure: `journal failed: ${path}: ${String(error)}` };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function appendResult(runner, step, command, result, durationMs, verdict) {
|
|
202
|
+
const journal = writeJournal(runner, step, command, result.status ?? undefined, result.stdout ?? '', result.stderr ?? '');
|
|
172
203
|
runner.steps.push({
|
|
173
204
|
step,
|
|
174
205
|
command,
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
206
|
+
durationMs,
|
|
207
|
+
...(journal.path === undefined ? {} : { journal: journal.path }),
|
|
208
|
+
...(journal.failure === undefined
|
|
209
|
+
? verdict
|
|
210
|
+
: {
|
|
211
|
+
exit: 1,
|
|
212
|
+
note: [verdict.note, journal.failure].filter((value) => value !== undefined).join('; '),
|
|
213
|
+
}),
|
|
178
214
|
});
|
|
179
|
-
return result;
|
|
180
215
|
}
|
|
181
|
-
function
|
|
182
|
-
|
|
216
|
+
function appendStep(runner, step, command, note, exit = 0) {
|
|
217
|
+
const journal = runner.dry ? {} : writeJournal(runner, step, command, exit, note, '');
|
|
218
|
+
runner.steps.push({
|
|
219
|
+
step,
|
|
220
|
+
command,
|
|
221
|
+
exit: runner.dry ? undefined : journal.failure === undefined ? exit : 1,
|
|
222
|
+
durationMs: 0,
|
|
223
|
+
note: journal.failure === undefined ? note : `${note}; ${journal.failure}`,
|
|
224
|
+
...(journal.path === undefined ? {} : { journal: journal.path }),
|
|
225
|
+
});
|
|
183
226
|
}
|
|
184
227
|
function failed(runner) {
|
|
185
|
-
return runner.steps.some((entry) => entry.
|
|
228
|
+
return runner.steps.some((entry) => entry.superseded !== true &&
|
|
229
|
+
entry.exit !== undefined &&
|
|
186
230
|
entry.exit !== 0 &&
|
|
187
231
|
entry.step !== 'pins' &&
|
|
188
232
|
entry.step !== 'compare');
|
|
189
233
|
}
|
|
190
|
-
|
|
191
|
-
|
|
234
|
+
function extractConflict(result) {
|
|
235
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
236
|
+
if (!/\bcode ERESOLVE\b/u.test(output))
|
|
237
|
+
return undefined;
|
|
238
|
+
const lines = output.split(/\r\n|\n/);
|
|
239
|
+
return ((lines.find((line) => line.includes('Conflicting peer dependency:')) ??
|
|
240
|
+
lines.find((line) => line.includes('Could not resolve dependency:')))?.replace(/^npm (?:error|ERR!)\s*/u, '') ?? 'the install could not move a locked peer');
|
|
241
|
+
}
|
|
242
|
+
function runInstall(runner, step) {
|
|
243
|
+
// npm can unlink a node_modules junction before reporting its resolution failure.
|
|
244
|
+
if (!runner.dry &&
|
|
245
|
+
runner.regenerate &&
|
|
246
|
+
existsSync(join(runner.target, 'node_modules')) &&
|
|
247
|
+
lstatSync(join(runner.target, 'node_modules')).isSymbolicLink()) {
|
|
248
|
+
appendStep(runner, step, 'regenerate lock and modules', 'ERESOLVE; regeneration refused: node_modules is a link', 1);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const installed = runNpmStep(runner, step, ['install'], (result) => {
|
|
252
|
+
const conflict = extractConflict(result);
|
|
253
|
+
return conflict === undefined || result.status === 0
|
|
254
|
+
? readVerdict(result)
|
|
255
|
+
: {
|
|
256
|
+
exit: result.status ?? 1,
|
|
257
|
+
note: `ERESOLVE (${conflict}): pass --regenerate and resume with --from ${step} to replace package-lock.json and node_modules from the manifest`,
|
|
258
|
+
...(runner.regenerate ? { superseded: true } : {}),
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
if (installed !== undefined &&
|
|
262
|
+
installed.status !== 0 &&
|
|
263
|
+
extractConflict(installed) !== undefined &&
|
|
264
|
+
runner.regenerate)
|
|
265
|
+
regenerateLock(runner, step, installed);
|
|
266
|
+
}
|
|
267
|
+
function refuseLocalSpecs(runner, step) {
|
|
268
|
+
const specs = scanLocalSpecs(readManifest(runner.target) ?? {});
|
|
269
|
+
if (specs.length === 0)
|
|
270
|
+
return false;
|
|
271
|
+
appendStep(runner, step, 'scan manifest specs', `local dependency specs: ${specs.map(([path, spec]) => `${path}: ${spec}`).join(', ')}; restore the pins first`, 1);
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
function readRegenerationRefusal(runner) {
|
|
275
|
+
if (runStep(runner, 'regenerate', 'git', ['ls-files', '--error-unmatch', 'package-lock.json'], runner.target)?.status !== 0)
|
|
276
|
+
return 'package-lock.json is untracked';
|
|
277
|
+
const status = runStep(runner, 'regenerate', 'git', ['status', '--porcelain', '--', 'package-lock.json'], runner.target);
|
|
278
|
+
if (status?.status !== 0 || status.stdout.trim() !== '')
|
|
279
|
+
return 'package-lock.json carries uncommitted changes';
|
|
280
|
+
const manifest = readManifest(runner.target);
|
|
281
|
+
const specs = scanLocalSpecs(manifest ?? {});
|
|
282
|
+
if (specs.length > 0)
|
|
283
|
+
return `local dependency specs: ${specs.map(([path, spec]) => `${path}: ${spec}`).join(', ')}`;
|
|
284
|
+
if (manifest !== undefined && 'workspaces' in manifest)
|
|
285
|
+
return 'the manifest declares workspaces';
|
|
286
|
+
if (runStep(runner, 'regenerate', 'git', ['check-ignore', '-q', 'tmp/units/wave/probe'], runner.target)?.status !== 0)
|
|
287
|
+
return 'tmp/ is not git-ignored';
|
|
288
|
+
const root = runStep(runner, 'regenerate', 'git', ['rev-parse', '--show-toplevel'], runner.target);
|
|
289
|
+
if (root?.status !== 0 ||
|
|
290
|
+
normalizeSlashes(resolve(root.stdout.trim())) !== normalizeSlashes(resolve(runner.target)))
|
|
291
|
+
return 'the target is not the root of its work tree';
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
function moveModules(source, target, attempt = 0) {
|
|
295
|
+
try {
|
|
296
|
+
renameSync(source, target);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (!isRecord(error) || (error.code !== 'EBUSY' && error.code !== 'EPERM') || attempt >= 9)
|
|
300
|
+
throw error;
|
|
301
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
|
302
|
+
moveModules(source, target, attempt + 1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function regenerateLock(runner, step, conflict) {
|
|
306
|
+
const refusal = readRegenerationRefusal(runner);
|
|
307
|
+
if (refusal !== undefined || runner.directory === undefined) {
|
|
308
|
+
appendStep(runner, step, 'regenerate lock and modules', `ERESOLVE; regeneration refused: ${refusal ?? 'tmp/ is not git-ignored'}`, 1);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const lock = join(runner.target, 'package-lock.json');
|
|
312
|
+
const modules = join(runner.target, 'node_modules');
|
|
313
|
+
const backup = join(runner.directory, 'package-lock.before.json');
|
|
314
|
+
const moved = join(runner.directory, 'node_modules.before');
|
|
315
|
+
const manifest = readFileSync(join(runner.target, 'package.json'));
|
|
316
|
+
const before = createHash('sha256').update(readFileSync(lock)).digest('hex');
|
|
317
|
+
try {
|
|
318
|
+
for (const path of [backup, moved, lock, modules])
|
|
319
|
+
validateWavePath(runner.target, path);
|
|
320
|
+
mkdirSync(runner.directory, { recursive: true });
|
|
321
|
+
for (const path of [backup, moved, lock, modules])
|
|
322
|
+
validateWavePath(runner.target, path);
|
|
323
|
+
copyFileSync(lock, backup);
|
|
324
|
+
moveModules(modules, moved);
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
appendStep(runner, step, 'regenerate lock and modules', `ERESOLVE; regeneration refused: ${isRecord(error) ? String(error.code) : ''} in use; stop the processes that hold it; ${String(error)}`, 1);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const result = regenerateTree(runner.target, manifest, before);
|
|
331
|
+
const restored = result.exit === 0 ? undefined : restoreLock(runner.target, moved, backup, before);
|
|
332
|
+
const cleanup = result.exit === 0 ? discardBackup(runner.target, moved, backup) : undefined;
|
|
333
|
+
const names = [
|
|
334
|
+
...new Set([
|
|
335
|
+
...`${conflict.stdout}\n${conflict.stderr}`.matchAll(/(?:node_modules\/)((?:@[^/\s]+\/)?[^/\s]+)/gu),
|
|
336
|
+
].flatMap((match) => (match[1] === undefined ? [] : [match[1]]))),
|
|
337
|
+
];
|
|
338
|
+
const versions = result.exit === 0
|
|
339
|
+
? names
|
|
340
|
+
.map((name) => `${name}@${readString(readManifest(join(modules, name)), 'version') ?? 'absent'}`)
|
|
341
|
+
.join(', ')
|
|
342
|
+
: undefined;
|
|
343
|
+
appendStep(runner, step, 'regenerate lock and modules', [result.note, restored, versions === undefined ? undefined : `installed ${versions}`, cleanup]
|
|
344
|
+
.filter((value) => value !== undefined)
|
|
345
|
+
.join('; '), cleanup === undefined ? result.exit : 1);
|
|
346
|
+
if (result.installed !== undefined)
|
|
347
|
+
appendResult(runner, step, 'npm install (regenerated)', result.installed, result.duration ?? 0, readVerdict(result.installed));
|
|
348
|
+
if (result.listed !== undefined)
|
|
349
|
+
appendResult(runner, step, 'npm ls --all --json', result.listed, 0, readVerdict(result.listed));
|
|
350
|
+
}
|
|
351
|
+
// Journal writes and backup disposal cannot enter the transaction's rollback boundary.
|
|
352
|
+
function regenerateTree(target, manifest, before) {
|
|
353
|
+
const lock = join(target, 'package-lock.json');
|
|
354
|
+
try {
|
|
355
|
+
unlinkSync(lock);
|
|
356
|
+
const started = Date.now();
|
|
357
|
+
const installed = runNpm(['install'], target);
|
|
358
|
+
const duration = Date.now() - started;
|
|
359
|
+
const listed = installed.status === 0 ? runNpm(['ls', '--all', '--json'], target) : undefined;
|
|
360
|
+
const unchanged = manifest.equals(readFileSync(join(target, 'package.json')));
|
|
361
|
+
const proved = installed.status === 0 && existsSync(lock) && listed?.status === 0 && unchanged;
|
|
362
|
+
const after = existsSync(lock)
|
|
363
|
+
? createHash('sha256').update(readFileSync(lock)).digest('hex')
|
|
364
|
+
: 'absent';
|
|
365
|
+
return {
|
|
366
|
+
exit: proved ? 0 : 1,
|
|
367
|
+
note: `${proved ? '' : 'ERESOLVE; '}lock SHA-256 ${before} -> ${after}${unchanged ? '' : '; manifest bytes changed'}`,
|
|
368
|
+
installed,
|
|
369
|
+
...(listed === undefined ? {} : { listed }),
|
|
370
|
+
duration,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
return { exit: 1, note: `ERESOLVE; ${String(error)}` };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function discardBackup(target, moved, backup) {
|
|
378
|
+
try {
|
|
379
|
+
for (const path of [moved, backup, join(target, 'node_modules')])
|
|
380
|
+
validateWavePath(target, path);
|
|
381
|
+
removeTree(moved);
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
return `backup cleanup failed: ${String(error)}; proved installation retained; backups: ${backup}; ${moved}`;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function restoreLock(target, moved, backup, before) {
|
|
389
|
+
try {
|
|
390
|
+
for (const path of [
|
|
391
|
+
moved,
|
|
392
|
+
backup,
|
|
393
|
+
join(target, 'node_modules'),
|
|
394
|
+
join(target, 'package-lock.json'),
|
|
395
|
+
])
|
|
396
|
+
validateWavePath(target, path);
|
|
397
|
+
if (!lstatSync(moved).isDirectory() || !lstatSync(backup).isFile())
|
|
398
|
+
throw new Error('no recoverable backup');
|
|
399
|
+
const saved = readFileSync(backup);
|
|
400
|
+
if (createHash('sha256').update(saved).digest('hex') !== before)
|
|
401
|
+
throw new Error('backup lock digest changed');
|
|
402
|
+
removeTree(join(target, 'node_modules'));
|
|
403
|
+
moveModules(moved, join(target, 'node_modules'));
|
|
404
|
+
writeFileSync(join(target, 'package-lock.json'), saved);
|
|
405
|
+
return 'tree and lock restored';
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
return `rollback failed: ${String(error)}; backups: ${backup}; ${moved}`;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function validateWavePath(target, destination) {
|
|
412
|
+
const root = resolve(target);
|
|
413
|
+
const path = resolve(destination);
|
|
414
|
+
const location = relative(root, path);
|
|
415
|
+
if (location === '..' || location.startsWith(`..${sep}`) || isAbsolute(location))
|
|
416
|
+
throw new Error(`unsafe wave path: ${path} resolves outside ${root}`);
|
|
417
|
+
const physical = realpathSync(root);
|
|
418
|
+
const segments = location === '' ? [] : location.split(sep);
|
|
419
|
+
for (const entry of [
|
|
420
|
+
root,
|
|
421
|
+
...segments.map((_, index) => join(root, ...segments.slice(0, index + 1))),
|
|
422
|
+
]) {
|
|
423
|
+
const status = lstatSync(entry, { throwIfNoEntry: false });
|
|
424
|
+
if (status === undefined)
|
|
425
|
+
continue;
|
|
426
|
+
if (status.isSymbolicLink())
|
|
427
|
+
throw new Error(`unsafe wave path: ${entry} is a link`);
|
|
428
|
+
if (entry !== path && !status.isDirectory())
|
|
429
|
+
throw new Error(`unsafe wave path: ${entry} is not a directory`);
|
|
430
|
+
const actual = relative(physical, realpathSync(entry));
|
|
431
|
+
if (actual === '..' || actual.startsWith(`..${sep}`) || isAbsolute(actual))
|
|
432
|
+
throw new Error(`unsafe wave path: ${entry} resolves outside ${physical}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function readDirtyPaths(text) {
|
|
436
|
+
const [entry, ...rest] = text.split('\0');
|
|
437
|
+
if (entry === undefined || entry === '')
|
|
438
|
+
return [];
|
|
439
|
+
const renamed = /[RC]/u.test(entry.slice(0, 2));
|
|
440
|
+
const original = renamed ? rest[0] : undefined;
|
|
441
|
+
return [
|
|
442
|
+
entry.slice(3),
|
|
443
|
+
...(original === undefined ? [] : [original]),
|
|
444
|
+
...readDirtyPaths((renamed ? rest.slice(1) : rest).join('\0')),
|
|
445
|
+
];
|
|
446
|
+
}
|
|
447
|
+
function readReceipt(target) {
|
|
448
|
+
try {
|
|
449
|
+
const value = parseJsonRecord(readFileSync(join(target, 'tmp', 'units', 'wave', 'overwrite.json'), 'utf8'));
|
|
450
|
+
if (value === undefined ||
|
|
451
|
+
typeof value.head !== 'string' ||
|
|
452
|
+
typeof value.version !== 'string' ||
|
|
453
|
+
typeof value.offline !== 'boolean' ||
|
|
454
|
+
!Array.isArray(value.written) ||
|
|
455
|
+
!value.written.every((path) => typeof path === 'string') ||
|
|
456
|
+
!Array.isArray(value.removed) ||
|
|
457
|
+
!value.removed.every((path) => typeof path === 'string'))
|
|
458
|
+
return undefined;
|
|
459
|
+
return {
|
|
460
|
+
head: value.head,
|
|
461
|
+
version: value.version,
|
|
462
|
+
offline: value.offline,
|
|
463
|
+
written: value.written,
|
|
464
|
+
removed: value.removed,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function runRecord(runner, name) {
|
|
472
|
+
if (name === SCAFFOLD) {
|
|
473
|
+
appendStep(runner, 'record', 'git commit', `skipped: ${SCAFFOLD}`);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (refuseLocalSpecs(runner, 'record'))
|
|
477
|
+
return;
|
|
478
|
+
if (runner.dry) {
|
|
479
|
+
runStep(runner, 'record', 'git', ['add', '-A', '--pathspec-from-file=-', '--pathspec-file-nul'], runner.target);
|
|
480
|
+
runStep(runner, 'record', 'git', ['commit', '-m', 'Overwrite with scaffold <version>'], runner.target);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const status = runStep(runner, 'record', 'git', ['status', '--porcelain=v1', '-z', '--untracked-files=all'], runner.target);
|
|
484
|
+
if (status?.status !== 0)
|
|
485
|
+
return;
|
|
486
|
+
const dirty = [...new Set(readDirtyPaths(status.stdout))];
|
|
487
|
+
if (dirty.length === 0) {
|
|
488
|
+
appendStep(runner, 'record', 'git commit', 'nothing to commit');
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const receipt = readReceipt(runner.target);
|
|
492
|
+
const head = runStep(runner, 'record', 'git', ['rev-parse', 'HEAD'], runner.target);
|
|
493
|
+
if (receipt === undefined || head?.status !== 0 || receipt.head !== head.stdout.trim()) {
|
|
494
|
+
appendStep(runner, 'record', 'attribute overwrite paths', `uncommitted changes the visit cannot attribute to an overwrite at HEAD: ${dirty.join(', ')}`, 1);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (receipt.offline) {
|
|
498
|
+
appendStep(runner, 'record', 'git commit', 'an offline overwrite stays uncommitted until scaffold publishes');
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const allowed = [...receipt.written, ...receipt.removed, 'package.json', 'package-lock.json'].map((path) => normalizeSlashes(relative(runner.target, resolve(runner.target, path))));
|
|
502
|
+
const outside = dirty.filter((path) => !allowed.some((entry) => entry !== '' &&
|
|
503
|
+
entry !== '..' &&
|
|
504
|
+
!entry.startsWith('../') &&
|
|
505
|
+
!isAbsolute(entry) &&
|
|
506
|
+
path === entry));
|
|
507
|
+
if (outside.length > 0) {
|
|
508
|
+
appendStep(runner, 'record', 'contain overwrite paths', `dirty paths outside the overwrite: ${outside.join(', ')}`, 1);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
runStep(runner, 'record', 'git', ['--literal-pathspecs', 'add', '-A', '--pathspec-from-file=-', '--pathspec-file-nul'], runner.target, readVerdict, `${dirty.join('\0')}\0`);
|
|
512
|
+
if (failed(runner))
|
|
513
|
+
return;
|
|
514
|
+
runStep(runner, 'record', 'git', ['commit', '-m', `Overwrite with scaffold ${receipt.version}`], runner.target);
|
|
515
|
+
if (failed(runner))
|
|
516
|
+
return;
|
|
517
|
+
runStep(runner, 'record', 'git', ['status', '--porcelain'], runner.target, (result) => result.status === 0 && result.stdout.trim() !== ''
|
|
518
|
+
? { exit: 1, note: `tree remains dirty: ${result.stdout}` }
|
|
519
|
+
: readVerdict(result));
|
|
520
|
+
}
|
|
521
|
+
// A later release of the same name reads the range the earlier reading wrote.
|
|
192
522
|
function computeRanges(ranges, releases) {
|
|
193
523
|
return releases.reduce((state, release) => {
|
|
194
524
|
if (release.latest === undefined || !(release.name in state.ranges))
|
|
@@ -229,14 +559,16 @@ function runPin(runner, scaffoldFile, scaffoldPrefix, offline) {
|
|
|
229
559
|
if (releases === undefined || (audit.status !== 0 && audit.status !== 1))
|
|
230
560
|
return;
|
|
231
561
|
const moved = writeRanges(runner.target, releases);
|
|
232
|
-
|
|
562
|
+
appendStep(runner, 'pin', 'rewrite audit release ranges to the registry caret', moved.length === 0 ? 'no range moved' : moved.join(', '));
|
|
233
563
|
}
|
|
234
564
|
else {
|
|
235
|
-
|
|
565
|
+
appendStep(runner, 'pin', 'rewrite audit release ranges to the registry caret', 'dry run');
|
|
236
566
|
}
|
|
237
|
-
|
|
567
|
+
runInstall(runner, 'pin');
|
|
238
568
|
}
|
|
239
569
|
function runCommit(runner) {
|
|
570
|
+
if (refuseLocalSpecs(runner, 'commit'))
|
|
571
|
+
return;
|
|
240
572
|
const status = runStep(runner, 'commit', 'git', ['status', '--porcelain', '--', 'package.json', 'package-lock.json'], runner.target);
|
|
241
573
|
if (status === undefined || status.stdout.trim() !== '') {
|
|
242
574
|
runStep(runner, 'commit', 'git', [
|
|
@@ -250,11 +582,34 @@ function runCommit(runner) {
|
|
|
250
582
|
], runner.target);
|
|
251
583
|
}
|
|
252
584
|
else {
|
|
253
|
-
|
|
585
|
+
appendStep(runner, 'commit', 'git commit', 'manifest and lockfile already committed');
|
|
254
586
|
}
|
|
255
587
|
}
|
|
256
588
|
function runOverwrite(runner, scaffoldFile, scaffoldPrefix, offline) {
|
|
257
|
-
runStep(runner, 'overwrite', scaffoldFile, [...scaffoldPrefix, 'overwrite', '--json', ...offline], runner.target, (result) => judgeOverwrite(result, runner.offline));
|
|
589
|
+
const overwrite = runStep(runner, 'overwrite', scaffoldFile, [...scaffoldPrefix, 'overwrite', '--json', ...offline], runner.target, (result) => judgeOverwrite(result, runner.offline));
|
|
590
|
+
if (overwrite !== undefined && !failed(runner) && runner.directory !== undefined) {
|
|
591
|
+
const receipt = readJsonObject(overwrite.stdout);
|
|
592
|
+
const head = runStep(runner, 'overwrite', 'git', ['rev-parse', 'HEAD'], runner.target);
|
|
593
|
+
const installed = readManifest(join(runner.target, 'node_modules', '@orkestrel', 'scaffold'));
|
|
594
|
+
const version = readString(installed, 'version');
|
|
595
|
+
if (head?.status === 0 &&
|
|
596
|
+
version !== undefined &&
|
|
597
|
+
Array.isArray(receipt?.written) &&
|
|
598
|
+
receipt.written.every((path) => typeof path === 'string') &&
|
|
599
|
+
Array.isArray(receipt.removed) &&
|
|
600
|
+
receipt.removed.every((path) => typeof path === 'string')) {
|
|
601
|
+
const path = join(runner.target, 'tmp', 'units', 'wave', 'overwrite.json');
|
|
602
|
+
try {
|
|
603
|
+
validateWavePath(runner.target, path);
|
|
604
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
605
|
+
validateWavePath(runner.target, path);
|
|
606
|
+
writeFileSync(path, `${JSON.stringify({ head: head.stdout.trim(), version, offline: runner.offline, written: receipt.written, removed: receipt.removed })}\n`);
|
|
607
|
+
}
|
|
608
|
+
catch (error) {
|
|
609
|
+
appendStep(runner, 'overwrite', 'write overwrite receipt', String(error), 1);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
258
613
|
if (!failed(runner)) {
|
|
259
614
|
runStep(runner, 'overwrite', scaffoldFile, [...scaffoldPrefix, 'audit', ...offline], runner.target);
|
|
260
615
|
}
|
|
@@ -267,17 +622,11 @@ function runVerify(runner, scaffoldFile, scaffoldPrefix, offline, own) {
|
|
|
267
622
|
if (releases === undefined)
|
|
268
623
|
return;
|
|
269
624
|
const drift = releases.filter((release) => release.latest !== undefined && release.range !== `^${release.latest}`);
|
|
270
|
-
runner.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
note: drift.length === 0
|
|
276
|
-
? undefined
|
|
277
|
-
: drift
|
|
278
|
-
.map((release) => `${release.name} ${release.range} vs ${String(release.latest)}`)
|
|
279
|
-
.join(', '),
|
|
280
|
-
});
|
|
625
|
+
appendStep(runner, 'verify', 'compare every declared range with the registry', drift.length === 0
|
|
626
|
+
? 'all declared ranges match'
|
|
627
|
+
: drift
|
|
628
|
+
.map((release) => `${release.name} ${release.range} vs ${String(release.latest)}`)
|
|
629
|
+
.join(', '), drift.length === 0 ? 0 : 1);
|
|
281
630
|
}
|
|
282
631
|
function runPins(runner, before, version) {
|
|
283
632
|
const after = readRanges(readManifest(runner.target) ?? {}, RANGE_FIELDS);
|
|
@@ -285,7 +634,7 @@ function runPins(runner, before, version) {
|
|
|
285
634
|
.filter(([name, range]) => after.get(name) !== range)
|
|
286
635
|
.flatMap(([, range]) => ['--range', range]);
|
|
287
636
|
if (version === undefined) {
|
|
288
|
-
|
|
637
|
+
appendStep(runner, 'pins', basename(PINS), 'no prior version to sweep for; pass --prior');
|
|
289
638
|
return undefined;
|
|
290
639
|
}
|
|
291
640
|
const swept = runStep(runner, 'pins', process.execPath, [PINS, '--version', version, ...prior], runner.target);
|
|
@@ -303,7 +652,7 @@ function runCompare(runner, name) {
|
|
|
303
652
|
? false
|
|
304
653
|
: undefined;
|
|
305
654
|
if (name === undefined) {
|
|
306
|
-
|
|
655
|
+
appendStep(runner, 'compare', 'npm view', 'the manifest names no package to read');
|
|
307
656
|
return { distMoved, rangesMoved: undefined, ranges: undefined, hits: undefined };
|
|
308
657
|
}
|
|
309
658
|
const view = runNpmStep(runner, 'compare', ['view', name, '--json']);
|
|
@@ -331,12 +680,6 @@ function runVisit(argv) {
|
|
|
331
680
|
const [scaffoldFile, ...scaffoldPrefix] = scaffold;
|
|
332
681
|
if (scaffoldFile === undefined)
|
|
333
682
|
return undefined;
|
|
334
|
-
const runner = {
|
|
335
|
-
target,
|
|
336
|
-
dry: argv.includes('--dry-run'),
|
|
337
|
-
offline: argv.includes('--offline'),
|
|
338
|
-
steps: [],
|
|
339
|
-
};
|
|
340
683
|
const from = STEPS.indexOf(readOption(argv, '--from') ?? 'pin');
|
|
341
684
|
const to = STEPS.indexOf(readOption(argv, '--to') ?? 'compare');
|
|
342
685
|
const selected = new Set(STEPS.slice(from, to + 1));
|
|
@@ -347,15 +690,34 @@ function runVisit(argv) {
|
|
|
347
690
|
}
|
|
348
691
|
const name = readString(manifest, 'name');
|
|
349
692
|
const version = readString(manifest, 'version');
|
|
693
|
+
const runner = {
|
|
694
|
+
target: resolve(target),
|
|
695
|
+
dry: argv.includes('--dry-run'),
|
|
696
|
+
offline: argv.includes('--offline'),
|
|
697
|
+
regenerate: argv.includes('--regenerate'),
|
|
698
|
+
directory: argv.includes('--dry-run') ||
|
|
699
|
+
(name === SCAFFOLD && from === STEPS.indexOf('record') && to === from)
|
|
700
|
+
? undefined
|
|
701
|
+
: readJournalDirectory(resolve(target)),
|
|
702
|
+
steps: [],
|
|
703
|
+
};
|
|
704
|
+
if (runner.directory !== undefined) {
|
|
705
|
+
try {
|
|
706
|
+
validateWavePath(runner.target, runner.directory);
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
appendStep(runner, 'journal', 'validate wave destination', String(error), 1);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
350
712
|
const before = readRanges(manifest, RANGE_FIELDS);
|
|
351
713
|
const offline = runner.offline ? ['--offline'] : [];
|
|
352
|
-
if (selected.has('pin'))
|
|
714
|
+
if (selected.has('pin') && !failed(runner))
|
|
353
715
|
runPin(runner, scaffoldFile, scaffoldPrefix, offline);
|
|
354
716
|
if (selected.has('commit') && !failed(runner))
|
|
355
717
|
runCommit(runner);
|
|
356
718
|
if (selected.has('overwrite') && !failed(runner)) {
|
|
357
719
|
if (name === SCAFFOLD) {
|
|
358
|
-
|
|
720
|
+
appendStep(runner, 'overwrite', describeCommand(scaffoldFile, [...scaffoldPrefix, 'overwrite', '--json', ...offline]), `skipped with its audit: ${SCAFFOLD} is the package whose checkout the vendored host is staged from`);
|
|
359
721
|
}
|
|
360
722
|
else
|
|
361
723
|
runOverwrite(runner, scaffoldFile, scaffoldPrefix, offline);
|
|
@@ -364,12 +726,14 @@ function runVisit(argv) {
|
|
|
364
726
|
runVerify(runner, scaffoldFile, scaffoldPrefix, offline, name === SCAFFOLD);
|
|
365
727
|
}
|
|
366
728
|
if (selected.has('install') && !failed(runner))
|
|
367
|
-
|
|
729
|
+
runInstall(runner, 'install');
|
|
368
730
|
const hits = selected.has('pins') && !failed(runner)
|
|
369
731
|
? runPins(runner, before, readOption(argv, '--prior') ?? version)
|
|
370
732
|
: undefined;
|
|
371
733
|
if (selected.has('format') && !failed(runner))
|
|
372
734
|
runNpmStep(runner, 'format', ['run', 'format']);
|
|
735
|
+
if (selected.has('record') && !failed(runner))
|
|
736
|
+
runRecord(runner, name);
|
|
373
737
|
if (selected.has('gates') && !failed(runner)) {
|
|
374
738
|
for (const gate of GATES) {
|
|
375
739
|
if (failed(runner))
|
|
@@ -396,6 +760,23 @@ function runVisit(argv) {
|
|
|
396
760
|
row: `| ${name ?? target} | ${version ?? '?'} | ${bump} | ${reading} | round |`,
|
|
397
761
|
};
|
|
398
762
|
}
|
|
763
|
+
function readJournalDirectory(target) {
|
|
764
|
+
const inside = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
765
|
+
cwd: target,
|
|
766
|
+
encoding: 'utf8',
|
|
767
|
+
windowsHide: true,
|
|
768
|
+
});
|
|
769
|
+
if (inside.status !== 0 || inside.stdout.trim() !== 'true')
|
|
770
|
+
return undefined;
|
|
771
|
+
const ignored = spawnSync('git', ['check-ignore', '-q', 'tmp/units/wave/probe'], {
|
|
772
|
+
cwd: target,
|
|
773
|
+
encoding: 'utf8',
|
|
774
|
+
windowsHide: true,
|
|
775
|
+
});
|
|
776
|
+
return ignored.status === 0
|
|
777
|
+
? join(target, 'tmp', 'units', 'wave', new Date().toISOString().replace(/[:.]/gu, '-'))
|
|
778
|
+
: undefined;
|
|
779
|
+
}
|
|
399
780
|
function readLayers(catalogPath) {
|
|
400
781
|
if (!existsSync(catalogPath))
|
|
401
782
|
return undefined;
|
|
@@ -419,7 +800,7 @@ function readLayers(catalogPath) {
|
|
|
419
800
|
}
|
|
420
801
|
function renderVisit(visit) {
|
|
421
802
|
const lines = [
|
|
422
|
-
...visit.steps.map((entry) => `wave: ${entry.step.padEnd(9)} exit=${entry.exit === undefined ? 'dry' : String(entry.exit)} ${String(entry.durationMs).padStart(7)} ms ${entry.command}${entry.note === undefined ? '' : ` (${entry.note})`}`),
|
|
803
|
+
...visit.steps.map((entry) => `wave: ${entry.step.padEnd(9)} exit=${entry.exit === undefined ? 'dry' : String(entry.exit)} ${String(entry.durationMs).padStart(7)} ms ${entry.command}${entry.note === undefined ? '' : ` (${entry.note})`}${entry.journal === undefined ? '' : ` journal=${entry.journal}`}`),
|
|
423
804
|
`wave: ruling dist ${describeReading(visit.ruling.distMoved, 'moved', 'same')}, ranges ${describeReading(visit.ruling.rangesMoved, 'moved', 'same')}${visit.ruling.hits === undefined ? '' : `, ${visit.ruling.hits} self-pin hit(s)`}`,
|
|
424
805
|
...(visit.ruling.ranges ?? []).map((range) => `wave: range ${range}`),
|
|
425
806
|
visit.row,
|
|
@@ -444,6 +825,10 @@ function main(argv) {
|
|
|
444
825
|
return 64;
|
|
445
826
|
}
|
|
446
827
|
if (argv.includes('--plan')) {
|
|
828
|
+
if (argv.includes('--regenerate')) {
|
|
829
|
+
console.error('wave: --regenerate requires --visit, not --plan');
|
|
830
|
+
return 64;
|
|
831
|
+
}
|
|
447
832
|
const layers = readLayers(readOption(argv, '--catalog') ?? CATALOG);
|
|
448
833
|
if (layers === undefined) {
|
|
449
834
|
console.error('wave: no marker-bounded catalog table; run scaffold catalog first');
|
|
@@ -457,7 +842,7 @@ function main(argv) {
|
|
|
457
842
|
return 0;
|
|
458
843
|
}
|
|
459
844
|
if (!argv.includes('--visit')) {
|
|
460
|
-
console.error('usage: wave.ts --visit [--target DIR] [--offline] [--from STEP] [--to STEP] [--prior VERSION] [--dry-run] [--json] [--out FILE] | --plan [--catalog PATH] [--json]');
|
|
845
|
+
console.error('usage: wave.ts --visit [--target DIR] [--offline] [--regenerate] [--from STEP] [--to STEP] [--prior VERSION] [--dry-run] [--json] [--out FILE] | --plan [--catalog PATH] [--json]');
|
|
461
846
|
return 64;
|
|
462
847
|
}
|
|
463
848
|
const from = STEPS.indexOf(readOption(argv, '--from') ?? 'pin');
|
|
@@ -483,7 +868,8 @@ function main(argv) {
|
|
|
483
868
|
writeFileSync(out, output);
|
|
484
869
|
console.log(`wave: wrote ${out}`);
|
|
485
870
|
}
|
|
486
|
-
return visit.steps.some((entry) => entry.
|
|
871
|
+
return visit.steps.some((entry) => entry.superseded !== true &&
|
|
872
|
+
entry.exit !== undefined &&
|
|
487
873
|
entry.exit !== 0 &&
|
|
488
874
|
entry.step !== 'pins' &&
|
|
489
875
|
entry.step !== 'compare')
|
|
@@ -72,7 +72,8 @@ function readManifestName(directory) {
|
|
|
72
72
|
function publishPackage(directory, otp) {
|
|
73
73
|
const manifest = readManifestName(directory);
|
|
74
74
|
const journal = `tmp/units/publish-${(manifest.name ?? directory).replace(/[^A-Za-z0-9.-]+/gu, '-')}.log`;
|
|
75
|
-
|
|
75
|
+
// The refusal code must survive an inherited silent npm log level.
|
|
76
|
+
const result = runNpm(['publish', '--ignore-scripts', '--browser=false', '--loglevel=error', `--otp=${otp}`], directory);
|
|
76
77
|
writeFileSync(journal, `${result.stdout}\n${result.stderr}\nexit=${result.status ?? 'null'}\n`);
|
|
77
78
|
const match = `${result.stdout}\n${result.stderr}`.match(ACCEPTED);
|
|
78
79
|
const accepted = result.status === 0 && match !== null;
|