@orkestrel/scaffold 0.0.101 → 0.0.102
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/brief.js +6 -14
- package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +41 -28
- package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +5 -12
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +25 -13
- package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +18 -25
- package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +11 -9
- package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +233 -232
- package/dist/agents/skills/orkestrel-publish/scripts/compare.js +95 -89
- package/dist/agents/skills/orkestrel-publish/scripts/pins.js +5 -7
- package/dist/agents/skills/orkestrel-publish/scripts/swap.js +1273 -0
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +99 -89
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +54 -52
- package/dist/agents/skills/orkestrel-scout/scripts/map.js +160 -161
- package/dist/bin/main.js +483 -444
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +1 -0
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +8 -11
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +58 -28
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +5 -10
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +31 -12
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +28 -23
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +12 -10
- package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +318 -237
- package/dist/host/agents/skills/orkestrel-journey/references/statechart.md +2 -5
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +7 -6
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +12 -10
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +12 -5
- package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +124 -82
- package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +5 -7
- package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1655 -0
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +128 -97
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +70 -55
- package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +215 -155
- package/dist/host/claude/rules/architecture.md +2 -2
- package/dist/host/claude/rules/tests.md +8 -2
- package/dist/host/claude/rules/typescript.md +130 -0
- package/dist/host/claude/rules/workspace.md +30 -12
- package/dist/host/configs/helpers.ts +206 -133
- package/dist/host/configs/policy.ts +110 -105
- package/dist/host/guides/scaffold.md +244 -170
- package/dist/host/manifest.json +35 -29
- package/dist/host/tests/config.test.ts +1220 -633
- package/dist/host/tests/policy.test.ts +19 -0
- package/dist/host/tests/setupPolicy.ts +1669 -985
- package/dist/src/core/index.cjs +1595 -1686
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +202 -18
- package/dist/src/core/index.d.ts +202 -18
- package/dist/src/core/index.js +1590 -1687
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +448 -392
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +1182 -1052
- package/dist/src/server/index.d.ts +1182 -1052
- package/dist/src/server/index.js +447 -395
- package/dist/src/server/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -36,17 +36,12 @@ function readManifest(target) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
function readRanges(manifest, fields) {
|
|
39
|
-
|
|
40
|
-
for (const field of fields) {
|
|
39
|
+
return new Map(fields.flatMap((field) => {
|
|
41
40
|
const block = manifest[field];
|
|
42
41
|
if (typeof block !== 'object' || block === null)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
ranges.set(`${field} ${name}`, range);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return ranges;
|
|
42
|
+
return [];
|
|
43
|
+
return Object.entries(block).flatMap(([name, range]) => typeof range === 'string' ? [[`${field} ${name}`, range]] : []);
|
|
44
|
+
}));
|
|
50
45
|
}
|
|
51
46
|
function compareRanges(published, final) {
|
|
52
47
|
const names = [...new Set([...published.keys(), ...final.keys()])].sort();
|
|
@@ -112,7 +107,12 @@ function describeFailure(result) {
|
|
|
112
107
|
.slice(-3)
|
|
113
108
|
.join(' | ');
|
|
114
109
|
}
|
|
115
|
-
function
|
|
110
|
+
function readVerdict(result) {
|
|
111
|
+
return { exit: result.status ?? undefined, note: describeFailure(result) };
|
|
112
|
+
}
|
|
113
|
+
// A judge reads the finished command and answers the exit and note its one log entry carries, so no
|
|
114
|
+
// entry is amended after it is appended.
|
|
115
|
+
function runStep(runner, step, file, args, cwd, judge = readVerdict) {
|
|
116
116
|
const command = describeCommand(file, args);
|
|
117
117
|
if (runner.dry) {
|
|
118
118
|
runner.steps.push({ step, command, exit: undefined, durationMs: 0, note: 'dry run' });
|
|
@@ -125,15 +125,42 @@ function runStep(runner, step, file, args, cwd) {
|
|
|
125
125
|
windowsHide: true,
|
|
126
126
|
maxBuffer: 64 * 1024 * 1024,
|
|
127
127
|
});
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
exit: result.status ?? undefined,
|
|
132
|
-
durationMs: Date.now() - started,
|
|
133
|
-
note: describeFailure(result),
|
|
134
|
-
});
|
|
128
|
+
const durationMs = Date.now() - started;
|
|
129
|
+
const verdict = judge(result);
|
|
130
|
+
runner.steps.push({ step, command, exit: verdict.exit, durationMs, note: verdict.note });
|
|
135
131
|
return result;
|
|
136
132
|
}
|
|
133
|
+
// The audit exits 1 on the drift the pin step exists to close, so its exit is a reading here.
|
|
134
|
+
function judgePin(result) {
|
|
135
|
+
const releases = readReleases(result.stdout);
|
|
136
|
+
if (releases === undefined || (result.status !== 0 && result.status !== 1)) {
|
|
137
|
+
return {
|
|
138
|
+
exit: result.status === null || result.status === 0 ? 2 : result.status,
|
|
139
|
+
note: releases === undefined ? 'the audit printed no releases envelope' : describeFailure(result),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return { exit: 0, note: `${releases.length} release reading(s)` };
|
|
143
|
+
}
|
|
144
|
+
function judgeOverwrite(result, offline) {
|
|
145
|
+
if (!offline || result.status !== 1)
|
|
146
|
+
return readVerdict(result);
|
|
147
|
+
const note = readString(readJsonObject(result.stdout), 'note');
|
|
148
|
+
return note !== undefined && note.includes(OFFLINE_REFUSAL)
|
|
149
|
+
? { exit: 0, note: 'offline overwrite skipped the catalog step by design' }
|
|
150
|
+
: { exit: 1, note: describeFailure(result) };
|
|
151
|
+
}
|
|
152
|
+
// The audit reads the canonical sources of scaffold's own checkout as foreign paths and exits 1.
|
|
153
|
+
function judgeVerify(result, own) {
|
|
154
|
+
if (readReleases(result.stdout) === undefined) {
|
|
155
|
+
return {
|
|
156
|
+
exit: result.status === null || result.status === 0 ? 2 : result.status,
|
|
157
|
+
note: 'the audit printed no releases envelope',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return own && result.status === 1
|
|
161
|
+
? { exit: 0, note: 'foreign-path findings are expected on the scaffold checkout' }
|
|
162
|
+
: readVerdict(result);
|
|
163
|
+
}
|
|
137
164
|
function runNpmStep(runner, step, args) {
|
|
138
165
|
const command = `npm ${args.join(' ')}`;
|
|
139
166
|
if (runner.dry) {
|
|
@@ -154,52 +181,53 @@ function runNpmStep(runner, step, args) {
|
|
|
154
181
|
function recordStep(runner, step, command, note) {
|
|
155
182
|
runner.steps.push({ step, command, exit: runner.dry ? undefined : 0, durationMs: 0, note });
|
|
156
183
|
}
|
|
157
|
-
function replaceLastStep(runner, exit, note) {
|
|
158
|
-
const last = runner.steps.pop();
|
|
159
|
-
if (last !== undefined)
|
|
160
|
-
runner.steps.push({ ...last, exit, note });
|
|
161
|
-
}
|
|
162
184
|
function failed(runner) {
|
|
163
185
|
return runner.steps.some((entry) => entry.exit !== undefined &&
|
|
164
186
|
entry.exit !== 0 &&
|
|
165
187
|
entry.step !== 'pins' &&
|
|
166
188
|
entry.step !== 'compare');
|
|
167
189
|
}
|
|
190
|
+
// A release moves a declared range to its registry caret when the range differs, and a later release
|
|
191
|
+
// of the same name reads the range the earlier one wrote.
|
|
192
|
+
function computeRanges(ranges, releases) {
|
|
193
|
+
return releases.reduce((state, release) => {
|
|
194
|
+
if (release.latest === undefined || !(release.name in state.ranges))
|
|
195
|
+
return state;
|
|
196
|
+
const caret = `^${release.latest}`;
|
|
197
|
+
return state.ranges[release.name] === caret
|
|
198
|
+
? state
|
|
199
|
+
: {
|
|
200
|
+
ranges: { ...state.ranges, [release.name]: caret },
|
|
201
|
+
moved: [...state.moved, `${release.name} ${String(release.range)} -> ${caret}`],
|
|
202
|
+
};
|
|
203
|
+
}, { ranges, moved: [] });
|
|
204
|
+
}
|
|
168
205
|
function writeRanges(target, releases) {
|
|
169
206
|
const manifest = readManifest(target);
|
|
170
207
|
if (manifest === undefined)
|
|
171
208
|
return [];
|
|
172
|
-
const
|
|
173
|
-
for (const field of RANGE_FIELDS) {
|
|
209
|
+
const fields = RANGE_FIELDS.flatMap((field) => {
|
|
174
210
|
const block = manifest[field];
|
|
175
211
|
if (typeof block !== 'object' || block === null)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
manifest[field] = ranges;
|
|
212
|
+
return [];
|
|
213
|
+
return [{ field, ...computeRanges(Object.fromEntries(Object.entries(block)), releases) }];
|
|
214
|
+
});
|
|
215
|
+
const moved = fields.flatMap((entry) => entry.moved);
|
|
216
|
+
if (moved.length > 0) {
|
|
217
|
+
const written = {
|
|
218
|
+
...manifest,
|
|
219
|
+
...Object.fromEntries(fields.map((entry) => [entry.field, entry.ranges])),
|
|
220
|
+
};
|
|
221
|
+
writeFileSync(join(target, 'package.json'), `${JSON.stringify(written, null, '\t')}\n`);
|
|
188
222
|
}
|
|
189
|
-
if (moved.length > 0)
|
|
190
|
-
writeFileSync(join(target, 'package.json'), `${JSON.stringify(manifest, null, '\t')}\n`);
|
|
191
223
|
return moved;
|
|
192
224
|
}
|
|
193
225
|
function runPin(runner, scaffoldFile, scaffoldPrefix, offline) {
|
|
194
|
-
const audit = runStep(runner, 'pin', scaffoldFile, [...scaffoldPrefix, 'audit', '--json', ...offline], runner.target);
|
|
226
|
+
const audit = runStep(runner, 'pin', scaffoldFile, [...scaffoldPrefix, 'audit', '--json', ...offline], runner.target, judgePin);
|
|
195
227
|
if (audit !== undefined) {
|
|
196
228
|
const releases = readReleases(audit.stdout);
|
|
197
|
-
if (releases === undefined || (audit.status !== 0 && audit.status !== 1))
|
|
198
|
-
replaceLastStep(runner, audit.status === null || audit.status === 0 ? 2 : audit.status, releases === undefined ? 'the audit printed no releases envelope' : describeFailure(audit));
|
|
229
|
+
if (releases === undefined || (audit.status !== 0 && audit.status !== 1))
|
|
199
230
|
return;
|
|
200
|
-
}
|
|
201
|
-
// The audit exits 1 on the drift this step exists to close, so its exit is a reading here.
|
|
202
|
-
replaceLastStep(runner, 0, `${releases.length} release reading(s)`);
|
|
203
231
|
const moved = writeRanges(runner.target, releases);
|
|
204
232
|
recordStep(runner, 'pin', 'rewrite @orkestrel ranges to the registry caret', moved.length === 0 ? 'no range moved' : moved.join(', '));
|
|
205
233
|
}
|
|
@@ -226,33 +254,18 @@ function runCommit(runner) {
|
|
|
226
254
|
}
|
|
227
255
|
}
|
|
228
256
|
function runOverwrite(runner, scaffoldFile, scaffoldPrefix, offline) {
|
|
229
|
-
|
|
230
|
-
if (runner.offline && written !== undefined && written.status === 1) {
|
|
231
|
-
const note = readString(readJsonObject(written.stdout), 'note');
|
|
232
|
-
if (note !== undefined && note.includes(OFFLINE_REFUSAL)) {
|
|
233
|
-
replaceLastStep(runner, 0, 'offline overwrite skipped the catalog step by design');
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
replaceLastStep(runner, 1, describeFailure(written));
|
|
237
|
-
}
|
|
238
|
-
}
|
|
257
|
+
runStep(runner, 'overwrite', scaffoldFile, [...scaffoldPrefix, 'overwrite', '--json', ...offline], runner.target, (result) => judgeOverwrite(result, runner.offline));
|
|
239
258
|
if (!failed(runner)) {
|
|
240
259
|
runStep(runner, 'overwrite', scaffoldFile, [...scaffoldPrefix, 'audit', ...offline], runner.target);
|
|
241
260
|
}
|
|
242
261
|
}
|
|
243
262
|
function runVerify(runner, scaffoldFile, scaffoldPrefix, offline, own) {
|
|
244
|
-
const audit = runStep(runner, 'verify', scaffoldFile, [...scaffoldPrefix, 'audit', '--json', ...offline], runner.target);
|
|
263
|
+
const audit = runStep(runner, 'verify', scaffoldFile, [...scaffoldPrefix, 'audit', '--json', ...offline], runner.target, (result) => judgeVerify(result, own));
|
|
245
264
|
if (audit === undefined)
|
|
246
265
|
return;
|
|
247
|
-
// The audit reads the canonical sources of scaffold's own checkout as foreign paths and exits 1.
|
|
248
|
-
if (own && audit.status === 1) {
|
|
249
|
-
replaceLastStep(runner, 0, 'foreign-path findings are expected on the scaffold checkout');
|
|
250
|
-
}
|
|
251
266
|
const releases = readReleases(audit.stdout);
|
|
252
|
-
if (releases === undefined)
|
|
253
|
-
replaceLastStep(runner, audit.status === null || audit.status === 0 ? 2 : audit.status, 'the audit printed no releases envelope');
|
|
267
|
+
if (releases === undefined)
|
|
254
268
|
return;
|
|
255
|
-
}
|
|
256
269
|
const drift = releases.filter((release) => release.latest !== undefined && release.range !== `^${release.latest}`);
|
|
257
270
|
runner.steps.push({
|
|
258
271
|
step: 'verify',
|
|
@@ -336,13 +349,6 @@ function runVisit(argv) {
|
|
|
336
349
|
const version = readString(manifest, 'version');
|
|
337
350
|
const before = readRanges(manifest, RANGE_FIELDS);
|
|
338
351
|
const offline = runner.offline ? ['--offline'] : [];
|
|
339
|
-
let hits;
|
|
340
|
-
let ruling = {
|
|
341
|
-
distMoved: undefined,
|
|
342
|
-
rangesMoved: undefined,
|
|
343
|
-
ranges: undefined,
|
|
344
|
-
hits: undefined,
|
|
345
|
-
};
|
|
346
352
|
if (selected.has('pin'))
|
|
347
353
|
runPin(runner, scaffoldFile, scaffoldPrefix, offline);
|
|
348
354
|
if (selected.has('commit') && !failed(runner))
|
|
@@ -359,9 +365,9 @@ function runVisit(argv) {
|
|
|
359
365
|
}
|
|
360
366
|
if (selected.has('install') && !failed(runner))
|
|
361
367
|
runNpmStep(runner, 'install', ['install']);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
368
|
+
const hits = selected.has('pins') && !failed(runner)
|
|
369
|
+
? runPins(runner, before, readOption(argv, '--prior') ?? version)
|
|
370
|
+
: undefined;
|
|
365
371
|
if (selected.has('format') && !failed(runner))
|
|
366
372
|
runNpmStep(runner, 'format', ['run', 'format']);
|
|
367
373
|
if (selected.has('gates') && !failed(runner)) {
|
|
@@ -371,9 +377,10 @@ function runVisit(argv) {
|
|
|
371
377
|
runNpmStep(runner, 'gates', ['run', gate]);
|
|
372
378
|
}
|
|
373
379
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
380
|
+
const compared = selected.has('compare') && !failed(runner)
|
|
381
|
+
? runCompare(runner, name)
|
|
382
|
+
: { distMoved: undefined, rangesMoved: undefined, ranges: undefined, hits: undefined };
|
|
383
|
+
const ruling = { ...compared, hits };
|
|
377
384
|
const bump = ruling.distMoved === true || ruling.rangesMoved === true
|
|
378
385
|
? 'bump'
|
|
379
386
|
: ruling.distMoved === undefined || ruling.rangesMoved === undefined
|
|
@@ -397,23 +404,26 @@ function readLayers(catalogPath) {
|
|
|
397
404
|
const end = text.indexOf('<!-- /orkestrel:catalog -->');
|
|
398
405
|
if (start === -1 || end === -1)
|
|
399
406
|
return undefined;
|
|
400
|
-
const
|
|
401
|
-
|
|
407
|
+
const rows = text
|
|
408
|
+
.slice(start, end)
|
|
409
|
+
.split(/\r\n|\n/)
|
|
410
|
+
.flatMap((line) => {
|
|
402
411
|
const match = line.match(/^\|\s*`(@orkestrel\/[a-z0-9-]+)`\s*\|\s*`([^`]+)`\s*\|\s*(L\d+)\s*\|/u);
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
412
|
+
return match?.[1] === undefined || match[3] === undefined
|
|
413
|
+
? []
|
|
414
|
+
: [{ layer: match[3], member: `${match[1]} ${match[2] ?? ''}`.trim() }];
|
|
415
|
+
});
|
|
416
|
+
return new Map([...Map.groupBy(rows, ({ layer }) => layer)]
|
|
417
|
+
.map(([layer, members]) => [layer, members.map(({ member }) => member)])
|
|
418
|
+
.sort(([left], [right]) => Number(left.slice(1)) - Number(right.slice(1))));
|
|
410
419
|
}
|
|
411
420
|
function renderVisit(visit) {
|
|
412
|
-
const lines =
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
421
|
+
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})`}`),
|
|
423
|
+
`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
|
+
...(visit.ruling.ranges ?? []).map((range) => `wave: range ${range}`),
|
|
425
|
+
visit.row,
|
|
426
|
+
];
|
|
417
427
|
return `${lines.join('\n')}\n`;
|
|
418
428
|
}
|
|
419
429
|
function main(argv) {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// succeeded, 3 when it did not, 64 on usage.
|
|
19
19
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
20
20
|
import { dirname, join, resolve } from 'node:path';
|
|
21
|
-
import { readList, readMissingFlags, readOption, readString, runNpm, } from "../../orkestrel-dispatch/scripts/helpers.js";
|
|
21
|
+
import { parseJsonRecord, readList, readMissingFlags, readOption, readString, runNpm, } from "../../orkestrel-dispatch/scripts/helpers.js";
|
|
22
22
|
const POLL_MS = 5_000;
|
|
23
23
|
const ACCEPTED = /^\+ (\S+)@(\S+)\s*$/mu;
|
|
24
24
|
function waitFor(ms) {
|
|
@@ -31,27 +31,28 @@ function readWhoami() {
|
|
|
31
31
|
const user = result.stdout.trim();
|
|
32
32
|
return result.status === 0 && user !== '' ? user : undefined;
|
|
33
33
|
}
|
|
34
|
-
async function pollWhoami(waitSeconds) {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
return user;
|
|
34
|
+
async function pollWhoami(waitSeconds, started = Date.now()) {
|
|
35
|
+
const user = readWhoami();
|
|
36
|
+
if (user !== undefined || Date.now() - started >= waitSeconds * 1000)
|
|
37
|
+
return user;
|
|
38
|
+
await waitFor(POLL_MS);
|
|
39
|
+
return pollWhoami(waitSeconds, started);
|
|
42
40
|
}
|
|
43
41
|
function readServed(name, version) {
|
|
44
42
|
const result = runNpm(['view', `${name}@${version}`, 'version', '--json']);
|
|
45
43
|
return result.status === 0 && result.stdout.replace(/["\s]/gu, '') === version;
|
|
46
44
|
}
|
|
47
|
-
async function confirmServed(name, version, waitSeconds) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
async function confirmServed(name, version, waitSeconds, started = Date.now()) {
|
|
46
|
+
const served = readServed(name, version);
|
|
47
|
+
if (served || Date.now() - started >= waitSeconds * 1000)
|
|
48
|
+
return served;
|
|
49
|
+
await waitFor(POLL_MS);
|
|
50
|
+
return confirmServed(name, version, waitSeconds, started);
|
|
51
|
+
}
|
|
52
|
+
function findGitRoot(directory) {
|
|
53
|
+
return existsSync(join(directory, '.git')) || dirname(directory) === directory
|
|
54
|
+
? directory
|
|
55
|
+
: findGitRoot(dirname(directory));
|
|
55
56
|
}
|
|
56
57
|
function readErrorCode(text) {
|
|
57
58
|
return text.match(/\b(E[A-Z0-9]{3,}|EOTP|E401|E403|E404)\b/u)?.[1];
|
|
@@ -61,46 +62,49 @@ function readManifestName(directory) {
|
|
|
61
62
|
if (!existsSync(path))
|
|
62
63
|
return { name: undefined, version: undefined };
|
|
63
64
|
try {
|
|
64
|
-
const
|
|
65
|
-
if (typeof parsed !== 'object' || parsed === null)
|
|
66
|
-
return { name: undefined, version: undefined };
|
|
67
|
-
const record = Object.fromEntries(Object.entries(parsed));
|
|
65
|
+
const record = parseJsonRecord(readFileSync(path, 'utf8'));
|
|
68
66
|
return { name: readString(record, 'name'), version: readString(record, 'version') };
|
|
69
67
|
}
|
|
70
68
|
catch {
|
|
71
69
|
return { name: undefined, version: undefined };
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
journal,
|
|
95
|
-
};
|
|
96
|
-
if (!accepted) {
|
|
97
|
-
uploads.push(upload);
|
|
98
|
-
console.error(`window: ${name ?? directory} refused (${upload.code ?? `exit ${String(result.status)}`}); resume from it on a fresh code`);
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
72
|
+
function publishPackage(directory, otp) {
|
|
73
|
+
const manifest = readManifestName(directory);
|
|
74
|
+
const journal = `tmp/units/publish-${(manifest.name ?? directory).replace(/[^A-Za-z0-9.-]+/gu, '-')}.log`;
|
|
75
|
+
const result = runNpm(['publish', '--ignore-scripts', '--browser=false', `--otp=${otp}`], directory);
|
|
76
|
+
writeFileSync(journal, `${result.stdout}\n${result.stderr}\nexit=${result.status ?? 'null'}\n`);
|
|
77
|
+
const match = `${result.stdout}\n${result.stderr}`.match(ACCEPTED);
|
|
78
|
+
const accepted = result.status === 0 && match !== null;
|
|
79
|
+
const name = match?.[1] ?? manifest.name;
|
|
80
|
+
const version = match?.[2] ?? manifest.version;
|
|
81
|
+
const upload = {
|
|
82
|
+
directory,
|
|
83
|
+
name,
|
|
84
|
+
version,
|
|
85
|
+
exit: result.status ?? undefined,
|
|
86
|
+
accepted,
|
|
87
|
+
confirmed: false,
|
|
88
|
+
code: accepted ? undefined : readErrorCode(result.stderr),
|
|
89
|
+
journal,
|
|
90
|
+
};
|
|
91
|
+
if (accepted) {
|
|
101
92
|
console.error(`window: accepted ${name ?? '?'}@${version ?? '?'}`);
|
|
102
|
-
uploads.push(upload);
|
|
103
93
|
}
|
|
94
|
+
else {
|
|
95
|
+
console.error(`window: ${name ?? directory} refused (${upload.code ?? `exit ${String(result.status)}`}); resume from it on a fresh code`);
|
|
96
|
+
}
|
|
97
|
+
return upload;
|
|
98
|
+
}
|
|
99
|
+
async function publishAll(directories, otp, waitSeconds) {
|
|
100
|
+
mkdirSync('tmp/units', { recursive: true });
|
|
101
|
+
// A step after the first refusal returns the state unchanged, so nothing publishes past it.
|
|
102
|
+
const { uploads } = directories.reduce((state, directory) => {
|
|
103
|
+
if (state.stopped)
|
|
104
|
+
return state;
|
|
105
|
+
const upload = publishPackage(directory, otp);
|
|
106
|
+
return { uploads: [...state.uploads, upload], stopped: !upload.accepted };
|
|
107
|
+
}, { uploads: [], stopped: false });
|
|
104
108
|
const confirmed = [];
|
|
105
109
|
for (const upload of uploads) {
|
|
106
110
|
if (!upload.accepted || upload.name === undefined || upload.version === undefined) {
|
|
@@ -151,9 +155,7 @@ async function main(argv) {
|
|
|
151
155
|
return 64;
|
|
152
156
|
}
|
|
153
157
|
for (const directory of directories) {
|
|
154
|
-
|
|
155
|
-
while (!existsSync(join(root, '.git')) && dirname(root) !== root)
|
|
156
|
-
root = dirname(root);
|
|
158
|
+
const root = findGitRoot(resolve(directory));
|
|
157
159
|
const entry = join(root, '.git');
|
|
158
160
|
if (!existsSync(entry) || !statSync(entry).isFile())
|
|
159
161
|
continue;
|