@orkestrel/scaffold 0.0.60 → 0.0.61
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/README.md +13 -10
- package/dist/bin/main.js +632 -320
- package/dist/bin/main.js.map +1 -1
- package/dist/host/CLAUDE.md +5 -1
- package/dist/host/agents/orchestration.md +44 -19
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +5 -5
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +10 -9
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +21 -11
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +20 -2
- package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +1 -1
- package/dist/host/agents/skills/orkestrel-harden-package/references/hardening.md +1 -1
- package/dist/host/agents/skills/orkestrel-harden-package/references/research.md +1 -1
- package/dist/host/agents/templates/brief.md +16 -7
- package/dist/host/agents/transports/claude.md +4 -2
- package/dist/host/agents/transports/codex.md +4 -1
- package/dist/host/claude/agents/analyst.md +3 -1
- package/dist/host/claude/agents/application.md +1 -1
- package/dist/host/claude/agents/builder.md +3 -3
- package/dist/host/claude/agents/checker.md +5 -0
- package/dist/host/claude/agents/grok.md +15 -5
- package/dist/host/claude/agents/implementer.md +1 -1
- package/dist/host/claude/agents/orkestrel.md +2 -2
- package/dist/host/claude/agents/planner.md +10 -0
- package/dist/host/claude/agents/reviewer.md +14 -8
- package/dist/host/claude/agents/sol.md +3 -1
- package/dist/host/claude/agents/verifier.md +2 -4
- package/dist/host/claude/rules/architecture.md +7 -5
- package/dist/host/claude/rules/documentation.md +1 -0
- package/dist/host/claude/rules/names.md +23 -5
- package/dist/host/claude/rules/patterns.md +1 -0
- package/dist/host/claude/rules/quality.md +1 -1
- package/dist/host/claude/rules/tests.md +3 -3
- package/dist/host/claude/rules/typescript.md +4 -1
- package/dist/host/claude/rules/writing.md +2 -2
- package/dist/host/codex/agents/builder.toml +6 -6
- package/dist/host/codex/agents/checker.toml +2 -1
- package/dist/host/codex/agents/grok.toml +12 -5
- package/dist/host/codex/agents/implementer.toml +2 -2
- package/dist/host/codex/agents/opus.toml +6 -1
- package/dist/host/codex/agents/planner.toml +11 -6
- package/dist/host/codex/agents/reviewer.toml +8 -6
- package/dist/host/guides/scaffold.md +39 -14
- package/dist/host/manifest.json +41 -41
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/src/core/index.cjs +420 -278
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +361 -220
- package/dist/src/core/index.d.ts +361 -220
- package/dist/src/core/index.js +417 -279
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +208 -170
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +276 -152
- package/dist/src/server/index.d.ts +276 -152
- package/dist/src/server/index.js +200 -172
- package/dist/src/server/index.js.map +1 -1
- package/package.json +4 -3
package/dist/bin/main.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { align, renderTable, strip, stripControls, width } from "@orkestrel/console";
|
|
3
|
-
import { attempt, isRecord, isString, parseJSON } from "@orkestrel/contract";
|
|
3
|
+
import { attempt, isError, isRecord, isString, parseJSON } from "@orkestrel/contract";
|
|
4
|
+
import { BIN_ENTRY_PATH, CATALOG_AGENT_PATH, CONFORMANCE_TEST_PATH, Compiler, DEPENDENCY_NAME_PATTERN, ENVIRONMENTS, GLOBAL_SETUP_PATH, GROUPS, GUIDES_TEST_PATH, HOST_PATHS, INTEGRATION_TEST_PATH, MAX_MANIFEST_BYTES, SERVICE_SETUP_PATH, SHOWCASE_CONFIG_PATH, ScaffoldError, blueprintToDevDependencies, blueprintToRootVite, blueprintToScripts, blueprintToTestArtifacts, blueprintToWritableScripts, compareVersions, createBlueprint, extractRangeMajor, extractVersion, isFloorPath, isScaffoldError, manifestToDependencies, manifestToName, nameToGuide, replaceManifestScripts, replacePlanRanges } from "../src/core/index.js";
|
|
5
|
+
import { Materializer, Upstream, filesToHost, isExactCaseFile, isPhysicalDirectory, isWorktree, listFiles, readFileText, readHostFloor, readSnapshot, resolveContainedPath } from "../src/server/index.js";
|
|
4
6
|
import { createMarkdown, flattenText, isTableNode } from "@orkestrel/markdown";
|
|
5
7
|
import { executeSync } from "@orkestrel/process/server";
|
|
6
|
-
import { BIN_ENTRY_PATH, CATALOG_AGENT_PATH, CONFORMANCE_TEST_PATH, Compiler, DEPENDENCY_NAME_PATTERN, ENVIRONMENTS, GLOBAL_SETUP_PATH, GROUPS, GUIDES_TEST_PATH, HOST_PATHS, INTEGRATION_TEST_PATH, MAX_MANIFEST_BYTES, SERVICE_SETUP_PATH, SHOWCASE_CONFIG_PATH, ScaffoldError, blueprintToDevDependencies, blueprintToRootVite, blueprintToScripts, blueprintToTestArtifacts, blueprintToWritableScripts, compareVersions, createBlueprint, extractRangeMajor, extractVersion, isCanonPath, isDeferredPath, isScaffoldError, manifestToDependencies, manifestToName, nameToGuide, replaceManifestScripts, replacePlanRanges } from "../src/core/index.js";
|
|
7
|
-
import { Materializer, Upstream, filesToHost, isExactCaseFile, isPhysicalDirectory, isWorktree, listFiles, readFileText, readHostFloor, readSnapshot, resolveContainedPath } from "../src/server/index.js";
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
9
|
//#region src/bin/constants.ts
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Names the command the executable installs as.
|
|
12
12
|
*
|
|
13
13
|
* @remarks
|
|
14
14
|
* `package.json`'s `bin` field is the authority for what the command is called;
|
|
@@ -17,7 +17,7 @@ import { parseArgs } from "node:util";
|
|
|
17
17
|
*/
|
|
18
18
|
var EXECUTABLE_NAME = "scaffold";
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Lists the {@link Verb} values in usage order, frozen.
|
|
21
21
|
*
|
|
22
22
|
* @remarks
|
|
23
23
|
* The order the type declares them in, which is also the order usage lists them:
|
|
@@ -32,7 +32,7 @@ var VERBS = Object.freeze([
|
|
|
32
32
|
"overwrite"
|
|
33
33
|
]);
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* Describes what each exit code means, frozen.
|
|
36
36
|
*
|
|
37
37
|
* @remarks
|
|
38
38
|
* Keyed by the code constants rather than by literals, so the usage block
|
|
@@ -44,7 +44,7 @@ var EXIT_SUMMARY = Object.freeze({
|
|
|
44
44
|
[2]: "usage error"
|
|
45
45
|
});
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
47
|
+
* Names the machine-readable code a malformed command line reports.
|
|
48
48
|
*
|
|
49
49
|
* @remarks
|
|
50
50
|
* The executable contributes its own codes to the failure envelope, which is why
|
|
@@ -53,12 +53,12 @@ var EXIT_SUMMARY = Object.freeze({
|
|
|
53
53
|
* operation could.
|
|
54
54
|
*/
|
|
55
55
|
var USAGE_CODE = "USAGE";
|
|
56
|
-
/**
|
|
56
|
+
/** Names the machine-readable code a failure carrying no code of its own reports. */
|
|
57
57
|
var FAILED_CODE = "FAILED";
|
|
58
|
-
/**
|
|
58
|
+
/** Holds what the failure envelope says when the raised value carried no message. */
|
|
59
59
|
var FAILED_MESSAGE = "The command failed for an unrecognized reason";
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* Names the positional argument `new` alone takes, as usage writes it.
|
|
62
62
|
*
|
|
63
63
|
* @remarks
|
|
64
64
|
* The workspace name is the only positional argument any verb takes, so it is
|
|
@@ -66,7 +66,7 @@ var FAILED_MESSAGE = "The command failed for an unrecognized reason";
|
|
|
66
66
|
*/
|
|
67
67
|
var NAME_ARGUMENT = "<name>";
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* Declares every option the executable accepts, as `node:util` parses them, frozen.
|
|
70
70
|
*
|
|
71
71
|
* @remarks
|
|
72
72
|
* One table for every verb rather than one per verb, because the verb an option
|
|
@@ -95,7 +95,7 @@ var COMMAND_OPTIONS = Object.freeze({
|
|
|
95
95
|
json: Object.freeze({ type: "boolean" })
|
|
96
96
|
});
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
98
|
+
* Describes what each option does, keyed by the token usage prints, frozen.
|
|
99
99
|
*
|
|
100
100
|
* @remarks
|
|
101
101
|
* The key order is the glossary order. A key is the whole displayed token,
|
|
@@ -118,7 +118,7 @@ var OPTION_SUMMARY = Object.freeze({
|
|
|
118
118
|
ORKESTREL_SCAFFOLD_REPOSITORY: "the repository base mapped to upstream.repository.base"
|
|
119
119
|
});
|
|
120
120
|
/**
|
|
121
|
-
*
|
|
121
|
+
* Lists the options each verb takes, in usage order, frozen.
|
|
122
122
|
*
|
|
123
123
|
* @remarks
|
|
124
124
|
* The executable's half of the frozen command union: every option a branch
|
|
@@ -167,7 +167,7 @@ var VERB_OPTIONS = Object.freeze({
|
|
|
167
167
|
])
|
|
168
168
|
});
|
|
169
169
|
/**
|
|
170
|
-
*
|
|
170
|
+
* Describes what each verb does, in one line, frozen.
|
|
171
171
|
*
|
|
172
172
|
* @remarks
|
|
173
173
|
* Each line names what the verb writes, because authority is the verb's: a
|
|
@@ -183,7 +183,7 @@ var VERB_SUMMARY = Object.freeze({
|
|
|
183
183
|
//#endregion
|
|
184
184
|
//#region src/bin/errors.ts
|
|
185
185
|
/**
|
|
186
|
-
*
|
|
186
|
+
* Represents the error raised when a command line is not a command.
|
|
187
187
|
*
|
|
188
188
|
* @remarks
|
|
189
189
|
* Distinct from `ScaffoldError` because they answer different questions and
|
|
@@ -210,7 +210,7 @@ var VERB_SUMMARY = Object.freeze({
|
|
|
210
210
|
var UsageError = class extends Error {
|
|
211
211
|
code;
|
|
212
212
|
/**
|
|
213
|
-
*
|
|
213
|
+
* Constructs a usage error.
|
|
214
214
|
*
|
|
215
215
|
* @param message - What was wrong with the command line, in one sentence.
|
|
216
216
|
*/
|
|
@@ -221,10 +221,10 @@ var UsageError = class extends Error {
|
|
|
221
221
|
}
|
|
222
222
|
};
|
|
223
223
|
/**
|
|
224
|
-
*
|
|
224
|
+
* Narrows a caught value to a {@link UsageError}.
|
|
225
225
|
*
|
|
226
226
|
* @param value - The caught value to narrow.
|
|
227
|
-
* @returns
|
|
227
|
+
* @returns True if `value` is a {@link UsageError}; false otherwise.
|
|
228
228
|
*
|
|
229
229
|
* @example
|
|
230
230
|
* ```ts
|
|
@@ -240,7 +240,7 @@ function isUsageError(value) {
|
|
|
240
240
|
//#endregion
|
|
241
241
|
//#region src/bin/helpers.ts
|
|
242
242
|
/**
|
|
243
|
-
*
|
|
243
|
+
* Reads the option name out of the token usage displays it as.
|
|
244
244
|
*
|
|
245
245
|
* @param option - The displayed token, such as `--from <path>`.
|
|
246
246
|
* @returns The bare name `node:util` parses the option under.
|
|
@@ -263,7 +263,7 @@ function optionToName(option) {
|
|
|
263
263
|
return token.startsWith("--") ? token.slice(2) : token;
|
|
264
264
|
}
|
|
265
265
|
/**
|
|
266
|
-
*
|
|
266
|
+
* Projects process environment endpoints into upstream reader options.
|
|
267
267
|
*
|
|
268
268
|
* @param environment - The process environment to read.
|
|
269
269
|
* @returns The configured endpoint groups, or `undefined` when neither endpoint
|
|
@@ -279,7 +279,7 @@ function environmentToUpstream(environment) {
|
|
|
279
279
|
};
|
|
280
280
|
}
|
|
281
281
|
/**
|
|
282
|
-
*
|
|
282
|
+
* Renders one verb's synopsis.
|
|
283
283
|
*
|
|
284
284
|
* @param verb - The verb to describe.
|
|
285
285
|
* @returns The command line this verb accepts, with every option bracketed.
|
|
@@ -300,7 +300,7 @@ function verbToSyntax(verb) {
|
|
|
300
300
|
return `${EXECUTABLE_NAME} ${verb}${verb === "new" ? ` ${NAME_ARGUMENT}` : ""} ${VERB_OPTIONS[verb].map((option) => `[${option}]`).join(" ")}`;
|
|
301
301
|
}
|
|
302
302
|
/**
|
|
303
|
-
*
|
|
303
|
+
* Renders the whole command reference.
|
|
304
304
|
*
|
|
305
305
|
* @returns One line per output call: the synopsis, every verb, the option glossary, and the exit codes.
|
|
306
306
|
*
|
|
@@ -327,7 +327,7 @@ function renderUsage() {
|
|
|
327
327
|
];
|
|
328
328
|
}
|
|
329
329
|
/**
|
|
330
|
-
*
|
|
330
|
+
* Reads one command out of the arguments following the executable's own name.
|
|
331
331
|
*
|
|
332
332
|
* @param argv - The arguments the executable was given.
|
|
333
333
|
* @returns The command the arguments denote.
|
|
@@ -364,7 +364,7 @@ function argvToCommand(argv) {
|
|
|
364
364
|
}));
|
|
365
365
|
if (!parsed.success) {
|
|
366
366
|
const cause = parsed.error;
|
|
367
|
-
throw new UsageError(cause
|
|
367
|
+
throw new UsageError(isError(cause) ? cause.message : `Could not read the arguments to '${verb}'.`);
|
|
368
368
|
}
|
|
369
369
|
const { positionals, values } = parsed.value;
|
|
370
370
|
const accepted = VERB_OPTIONS[verb].map((option) => optionToName(option));
|
|
@@ -436,7 +436,7 @@ function argvToCommand(argv) {
|
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
438
|
/**
|
|
439
|
-
*
|
|
439
|
+
* Reads the exit code an audit reports.
|
|
440
440
|
*
|
|
441
441
|
* @param audit - The comparison of a plan against a target.
|
|
442
442
|
* @returns `EXIT_CLEAN` when the target matched the plan, `EXIT_DRIFT` otherwise.
|
|
@@ -463,7 +463,7 @@ function auditToExit(audit) {
|
|
|
463
463
|
return blocked || drifted ? 1 : 0;
|
|
464
464
|
}
|
|
465
465
|
/**
|
|
466
|
-
*
|
|
466
|
+
* Reads the runtime and development rows a writing verb may raise.
|
|
467
467
|
*
|
|
468
468
|
* @param manifest - The target manifest text.
|
|
469
469
|
* @param blueprint - The workspace shape that supplies the planned tool set.
|
|
@@ -502,7 +502,7 @@ function manifestToWritableDependencies(manifest, blueprint) {
|
|
|
502
502
|
};
|
|
503
503
|
}
|
|
504
504
|
/**
|
|
505
|
-
*
|
|
505
|
+
* Keeps only dependencies published by the Orkestrel fleet.
|
|
506
506
|
*
|
|
507
507
|
* @param dependencies - The declared dependencies to inspect.
|
|
508
508
|
* @returns The fleet declarations in their original order.
|
|
@@ -511,7 +511,7 @@ function dependenciesToFleet(dependencies) {
|
|
|
511
511
|
return dependencies.filter((dependency) => dependency.name.startsWith("@orkestrel/"));
|
|
512
512
|
}
|
|
513
513
|
/**
|
|
514
|
-
*
|
|
514
|
+
* Projects declared concrete ranges to their distributed release floors.
|
|
515
515
|
*
|
|
516
516
|
* @param dependencies - The declarations whose floor versions to read.
|
|
517
517
|
* @returns One found floor per declaration, or `undefined` when any range names
|
|
@@ -531,7 +531,7 @@ function dependenciesToFloors(dependencies) {
|
|
|
531
531
|
return releases;
|
|
532
532
|
}
|
|
533
533
|
/**
|
|
534
|
-
*
|
|
534
|
+
* Reads the exit code registry release evidence earns.
|
|
535
535
|
*
|
|
536
536
|
* @param releases - The release verdicts to measure.
|
|
537
537
|
* @returns `EXIT_DRIFT` for a failed lookup or an exact fleet mismatch; `EXIT_CLEAN` otherwise.
|
|
@@ -540,7 +540,7 @@ function releasesToExit(releases) {
|
|
|
540
540
|
return releases.some((release) => release.lookup !== "found" || release.name.startsWith("@orkestrel/") && release.range !== `^${release.latest}`) ? 1 : 0;
|
|
541
541
|
}
|
|
542
542
|
/**
|
|
543
|
-
*
|
|
543
|
+
* Projects foreign floor and supported-major drift into audit questions.
|
|
544
544
|
*
|
|
545
545
|
* @param releases - The release verdicts to measure.
|
|
546
546
|
* @param served - Alternate release verdicts used to detect a newer served major.
|
|
@@ -570,7 +570,7 @@ function releasesToQuestions(releases, served = releases) {
|
|
|
570
570
|
return questions;
|
|
571
571
|
}
|
|
572
572
|
/**
|
|
573
|
-
*
|
|
573
|
+
* Projects an audit into the human summary of its outcome.
|
|
574
574
|
*
|
|
575
575
|
* @param audit - The comparison to summarize.
|
|
576
576
|
* @returns The refusal, or the planned-path outcome, its grounds, and any foreign-path count.
|
|
@@ -607,7 +607,7 @@ function auditToSummary(audit) {
|
|
|
607
607
|
return foreign === 0 ? summary : `${summary} The plan does not own ${String(foreign)} further path${foreign === 1 ? "" : "s"} beneath its groups.`;
|
|
608
608
|
}
|
|
609
609
|
/**
|
|
610
|
-
*
|
|
610
|
+
* Projects a raised value into the machine-readable failure envelope.
|
|
611
611
|
*
|
|
612
612
|
* @param error - The value a command raised.
|
|
613
613
|
* @returns The envelope naming the coded reason and what went wrong.
|
|
@@ -633,13 +633,556 @@ function errorToEnvelope(error) {
|
|
|
633
633
|
} };
|
|
634
634
|
return { error: {
|
|
635
635
|
code: FAILED_CODE,
|
|
636
|
-
message: error
|
|
636
|
+
message: isError(error) ? error.message : FAILED_MESSAGE
|
|
637
637
|
} };
|
|
638
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* Writes one report line to the process output stream.
|
|
641
|
+
*
|
|
642
|
+
* @param line - The already-sanitized line to write.
|
|
643
|
+
*
|
|
644
|
+
* @remarks
|
|
645
|
+
* The destination a terminal caller means for the report. It is the executable's
|
|
646
|
+
* default `output` handler and the only place this package names
|
|
647
|
+
* `process.stdout`, so a caller driving the executable from inside another
|
|
648
|
+
* process replaces the whole write path by supplying its own handler.
|
|
649
|
+
*
|
|
650
|
+
* @example
|
|
651
|
+
* ```ts
|
|
652
|
+
* import { writeOutput } from './helpers.js'
|
|
653
|
+
*
|
|
654
|
+
* writeOutput('0 written, 0 unchanged, 0 removed in ./target.')
|
|
655
|
+
* ```
|
|
656
|
+
*/
|
|
657
|
+
function writeOutput(line) {
|
|
658
|
+
process.stdout.write(`${line}\n`);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Writes one diagnostic line to the process error stream.
|
|
662
|
+
*
|
|
663
|
+
* @param line - The already-sanitized line to write.
|
|
664
|
+
*
|
|
665
|
+
* @remarks
|
|
666
|
+
* The destination a terminal caller means for everything that must stay off the
|
|
667
|
+
* report, so a piped machine-readable value is never polluted by a warning. It
|
|
668
|
+
* is the executable's default `diagnostic` handler and the only place this
|
|
669
|
+
* package names `process.stderr`.
|
|
670
|
+
*
|
|
671
|
+
* @example
|
|
672
|
+
* ```ts
|
|
673
|
+
* import { writeDiagnostic } from './helpers.js'
|
|
674
|
+
*
|
|
675
|
+
* writeDiagnostic('FAILED: the target is not a git repository.')
|
|
676
|
+
* ```
|
|
677
|
+
*/
|
|
678
|
+
function writeDiagnostic(line) {
|
|
679
|
+
process.stderr.write(`${line}\n`);
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Strips one line of everything a terminal would act on rather than print.
|
|
683
|
+
*
|
|
684
|
+
* @param line - The line to sanitize.
|
|
685
|
+
* @returns The line with ANSI escapes and control characters removed and every
|
|
686
|
+
* break folded to a space.
|
|
687
|
+
*
|
|
688
|
+
* @remarks
|
|
689
|
+
* The single write path's cleaner, and the only place hostile bytes are answered
|
|
690
|
+
* for. A refusal quotes the argument that caused it, so an escape sequence, a
|
|
691
|
+
* bell, or a forged second line arrives here inside otherwise ordinary prose.
|
|
692
|
+
* ANSI escapes go first because they are what repaints a terminal, control
|
|
693
|
+
* characters next, and what remains is folded onto one line because a handler
|
|
694
|
+
* takes one line and a caller writing a record per call must get one record.
|
|
695
|
+
* A lone carriage return is folded as a break too, because a terminal repaints
|
|
696
|
+
* the line already printed on one. The fold is a replacement rather than a line
|
|
697
|
+
* split, so no bare `\r` is read as a line terminator here.
|
|
698
|
+
*
|
|
699
|
+
* @example
|
|
700
|
+
* ```ts
|
|
701
|
+
* import { sanitizeLine } from './helpers.js'
|
|
702
|
+
*
|
|
703
|
+
* sanitizeLine('first\nsecond') // 'first second'
|
|
704
|
+
* ```
|
|
705
|
+
*/
|
|
706
|
+
function sanitizeLine(line) {
|
|
707
|
+
return stripControls(strip(line)).replace(/\r\n|\r|\n/gu, " ");
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Projects an incomplete version resolution into the refusal it earns.
|
|
711
|
+
*
|
|
712
|
+
* @param versions - The resolution to measure.
|
|
713
|
+
* @returns The refusal to throw, or `undefined` when the resolution is complete.
|
|
714
|
+
*
|
|
715
|
+
* @remarks
|
|
716
|
+
* Read before a caller opens a write, so a run that could not name every floor
|
|
717
|
+
* refuses instead of writing a partial pin set.
|
|
718
|
+
*
|
|
719
|
+
* @example
|
|
720
|
+
* ```ts
|
|
721
|
+
* import { versionsToRefusal } from './helpers.js'
|
|
722
|
+
*
|
|
723
|
+
* versionsToRefusal({ releases: [], pins: { runtime: [], development: [] }, forced: false, complete: true })
|
|
724
|
+
* // undefined
|
|
725
|
+
* ```
|
|
726
|
+
*/
|
|
727
|
+
function versionsToRefusal(versions) {
|
|
728
|
+
if (versions.complete) return void 0;
|
|
729
|
+
const names = versions.releases.filter((release) => release.lookup !== "found").map((release) => release.name);
|
|
730
|
+
return new ScaffoldError("FETCH", names.length === 0 ? "A declared dependency names no concrete floor." : `The registry named no release for ${names.join(", ")}.`, { names: names.length });
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Projects an incomplete catalog fetch into the refusal it earns.
|
|
734
|
+
*
|
|
735
|
+
* @param entries - The catalog rows the fetch produced.
|
|
736
|
+
* @param mirrors - The guide mirrors the fetch produced.
|
|
737
|
+
* @returns The refusal to throw, or `undefined` when every row answered.
|
|
738
|
+
*
|
|
739
|
+
* @remarks
|
|
740
|
+
* A catalog transaction starts only after every packument answered. A mirror the
|
|
741
|
+
* host could not serve — a failed read or an absent guide, which is what a
|
|
742
|
+
* published package with a private repository answers with — is skipped and
|
|
743
|
+
* reported rather than refusing every other write, because one unreachable
|
|
744
|
+
* package never costs the caller the rest of the fetch.
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* ```ts
|
|
748
|
+
* import { fetchToRefusal } from './helpers.js'
|
|
749
|
+
*
|
|
750
|
+
* fetchToRefusal([], []) // undefined
|
|
751
|
+
* ```
|
|
752
|
+
*/
|
|
753
|
+
function fetchToRefusal(entries, mirrors) {
|
|
754
|
+
const failed = [...entries.filter((entry) => entry.lookup !== "found").map((entry) => entry.name), ...mirrors.filter((mirror) => mirror.lookup !== "found" && mirror.lookup !== "failed" && mirror.lookup !== "missing").map((mirror) => mirror.name)];
|
|
755
|
+
if (failed.length === 0) return void 0;
|
|
756
|
+
return new ScaffoldError("FETCH", `Upstream produced no complete catalog answer for ${failed.join(", ")}.`, { names: failed.length });
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Projects the catalog packuments already read into declared release evidence.
|
|
760
|
+
*
|
|
761
|
+
* @param declared - The declared dependencies to answer for, in declaration order.
|
|
762
|
+
* @param entries - The catalog rows the fetch produced.
|
|
763
|
+
* @returns One release verdict per declaration, in input order.
|
|
764
|
+
*
|
|
765
|
+
* @remarks
|
|
766
|
+
* The catalog verb already holds every fleet packument, so a declared range is
|
|
767
|
+
* measured against what that read returned rather than against a second request.
|
|
768
|
+
*
|
|
769
|
+
* @example
|
|
770
|
+
* ```ts
|
|
771
|
+
* import { entriesToReleases } from './helpers.js'
|
|
772
|
+
*
|
|
773
|
+
* entriesToReleases([{ name: '@orkestrel/emitter', range: '^0.0.5' }], [])
|
|
774
|
+
* // [{ name: '@orkestrel/emitter', range: '^0.0.5', lookup: 'missing', note: '…' }]
|
|
775
|
+
* ```
|
|
776
|
+
*/
|
|
777
|
+
function entriesToReleases(declared, entries) {
|
|
778
|
+
return declared.map((dependency) => {
|
|
779
|
+
const entry = entries.find((candidate) => candidate.name === dependency.name);
|
|
780
|
+
if (entry?.lookup === "found") return {
|
|
781
|
+
...dependency,
|
|
782
|
+
lookup: "found",
|
|
783
|
+
latest: entry.version
|
|
784
|
+
};
|
|
785
|
+
return {
|
|
786
|
+
...dependency,
|
|
787
|
+
lookup: entry?.lookup ?? "missing",
|
|
788
|
+
note: entry?.note ?? "the organization catalog does not list the declared package"
|
|
789
|
+
};
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Projects release evidence into the complete pin set a write may apply.
|
|
794
|
+
*
|
|
795
|
+
* @param releases - The release verdicts, one per declared dependency, in the
|
|
796
|
+
* order the declarations were looked up.
|
|
797
|
+
* @param declared - The runtime and development declarations the verdicts answer for.
|
|
798
|
+
* @returns The runtime and development pins, each at the caret of its release.
|
|
799
|
+
* @throws `ScaffoldError('FETCH', …)` when a verdict named no release, when a
|
|
800
|
+
* verdict has no matching declaration, or when the two sets differ in length.
|
|
801
|
+
*
|
|
802
|
+
* @remarks
|
|
803
|
+
* All or nothing: a caller never writes a partial pin set, because a manifest
|
|
804
|
+
* carrying some raised ranges and some stale ones is harder to recover from than
|
|
805
|
+
* one that was not written at all.
|
|
806
|
+
*
|
|
807
|
+
* @example
|
|
808
|
+
* ```ts
|
|
809
|
+
* import { releasesToPins } from './helpers.js'
|
|
810
|
+
*
|
|
811
|
+
* releasesToPins(
|
|
812
|
+
* [{ name: '@orkestrel/emitter', range: '^0.0.5', lookup: 'found', latest: '0.0.6' }],
|
|
813
|
+
* { runtime: [{ name: '@orkestrel/emitter', range: '^0.0.5' }], development: [] },
|
|
814
|
+
* ) // { runtime: [{ name: '@orkestrel/emitter', range: '^0.0.6' }], development: [] }
|
|
815
|
+
* ```
|
|
816
|
+
*/
|
|
817
|
+
function releasesToPins(releases, declared) {
|
|
818
|
+
const refused = releases.filter((release) => release.lookup !== "found");
|
|
819
|
+
if (refused.length > 0) throw new ScaffoldError("FETCH", `The registry named no release for ${refused.map((release) => release.name).join(", ")}.`, { names: refused.length });
|
|
820
|
+
const dependencies = [...declared.runtime, ...declared.development];
|
|
821
|
+
const pins = [];
|
|
822
|
+
for (let index = 0; index < releases.length; index += 1) {
|
|
823
|
+
const release = releases[index];
|
|
824
|
+
const dependency = dependencies[index];
|
|
825
|
+
if (release === void 0 || dependency === void 0) throw new ScaffoldError("FETCH", "The release answer has no matching declaration.");
|
|
826
|
+
if (release.lookup !== "found") throw new ScaffoldError("FETCH", `The registry named no release for ${release.name}.`);
|
|
827
|
+
pins.push({
|
|
828
|
+
name: dependency.name,
|
|
829
|
+
range: `^${release.latest}`
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
if (pins.length !== dependencies.length) throw new ScaffoldError("FETCH", "The release answer does not match the declaration set.");
|
|
833
|
+
return {
|
|
834
|
+
runtime: pins.slice(0, declared.runtime.length),
|
|
835
|
+
development: pins.slice(declared.runtime.length)
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Reads the literal Vitest projects and npm run scripts one shell command invokes.
|
|
840
|
+
*
|
|
841
|
+
* @param script - The manifest script text to read.
|
|
842
|
+
* @returns The invoked projects and scripts, or `undefined` when the command
|
|
843
|
+
* cannot be read literally.
|
|
844
|
+
*
|
|
845
|
+
* @remarks
|
|
846
|
+
* Quotes group a token but do not hide the option, while shell expansions make
|
|
847
|
+
* its value unresolved and therefore refuse the write that asked the question.
|
|
848
|
+
* An unterminated quote, a trailing escape, and an unresolved `--project` value
|
|
849
|
+
* all answer `undefined` rather than a partial reading.
|
|
850
|
+
*
|
|
851
|
+
* @example
|
|
852
|
+
* ```ts
|
|
853
|
+
* import { scriptToInvocations } from './helpers.js'
|
|
854
|
+
*
|
|
855
|
+
* scriptToInvocations('vitest run --project src:core')
|
|
856
|
+
* // { projects: ['src:core'], scripts: [] }
|
|
857
|
+
* ```
|
|
858
|
+
*/
|
|
859
|
+
function scriptToInvocations(script) {
|
|
860
|
+
const tokens = [];
|
|
861
|
+
let value = "";
|
|
862
|
+
let resolved = true;
|
|
863
|
+
let started = false;
|
|
864
|
+
let quote;
|
|
865
|
+
for (let index = 0; index < script.length; index += 1) {
|
|
866
|
+
const character = script[index];
|
|
867
|
+
if (character === void 0) return void 0;
|
|
868
|
+
if (quote === void 0) {
|
|
869
|
+
if (/\s/.test(character)) {
|
|
870
|
+
if (started) tokens.push({
|
|
871
|
+
value,
|
|
872
|
+
resolved
|
|
873
|
+
});
|
|
874
|
+
value = "";
|
|
875
|
+
resolved = true;
|
|
876
|
+
started = false;
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
if (";&|()".includes(character)) {
|
|
880
|
+
if (started) tokens.push({
|
|
881
|
+
value,
|
|
882
|
+
resolved
|
|
883
|
+
});
|
|
884
|
+
const paired = script[index + 1] === character && (character === "&" || character === "|");
|
|
885
|
+
tokens.push({
|
|
886
|
+
value: paired ? `${character}${character}` : character,
|
|
887
|
+
resolved: true
|
|
888
|
+
});
|
|
889
|
+
value = "";
|
|
890
|
+
resolved = true;
|
|
891
|
+
started = false;
|
|
892
|
+
if (paired) index += 1;
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
if (character === "\"" || character === "'") {
|
|
896
|
+
quote = character;
|
|
897
|
+
started = true;
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
if (character === "\\") {
|
|
901
|
+
const escaped = script[index + 1];
|
|
902
|
+
if (escaped === void 0) return void 0;
|
|
903
|
+
value += escaped;
|
|
904
|
+
started = true;
|
|
905
|
+
index += 1;
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
if (character === "$" || character === "`" || character === "%") resolved = false;
|
|
909
|
+
value += character;
|
|
910
|
+
started = true;
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
if (character === quote) {
|
|
914
|
+
quote = void 0;
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
if (character === "\\" && quote === "\"") {
|
|
918
|
+
const escaped = script[index + 1];
|
|
919
|
+
if (escaped === void 0) return void 0;
|
|
920
|
+
value += escaped;
|
|
921
|
+
index += 1;
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
if (quote === "\"" && (character === "$" || character === "`" || character === "%")) resolved = false;
|
|
925
|
+
value += character;
|
|
926
|
+
started = true;
|
|
927
|
+
}
|
|
928
|
+
if (quote !== void 0) return void 0;
|
|
929
|
+
if (started) tokens.push({
|
|
930
|
+
value,
|
|
931
|
+
resolved
|
|
932
|
+
});
|
|
933
|
+
const projects = [];
|
|
934
|
+
const scripts = [];
|
|
935
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
936
|
+
const token = tokens[index];
|
|
937
|
+
if (token === void 0) return void 0;
|
|
938
|
+
if (token.value === "npm" && tokens[index + 1]?.value === "run") {
|
|
939
|
+
const name = tokens[index + 2];
|
|
940
|
+
if (name === void 0 || !name.resolved || name.value.length === 0 || [
|
|
941
|
+
"&&",
|
|
942
|
+
"||",
|
|
943
|
+
";",
|
|
944
|
+
"|",
|
|
945
|
+
"&",
|
|
946
|
+
"(",
|
|
947
|
+
")"
|
|
948
|
+
].includes(name.value)) return void 0;
|
|
949
|
+
scripts.push(name.value);
|
|
950
|
+
index += 2;
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
if (token.value === "--project") {
|
|
954
|
+
const project = tokens[index + 1];
|
|
955
|
+
if (project === void 0 || !project.resolved || project.value.length === 0 || [
|
|
956
|
+
"&&",
|
|
957
|
+
"||",
|
|
958
|
+
";",
|
|
959
|
+
"|",
|
|
960
|
+
"&",
|
|
961
|
+
"(",
|
|
962
|
+
")"
|
|
963
|
+
].includes(project.value)) return void 0;
|
|
964
|
+
projects.push(project.value);
|
|
965
|
+
index += 1;
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
if (token.value.startsWith("--project=")) {
|
|
969
|
+
const project = token.value.slice(10);
|
|
970
|
+
if (!token.resolved || project.length === 0) return void 0;
|
|
971
|
+
projects.push(project);
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (!token.resolved && token.value.includes("--project")) return void 0;
|
|
975
|
+
}
|
|
976
|
+
return {
|
|
977
|
+
projects,
|
|
978
|
+
scripts
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Reads the environments one axis of a target physically ships.
|
|
983
|
+
*
|
|
984
|
+
* @param target - The target directory to read.
|
|
985
|
+
* @param axis - The axis directory, `src` or `app`.
|
|
986
|
+
* @returns The environments that axis holds as directories, in declared order.
|
|
987
|
+
*
|
|
988
|
+
* @remarks
|
|
989
|
+
* Read as directories rather than declared, because a directory is the fact and
|
|
990
|
+
* a declaration would be a second copy of it free to disagree.
|
|
991
|
+
*
|
|
992
|
+
* @example
|
|
993
|
+
* ```ts
|
|
994
|
+
* import { targetToEnvironments } from './helpers.js'
|
|
995
|
+
*
|
|
996
|
+
* targetToEnvironments('./packages/router', 'src') // ['core', 'server']
|
|
997
|
+
* ```
|
|
998
|
+
*/
|
|
999
|
+
function targetToEnvironments(target, axis) {
|
|
1000
|
+
return ENVIRONMENTS.filter((environment) => {
|
|
1001
|
+
const full = resolveContainedPath(target, `${axis}/${environment}`);
|
|
1002
|
+
return full !== void 0 && isPhysicalDirectory(full);
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Reads the packages a target's catalog table listed before this run.
|
|
1007
|
+
*
|
|
1008
|
+
* @param target - The target directory holding the catalog agent file.
|
|
1009
|
+
* @returns The package names the table's first column carries, in table order,
|
|
1010
|
+
* and no names when the file is absent.
|
|
1011
|
+
*
|
|
1012
|
+
* @remarks
|
|
1013
|
+
* Read through the declared markdown parser rather than by pattern, so a row is
|
|
1014
|
+
* a row because the document says so.
|
|
1015
|
+
*
|
|
1016
|
+
* @example
|
|
1017
|
+
* ```ts
|
|
1018
|
+
* import { catalogToNames } from './helpers.js'
|
|
1019
|
+
*
|
|
1020
|
+
* catalogToNames('./packages/router') // ['@orkestrel/contract', '@orkestrel/emitter']
|
|
1021
|
+
* ```
|
|
1022
|
+
*/
|
|
1023
|
+
function catalogToNames(target) {
|
|
1024
|
+
const text = readFileText(target, CATALOG_AGENT_PATH);
|
|
1025
|
+
if (text === void 0) return [];
|
|
1026
|
+
const names = [];
|
|
1027
|
+
for (const table of createMarkdown(text).filter(isTableNode)) for (const row of table.rows) {
|
|
1028
|
+
const [cell] = row;
|
|
1029
|
+
if (cell === void 0) continue;
|
|
1030
|
+
const name = cell.map(flattenText).join("").trim();
|
|
1031
|
+
if (DEPENDENCY_NAME_PATTERN.test(name)) names.push(name);
|
|
1032
|
+
}
|
|
1033
|
+
return names;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Reads one git query as its NUL-separated records.
|
|
1037
|
+
*
|
|
1038
|
+
* @param target - The repository the query runs in.
|
|
1039
|
+
* @param args - The git arguments, without the program name.
|
|
1040
|
+
* @returns The non-empty records git wrote, in git's own order.
|
|
1041
|
+
* @throws `ScaffoldError('TARGET', …)` when the query fails, which is what a
|
|
1042
|
+
* directory that is not a git repository answers with.
|
|
1043
|
+
*
|
|
1044
|
+
* @remarks
|
|
1045
|
+
* Git is asked rather than reimplemented, because the tracked set and the dirty
|
|
1046
|
+
* set are git's own answers and nothing else can give them. `executeSync`
|
|
1047
|
+
* resolves the bare `git` name against `PATH` and `PATHEXT` on Windows, never
|
|
1048
|
+
* through a shell, so the query runs without an extension of its own. A failed
|
|
1049
|
+
* run resolves instead of throwing there, so git's own refusal is buffered into
|
|
1050
|
+
* the result rather than written onto a stream nobody chose.
|
|
1051
|
+
*
|
|
1052
|
+
* @example
|
|
1053
|
+
* ```ts
|
|
1054
|
+
* import { readGitRecords } from './helpers.js'
|
|
1055
|
+
*
|
|
1056
|
+
* readGitRecords('./packages/router', ['ls-files', '-z']) // ['package.json', 'src/core/index.ts']
|
|
1057
|
+
* ```
|
|
1058
|
+
*/
|
|
1059
|
+
function readGitRecords(target, args) {
|
|
1060
|
+
const result = executeSync({
|
|
1061
|
+
file: "git",
|
|
1062
|
+
arguments: [...args]
|
|
1063
|
+
}, {
|
|
1064
|
+
workspace: target,
|
|
1065
|
+
limit: MAX_MANIFEST_BYTES,
|
|
1066
|
+
strict: false
|
|
1067
|
+
});
|
|
1068
|
+
if (result.failed) throw new ScaffoldError("TARGET", `The target at ${target} is not a git repository.`, { target });
|
|
1069
|
+
return result.stdout.split("\0").filter((record) => record.length > 0);
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Reads the environments a comma-separated selection names.
|
|
1073
|
+
*
|
|
1074
|
+
* @param selection - The selection text, or `undefined` for no selection.
|
|
1075
|
+
* @param axis - The axis the option configures, quoted in a refusal.
|
|
1076
|
+
* @returns The named environments in declared order, and none for no selection.
|
|
1077
|
+
* @throws `UsageError` when the selection names something that is not an environment.
|
|
1078
|
+
*
|
|
1079
|
+
* @example
|
|
1080
|
+
* ```ts
|
|
1081
|
+
* import { selectionToEnvironments } from './helpers.js'
|
|
1082
|
+
*
|
|
1083
|
+
* selectionToEnvironments('server,core', 'src') // ['core', 'server']
|
|
1084
|
+
* ```
|
|
1085
|
+
*/
|
|
1086
|
+
function selectionToEnvironments(selection, axis) {
|
|
1087
|
+
if (selection === void 0) return [];
|
|
1088
|
+
const requested = selection.split(",");
|
|
1089
|
+
const refused = requested.filter((name) => !ENVIRONMENTS.some((environment) => environment === name));
|
|
1090
|
+
if (refused.length > 0) throw new UsageError(`'--${axis}' does not take ${refused.join(", ")}. It takes ${ENVIRONMENTS.join(", ")}.`);
|
|
1091
|
+
return ENVIRONMENTS.filter((environment) => requested.includes(environment));
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Reads the artifact groups a comma-separated selection names.
|
|
1095
|
+
*
|
|
1096
|
+
* @param selection - The selection text, or `undefined` for no selection.
|
|
1097
|
+
* @returns The named groups in plan order, and `undefined` for no selection,
|
|
1098
|
+
* which covers every group.
|
|
1099
|
+
* @throws `UsageError` when the selection names something that is not a group.
|
|
1100
|
+
*
|
|
1101
|
+
* @example
|
|
1102
|
+
* ```ts
|
|
1103
|
+
* import { selectionToGroups } from './helpers.js'
|
|
1104
|
+
*
|
|
1105
|
+
* selectionToGroups('tests,manifest') // ['manifest', 'tests']
|
|
1106
|
+
* ```
|
|
1107
|
+
*/
|
|
1108
|
+
function selectionToGroups(selection) {
|
|
1109
|
+
if (selection === void 0) return void 0;
|
|
1110
|
+
const requested = selection.split(",");
|
|
1111
|
+
const refused = requested.filter((name) => !GROUPS.some((group) => group === name));
|
|
1112
|
+
if (refused.length > 0) throw new UsageError(`'--groups' does not take ${refused.join(", ")}. It takes ${GROUPS.join(", ")}.`);
|
|
1113
|
+
return GROUPS.filter((group) => requested.includes(group));
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Reads the fleet packages a comma-separated selection names.
|
|
1117
|
+
*
|
|
1118
|
+
* @param selection - The selection text, or `undefined` for no selection.
|
|
1119
|
+
* @returns The named packages in selection order, and none for no selection.
|
|
1120
|
+
* @throws `UsageError` when a name is not a published `@orkestrel` package name.
|
|
1121
|
+
*
|
|
1122
|
+
* @example
|
|
1123
|
+
* ```ts
|
|
1124
|
+
* import { selectionToPackages } from './helpers.js'
|
|
1125
|
+
*
|
|
1126
|
+
* selectionToPackages('@orkestrel/emitter') // ['@orkestrel/emitter']
|
|
1127
|
+
* ```
|
|
1128
|
+
*/
|
|
1129
|
+
function selectionToPackages(selection) {
|
|
1130
|
+
if (selection === void 0) return [];
|
|
1131
|
+
const requested = selection.split(",");
|
|
1132
|
+
const refused = requested.filter((name) => !DEPENDENCY_NAME_PATTERN.test(name));
|
|
1133
|
+
if (refused.length > 0) throw new UsageError(`'--deps' does not take ${refused.join(", ")}. Every name is a published @orkestrel package.`);
|
|
1134
|
+
return requested;
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Merges the results of two mutations of one target into one result.
|
|
1138
|
+
*
|
|
1139
|
+
* @param first - The earlier result, which fixes the reported target.
|
|
1140
|
+
* @param second - The later result.
|
|
1141
|
+
* @returns One result carrying both path lists, first's paths ahead of second's.
|
|
1142
|
+
*
|
|
1143
|
+
* @remarks
|
|
1144
|
+
* Written and skipped never overlap across the calls a verb makes, because each
|
|
1145
|
+
* call answers for its own paths.
|
|
1146
|
+
*
|
|
1147
|
+
* @example
|
|
1148
|
+
* ```ts
|
|
1149
|
+
* import { mergeResults } from './helpers.js'
|
|
1150
|
+
*
|
|
1151
|
+
* mergeResults(
|
|
1152
|
+
* { target: './t', written: ['a'], skipped: [], removed: [] },
|
|
1153
|
+
* { target: './t', written: ['b'], skipped: [], removed: [] },
|
|
1154
|
+
* ) // { target: './t', written: ['a', 'b'], skipped: [], removed: [] }
|
|
1155
|
+
* ```
|
|
1156
|
+
*/
|
|
1157
|
+
function mergeResults(first, second) {
|
|
1158
|
+
return {
|
|
1159
|
+
target: first.target,
|
|
1160
|
+
written: [...first.written, ...second.written],
|
|
1161
|
+
skipped: [...first.skipped, ...second.skipped],
|
|
1162
|
+
removed: [...first.removed, ...second.removed]
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Projects one mutation result into the line that states what it did.
|
|
1167
|
+
*
|
|
1168
|
+
* @param result - The result to state.
|
|
1169
|
+
* @returns One line naming the written, unchanged, and removed tallies and the target.
|
|
1170
|
+
*
|
|
1171
|
+
* @example
|
|
1172
|
+
* ```ts
|
|
1173
|
+
* import { resultToTally } from './helpers.js'
|
|
1174
|
+
*
|
|
1175
|
+
* resultToTally({ target: './t', written: ['a'], skipped: [], removed: [] })
|
|
1176
|
+
* // '1 written, 0 unchanged, 0 removed in ./t.'
|
|
1177
|
+
* ```
|
|
1178
|
+
*/
|
|
1179
|
+
function resultToTally(result) {
|
|
1180
|
+
return `${String(result.written.length)} written, ${String(result.skipped.length)} unchanged, ${String(result.removed.length)} removed in ${result.target}.`;
|
|
1181
|
+
}
|
|
639
1182
|
//#endregion
|
|
640
1183
|
//#region src/bin/CLI.ts
|
|
641
1184
|
/**
|
|
642
|
-
*
|
|
1185
|
+
* Represents the executable: one command line in, one exit code out.
|
|
643
1186
|
*
|
|
644
1187
|
* @remarks
|
|
645
1188
|
* Every destination this class writes to is a handler it was given, and the run
|
|
@@ -672,25 +1215,23 @@ function errorToEnvelope(error) {
|
|
|
672
1215
|
* code // 0
|
|
673
1216
|
* ```
|
|
674
1217
|
*/
|
|
675
|
-
var CLI = class
|
|
676
|
-
static #stdout = (line) => void process.stdout.write(`${line}\n`);
|
|
677
|
-
static #stderr = (line) => void process.stderr.write(`${line}\n`);
|
|
1218
|
+
var CLI = class {
|
|
678
1219
|
#output;
|
|
679
1220
|
#diagnostic;
|
|
680
1221
|
#upstream;
|
|
681
1222
|
/**
|
|
682
|
-
*
|
|
1223
|
+
* Constructs the executable over the destinations it writes to.
|
|
683
1224
|
*
|
|
684
1225
|
* @param options - The report and diagnostic handlers and the upstream
|
|
685
1226
|
* endpoints; the process streams and the published endpoints when absent.
|
|
686
1227
|
*/
|
|
687
1228
|
constructor(options) {
|
|
688
|
-
this.#output = options?.output ??
|
|
689
|
-
this.#diagnostic = options?.diagnostic ??
|
|
1229
|
+
this.#output = options?.output ?? writeOutput;
|
|
1230
|
+
this.#diagnostic = options?.diagnostic ?? writeDiagnostic;
|
|
690
1231
|
this.#upstream = options?.upstream;
|
|
691
1232
|
}
|
|
692
1233
|
/**
|
|
693
|
-
*
|
|
1234
|
+
* Runs one command line to completion and reports through the configured output.
|
|
694
1235
|
*
|
|
695
1236
|
* @param argv - The arguments following the executable's own name.
|
|
696
1237
|
* @returns The exit code: `0` clean, `1` drift or failure, `2` a usage error.
|
|
@@ -734,11 +1275,11 @@ var CLI = class CLI {
|
|
|
734
1275
|
async #create(command) {
|
|
735
1276
|
const target = command.target ?? command.name;
|
|
736
1277
|
const blueprint = createBlueprint(command.name, {
|
|
737
|
-
src:
|
|
738
|
-
app:
|
|
1278
|
+
src: selectionToEnvironments(command.src, "src"),
|
|
1279
|
+
app: selectionToEnvironments(command.app, "app"),
|
|
739
1280
|
bin: command.bin === true,
|
|
740
1281
|
setup: false,
|
|
741
|
-
dependencies:
|
|
1282
|
+
dependencies: selectionToPackages(command.dependencies).map((name) => ({
|
|
742
1283
|
name,
|
|
743
1284
|
range: "^0.0.0"
|
|
744
1285
|
}))
|
|
@@ -751,7 +1292,8 @@ var CLI = class CLI {
|
|
|
751
1292
|
runtime: dependenciesToFleet(declarations.runtime),
|
|
752
1293
|
development: dependenciesToFleet(declarations.development)
|
|
753
1294
|
}, command.offline === true);
|
|
754
|
-
|
|
1295
|
+
const refusal = versionsToRefusal(versions);
|
|
1296
|
+
if (refusal !== void 0) throw refusal;
|
|
755
1297
|
const resolved = replacePlanRanges(plan, versions.pins);
|
|
756
1298
|
if (resolved === void 0) throw new ScaffoldError("BLOCKED", "The compiled plan ranges could not be replaced.");
|
|
757
1299
|
const host = await this.#host(command.from, void 0, command.offline === true);
|
|
@@ -767,7 +1309,7 @@ var CLI = class CLI {
|
|
|
767
1309
|
if (command.json === true) this.#report(outcome);
|
|
768
1310
|
else {
|
|
769
1311
|
this.#say(`Scaffolded ${blueprint.name} into ${result.target}.`);
|
|
770
|
-
this.#say(
|
|
1312
|
+
this.#say(resultToTally(result));
|
|
771
1313
|
}
|
|
772
1314
|
if (versions.forced || host.forced) this.#reportBaselines(outcome.provenance);
|
|
773
1315
|
return 0;
|
|
@@ -779,7 +1321,7 @@ var CLI = class CLI {
|
|
|
779
1321
|
const target = command.target ?? ".";
|
|
780
1322
|
const manifest = this.#manifest(target);
|
|
781
1323
|
const blueprint = this.#derive(target);
|
|
782
|
-
const groups =
|
|
1324
|
+
const groups = selectionToGroups(command.groups);
|
|
783
1325
|
const declared = manifestToWritableDependencies(manifest, blueprint);
|
|
784
1326
|
const versions = await this.#versions(declared, command.offline === true);
|
|
785
1327
|
const questions = [...this.#targetQuestions(target, blueprint, groups), ...releasesToQuestions(versions.releases)];
|
|
@@ -812,7 +1354,7 @@ var CLI = class CLI {
|
|
|
812
1354
|
}
|
|
813
1355
|
async #restore(command) {
|
|
814
1356
|
const target = command.target ?? ".";
|
|
815
|
-
const groups =
|
|
1357
|
+
const groups = selectionToGroups(command.groups);
|
|
816
1358
|
const blueprint = this.#derive(target);
|
|
817
1359
|
this.#assertTarget(target, blueprint, groups);
|
|
818
1360
|
const host = await this.#host(command.from, target, command.offline === true);
|
|
@@ -827,8 +1369,9 @@ var CLI = class CLI {
|
|
|
827
1369
|
return 1;
|
|
828
1370
|
}
|
|
829
1371
|
const versions = await this.#versions(manifestToWritableDependencies(this.#manifest(target), blueprint), command.offline === true);
|
|
830
|
-
|
|
831
|
-
|
|
1372
|
+
const refusal = versionsToRefusal(versions);
|
|
1373
|
+
if (refusal !== void 0) throw refusal;
|
|
1374
|
+
const result = mergeResults(host.materializer.repair(plan, audit, target), host.materializer.declare({
|
|
832
1375
|
pins: versions.pins,
|
|
833
1376
|
scripts: blueprintToWritableScripts(blueprint)
|
|
834
1377
|
}, target));
|
|
@@ -848,7 +1391,7 @@ var CLI = class CLI {
|
|
|
848
1391
|
else {
|
|
849
1392
|
this.#present(terminal);
|
|
850
1393
|
this.#reportReplacements(audit, terminal);
|
|
851
|
-
this.#say(
|
|
1394
|
+
this.#say(resultToTally(result));
|
|
852
1395
|
}
|
|
853
1396
|
if (versions.forced || host.forced) this.#reportBaselines(outcome.provenance);
|
|
854
1397
|
if (command.offline !== true && (versions.forced || host.forced)) return 1;
|
|
@@ -861,21 +1404,22 @@ var CLI = class CLI {
|
|
|
861
1404
|
const target = command.target ?? ".";
|
|
862
1405
|
const [host, ...extra] = command.from ?? [];
|
|
863
1406
|
if (extra.length > 0) this.#warn(`Read the data root from ${String(host)}. The other ${String(extra.length)} local root${extra.length === 1 ? "" : "s"} named by --from reach nothing this run does.`);
|
|
864
|
-
const previous =
|
|
1407
|
+
const previous = catalogToNames(target);
|
|
865
1408
|
const fetched = await this.#fetch(target, command.all === true);
|
|
866
1409
|
const declarations = manifestToDependencies(this.#manifest(target));
|
|
867
1410
|
const writable = {
|
|
868
1411
|
runtime: dependenciesToFleet(declarations.runtime),
|
|
869
1412
|
development: dependenciesToFleet(declarations.development)
|
|
870
1413
|
};
|
|
871
|
-
const releases =
|
|
872
|
-
const pins =
|
|
873
|
-
|
|
1414
|
+
const releases = entriesToReleases([...writable.runtime, ...writable.development], fetched.entries);
|
|
1415
|
+
const pins = releasesToPins(releases, writable);
|
|
1416
|
+
const refusal = fetchToRefusal(fetched.entries, fetched.mirrors);
|
|
1417
|
+
if (refusal !== void 0) throw refusal;
|
|
874
1418
|
const guides = fetched.mirrors.length === 0 ? void 0 : fetched.mirrors.some((mirror) => mirror.lookup !== "found") ? "floor" : "live";
|
|
875
1419
|
const materializer = new Materializer({ host: host ?? readHostFloor() });
|
|
876
1420
|
let result;
|
|
877
1421
|
try {
|
|
878
|
-
result =
|
|
1422
|
+
result = mergeResults(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare({
|
|
879
1423
|
pins,
|
|
880
1424
|
scripts: []
|
|
881
1425
|
}, target));
|
|
@@ -899,7 +1443,7 @@ var CLI = class CLI {
|
|
|
899
1443
|
}
|
|
900
1444
|
async #replace(command) {
|
|
901
1445
|
const target = command.target ?? ".";
|
|
902
|
-
const groups =
|
|
1446
|
+
const groups = selectionToGroups(command.groups);
|
|
903
1447
|
const blueprint = this.#derive(target);
|
|
904
1448
|
this.#assertTarget(target, blueprint, groups);
|
|
905
1449
|
const worktree = this.#worktree(target);
|
|
@@ -922,18 +1466,16 @@ var CLI = class CLI {
|
|
|
922
1466
|
pins: manifestToWritableDependencies(this.#manifest(target), blueprint),
|
|
923
1467
|
scripts: blueprintToWritableScripts(blueprint)
|
|
924
1468
|
};
|
|
925
|
-
const
|
|
926
|
-
const removed = host.materializer.remove(plan, audit, command.dirty === true ? {
|
|
1469
|
+
const local = mergeResults(host.materializer.repair(plan, audit, target), host.materializer.remove(plan, audit, command.dirty === true ? {
|
|
927
1470
|
tracked: worktree.tracked,
|
|
928
1471
|
dirty: []
|
|
929
|
-
} : worktree, target);
|
|
930
|
-
const
|
|
931
|
-
const online = command.offline === true ? await this.#offline(host.materializer, target, declared, host.baseline) : await this.#reconcile(host.materializer, target, declared, host.baseline, host.forced);
|
|
1472
|
+
} : worktree, target));
|
|
1473
|
+
const remainder = command.offline === true ? await this.#declare(host.materializer, target, declared, host.baseline) : await this.#reconcile(host.materializer, target, declared, host.baseline, host.forced);
|
|
932
1474
|
const [measured] = this.#survey(host.materializer, blueprint, target, groups);
|
|
933
1475
|
const terminal = this.#appendQuestions(measured, target, blueprint, groups);
|
|
934
1476
|
const outcome = {
|
|
935
|
-
...
|
|
936
|
-
...
|
|
1477
|
+
...remainder,
|
|
1478
|
+
...mergeResults(local, remainder),
|
|
937
1479
|
audit: terminal
|
|
938
1480
|
};
|
|
939
1481
|
if (command.json === true) this.#report(outcome);
|
|
@@ -941,17 +1483,18 @@ var CLI = class CLI {
|
|
|
941
1483
|
this.#present(terminal);
|
|
942
1484
|
this.#reportReplacements(audit, terminal);
|
|
943
1485
|
this.#recount(outcome);
|
|
944
|
-
if (
|
|
1486
|
+
if (remainder.note !== void 0) this.#warn(remainder.note);
|
|
945
1487
|
}
|
|
946
|
-
if (
|
|
1488
|
+
if (remainder.note !== void 0) return 1;
|
|
947
1489
|
return auditToExit(terminal);
|
|
948
1490
|
} finally {
|
|
949
1491
|
host.materializer.destroy();
|
|
950
1492
|
}
|
|
951
1493
|
}
|
|
952
|
-
async #
|
|
1494
|
+
async #declare(materializer, target, declared, host) {
|
|
953
1495
|
const versions = await this.#versions(declared.pins, true);
|
|
954
|
-
|
|
1496
|
+
const refusal = versionsToRefusal(versions);
|
|
1497
|
+
if (refusal !== void 0) throw refusal;
|
|
955
1498
|
return {
|
|
956
1499
|
...materializer.declare({
|
|
957
1500
|
pins: versions.pins,
|
|
@@ -969,7 +1512,7 @@ var CLI = class CLI {
|
|
|
969
1512
|
};
|
|
970
1513
|
}
|
|
971
1514
|
async #reconcile(materializer, target, declared, host, hostForced) {
|
|
972
|
-
const previous =
|
|
1515
|
+
const previous = catalogToNames(target);
|
|
973
1516
|
let releases = [];
|
|
974
1517
|
let provenance = { ...host === void 0 ? {} : { host } };
|
|
975
1518
|
try {
|
|
@@ -979,16 +1522,18 @@ var CLI = class CLI {
|
|
|
979
1522
|
...versions.baseline === void 0 ? {} : { versions: versions.baseline },
|
|
980
1523
|
...host === void 0 ? {} : { host }
|
|
981
1524
|
};
|
|
982
|
-
|
|
1525
|
+
const refused = versionsToRefusal(versions);
|
|
1526
|
+
if (refused !== void 0) throw refused;
|
|
983
1527
|
const fetched = await this.#fetch(target, false);
|
|
984
|
-
|
|
1528
|
+
const incomplete = fetchToRefusal(fetched.entries, fetched.mirrors);
|
|
1529
|
+
if (incomplete !== void 0) throw incomplete;
|
|
985
1530
|
const guides = fetched.mirrors.length === 0 ? void 0 : fetched.mirrors.some((mirror) => mirror.lookup === "failed") ? "floor" : "live";
|
|
986
1531
|
provenance = {
|
|
987
1532
|
...versions.baseline === void 0 ? {} : { versions: versions.baseline },
|
|
988
1533
|
...guides === void 0 ? {} : { guides },
|
|
989
1534
|
...host === void 0 ? {} : { host }
|
|
990
1535
|
};
|
|
991
|
-
const written =
|
|
1536
|
+
const written = mergeResults(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare({
|
|
992
1537
|
pins: versions.pins,
|
|
993
1538
|
scripts: declared.scripts
|
|
994
1539
|
}, target));
|
|
@@ -1031,7 +1576,7 @@ var CLI = class CLI {
|
|
|
1031
1576
|
baseline: "floor",
|
|
1032
1577
|
forced: false
|
|
1033
1578
|
};
|
|
1034
|
-
const paths = floor.manifest.entries.filter((entry) => !
|
|
1579
|
+
const paths = floor.manifest.entries.filter((entry) => !isFloorPath(entry.destination)).map((entry) => entry.destination);
|
|
1035
1580
|
const current = target === void 0 ? floor.bytes : readSnapshot(target, paths);
|
|
1036
1581
|
const upstream = new Upstream(this.#upstream);
|
|
1037
1582
|
try {
|
|
@@ -1119,17 +1664,12 @@ var CLI = class CLI {
|
|
|
1119
1664
|
}
|
|
1120
1665
|
return {
|
|
1121
1666
|
releases,
|
|
1122
|
-
pins:
|
|
1667
|
+
pins: releasesToPins(releases, declared),
|
|
1123
1668
|
baseline: "live",
|
|
1124
1669
|
forced: false,
|
|
1125
1670
|
complete: true
|
|
1126
1671
|
};
|
|
1127
1672
|
}
|
|
1128
|
-
#assertVersions(versions) {
|
|
1129
|
-
if (versions.complete) return;
|
|
1130
|
-
const names = versions.releases.filter((release) => release.lookup !== "found").map((release) => release.name);
|
|
1131
|
-
throw new ScaffoldError("FETCH", names.length === 0 ? "A declared dependency names no concrete floor." : `The registry named no release for ${names.join(", ")}.`, { names: names.length });
|
|
1132
|
-
}
|
|
1133
1673
|
async #lookup(declared) {
|
|
1134
1674
|
if (declared.length === 0) return [];
|
|
1135
1675
|
const upstream = new Upstream(this.#upstream);
|
|
@@ -1179,48 +1719,7 @@ var CLI = class CLI {
|
|
|
1179
1719
|
}
|
|
1180
1720
|
}
|
|
1181
1721
|
#publish(materializer, target, entries, mirrors) {
|
|
1182
|
-
return
|
|
1183
|
-
}
|
|
1184
|
-
#catalogReleases(declared, entries) {
|
|
1185
|
-
return declared.map((dependency) => {
|
|
1186
|
-
const entry = entries.find((candidate) => candidate.name === dependency.name);
|
|
1187
|
-
if (entry?.lookup === "found") return {
|
|
1188
|
-
...dependency,
|
|
1189
|
-
lookup: "found",
|
|
1190
|
-
latest: entry.version
|
|
1191
|
-
};
|
|
1192
|
-
return {
|
|
1193
|
-
...dependency,
|
|
1194
|
-
lookup: entry?.lookup ?? "missing",
|
|
1195
|
-
note: entry?.note ?? "the organization catalog does not list the declared package"
|
|
1196
|
-
};
|
|
1197
|
-
});
|
|
1198
|
-
}
|
|
1199
|
-
#assertFetched(entries, mirrors) {
|
|
1200
|
-
const failed = [...entries.filter((entry) => entry.lookup !== "found").map((entry) => entry.name), ...mirrors.filter((mirror) => mirror.lookup !== "found" && mirror.lookup !== "failed" && mirror.lookup !== "missing").map((mirror) => mirror.name)];
|
|
1201
|
-
if (failed.length === 0) return;
|
|
1202
|
-
throw new ScaffoldError("FETCH", `Upstream produced no complete catalog answer for ${failed.join(", ")}.`, { names: failed.length });
|
|
1203
|
-
}
|
|
1204
|
-
#pin(releases, declared) {
|
|
1205
|
-
const refused = releases.filter((release) => release.lookup !== "found");
|
|
1206
|
-
if (refused.length > 0) throw new ScaffoldError("FETCH", `The registry named no release for ${refused.map((release) => release.name).join(", ")}.`, { names: refused.length });
|
|
1207
|
-
const dependencies = [...declared.runtime, ...declared.development];
|
|
1208
|
-
const pins = [];
|
|
1209
|
-
for (let index = 0; index < releases.length; index += 1) {
|
|
1210
|
-
const release = releases[index];
|
|
1211
|
-
const dependency = dependencies[index];
|
|
1212
|
-
if (release === void 0 || dependency === void 0) throw new ScaffoldError("FETCH", "The release answer has no matching declaration.");
|
|
1213
|
-
if (release.lookup !== "found") throw new ScaffoldError("FETCH", `The registry named no release for ${release.name}.`);
|
|
1214
|
-
pins.push({
|
|
1215
|
-
name: dependency.name,
|
|
1216
|
-
range: `^${release.latest}`
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
if (pins.length !== dependencies.length) throw new ScaffoldError("FETCH", "The release answer does not match the declaration set.");
|
|
1220
|
-
return {
|
|
1221
|
-
runtime: pins.slice(0, declared.runtime.length),
|
|
1222
|
-
development: pins.slice(declared.runtime.length)
|
|
1223
|
-
};
|
|
1722
|
+
return mergeResults(materializer.mirror(mirrors, target), materializer.catalog(entries, target));
|
|
1224
1723
|
}
|
|
1225
1724
|
#compile(blueprint, groups) {
|
|
1226
1725
|
const compiler = new Compiler();
|
|
@@ -1259,8 +1758,8 @@ var CLI = class CLI {
|
|
|
1259
1758
|
const global = resolveContainedPath(target, GLOBAL_SETUP_PATH);
|
|
1260
1759
|
const showcase = resolveContainedPath(target, SHOWCASE_CONFIG_PATH);
|
|
1261
1760
|
return createBlueprint(declared.slice(declared.lastIndexOf("/") + 1), {
|
|
1262
|
-
src:
|
|
1263
|
-
app:
|
|
1761
|
+
src: targetToEnvironments(target, "src"),
|
|
1762
|
+
app: targetToEnvironments(target, "app"),
|
|
1264
1763
|
dependencies: manifestToDependencies(manifest).runtime,
|
|
1265
1764
|
bin: bin !== void 0 && isExactCaseFile(bin),
|
|
1266
1765
|
setup: tests !== void 0 && listFiles(tests).some((path) => {
|
|
@@ -1276,128 +1775,6 @@ var CLI = class CLI {
|
|
|
1276
1775
|
showcase: showcase !== void 0 && isExactCaseFile(showcase)
|
|
1277
1776
|
});
|
|
1278
1777
|
}
|
|
1279
|
-
#invocations(script) {
|
|
1280
|
-
const tokens = [];
|
|
1281
|
-
let value = "";
|
|
1282
|
-
let resolved = true;
|
|
1283
|
-
let started = false;
|
|
1284
|
-
let quote;
|
|
1285
|
-
for (let index = 0; index < script.length; index += 1) {
|
|
1286
|
-
const character = script[index];
|
|
1287
|
-
if (character === void 0) return void 0;
|
|
1288
|
-
if (quote === void 0) {
|
|
1289
|
-
if (/\s/.test(character)) {
|
|
1290
|
-
if (started) tokens.push({
|
|
1291
|
-
value,
|
|
1292
|
-
resolved
|
|
1293
|
-
});
|
|
1294
|
-
value = "";
|
|
1295
|
-
resolved = true;
|
|
1296
|
-
started = false;
|
|
1297
|
-
continue;
|
|
1298
|
-
}
|
|
1299
|
-
if (";&|()".includes(character)) {
|
|
1300
|
-
if (started) tokens.push({
|
|
1301
|
-
value,
|
|
1302
|
-
resolved
|
|
1303
|
-
});
|
|
1304
|
-
const paired = script[index + 1] === character && (character === "&" || character === "|");
|
|
1305
|
-
tokens.push({
|
|
1306
|
-
value: paired ? `${character}${character}` : character,
|
|
1307
|
-
resolved: true
|
|
1308
|
-
});
|
|
1309
|
-
value = "";
|
|
1310
|
-
resolved = true;
|
|
1311
|
-
started = false;
|
|
1312
|
-
if (paired) index += 1;
|
|
1313
|
-
continue;
|
|
1314
|
-
}
|
|
1315
|
-
if (character === "\"" || character === "'") {
|
|
1316
|
-
quote = character;
|
|
1317
|
-
started = true;
|
|
1318
|
-
continue;
|
|
1319
|
-
}
|
|
1320
|
-
if (character === "\\") {
|
|
1321
|
-
const escaped = script[index + 1];
|
|
1322
|
-
if (escaped === void 0) return void 0;
|
|
1323
|
-
value += escaped;
|
|
1324
|
-
started = true;
|
|
1325
|
-
index += 1;
|
|
1326
|
-
continue;
|
|
1327
|
-
}
|
|
1328
|
-
if (character === "$" || character === "`" || character === "%") resolved = false;
|
|
1329
|
-
value += character;
|
|
1330
|
-
started = true;
|
|
1331
|
-
continue;
|
|
1332
|
-
}
|
|
1333
|
-
if (character === quote) {
|
|
1334
|
-
quote = void 0;
|
|
1335
|
-
continue;
|
|
1336
|
-
}
|
|
1337
|
-
if (character === "\\" && quote === "\"") {
|
|
1338
|
-
const escaped = script[index + 1];
|
|
1339
|
-
if (escaped === void 0) return void 0;
|
|
1340
|
-
value += escaped;
|
|
1341
|
-
index += 1;
|
|
1342
|
-
continue;
|
|
1343
|
-
}
|
|
1344
|
-
if (quote === "\"" && (character === "$" || character === "`" || character === "%")) resolved = false;
|
|
1345
|
-
value += character;
|
|
1346
|
-
started = true;
|
|
1347
|
-
}
|
|
1348
|
-
if (quote !== void 0) return void 0;
|
|
1349
|
-
if (started) tokens.push({
|
|
1350
|
-
value,
|
|
1351
|
-
resolved
|
|
1352
|
-
});
|
|
1353
|
-
const projects = [];
|
|
1354
|
-
const scripts = [];
|
|
1355
|
-
for (let index = 0; index < tokens.length; index += 1) {
|
|
1356
|
-
const token = tokens[index];
|
|
1357
|
-
if (token === void 0) return void 0;
|
|
1358
|
-
if (token.value === "npm" && tokens[index + 1]?.value === "run") {
|
|
1359
|
-
const name = tokens[index + 2];
|
|
1360
|
-
if (name === void 0 || !name.resolved || name.value.length === 0 || [
|
|
1361
|
-
"&&",
|
|
1362
|
-
"||",
|
|
1363
|
-
";",
|
|
1364
|
-
"|",
|
|
1365
|
-
"&",
|
|
1366
|
-
"(",
|
|
1367
|
-
")"
|
|
1368
|
-
].includes(name.value)) return void 0;
|
|
1369
|
-
scripts.push(name.value);
|
|
1370
|
-
index += 2;
|
|
1371
|
-
continue;
|
|
1372
|
-
}
|
|
1373
|
-
if (token.value === "--project") {
|
|
1374
|
-
const project = tokens[index + 1];
|
|
1375
|
-
if (project === void 0 || !project.resolved || project.value.length === 0 || [
|
|
1376
|
-
"&&",
|
|
1377
|
-
"||",
|
|
1378
|
-
";",
|
|
1379
|
-
"|",
|
|
1380
|
-
"&",
|
|
1381
|
-
"(",
|
|
1382
|
-
")"
|
|
1383
|
-
].includes(project.value)) return void 0;
|
|
1384
|
-
projects.push(project.value);
|
|
1385
|
-
index += 1;
|
|
1386
|
-
continue;
|
|
1387
|
-
}
|
|
1388
|
-
if (token.value.startsWith("--project=")) {
|
|
1389
|
-
const project = token.value.slice(10);
|
|
1390
|
-
if (!token.resolved || project.length === 0) return void 0;
|
|
1391
|
-
projects.push(project);
|
|
1392
|
-
continue;
|
|
1393
|
-
}
|
|
1394
|
-
if (!token.resolved && token.value.includes("--project")) return void 0;
|
|
1395
|
-
}
|
|
1396
|
-
return {
|
|
1397
|
-
projects,
|
|
1398
|
-
scripts
|
|
1399
|
-
};
|
|
1400
|
-
}
|
|
1401
1778
|
#projectQuestion(target, blueprint, writing = false) {
|
|
1402
1779
|
const text = this.#manifest(target);
|
|
1403
1780
|
const manifest = replaceManifestScripts(text, blueprintToWritableScripts(blueprint)) ?? text;
|
|
@@ -1410,7 +1787,7 @@ var CLI = class CLI {
|
|
|
1410
1787
|
let unresolved = false;
|
|
1411
1788
|
for (const script of Object.values(scripts)) {
|
|
1412
1789
|
if (!isString(script) || !script.includes("vitest")) continue;
|
|
1413
|
-
const invoked =
|
|
1790
|
+
const invoked = scriptToInvocations(script);
|
|
1414
1791
|
if (invoked === void 0) {
|
|
1415
1792
|
unresolved = true;
|
|
1416
1793
|
continue;
|
|
@@ -1434,7 +1811,7 @@ var CLI = class CLI {
|
|
|
1434
1811
|
const expectedLines = /* @__PURE__ */ new Map();
|
|
1435
1812
|
for (const [name, script] of Object.entries(expected)) {
|
|
1436
1813
|
if (!name.startsWith("test:")) continue;
|
|
1437
|
-
const invoked =
|
|
1814
|
+
const invoked = scriptToInvocations(script);
|
|
1438
1815
|
if (invoked === void 0) continue;
|
|
1439
1816
|
for (const project of invoked.projects) {
|
|
1440
1817
|
if (project === "probe") continue;
|
|
@@ -1452,7 +1829,7 @@ var CLI = class CLI {
|
|
|
1452
1829
|
visited.add(name);
|
|
1453
1830
|
const script = scripts[name];
|
|
1454
1831
|
if (!isString(script)) continue;
|
|
1455
|
-
const invoked =
|
|
1832
|
+
const invoked = scriptToInvocations(script);
|
|
1456
1833
|
if (invoked === void 0) {
|
|
1457
1834
|
unresolved = true;
|
|
1458
1835
|
continue;
|
|
@@ -1594,27 +1971,9 @@ var CLI = class CLI {
|
|
|
1594
1971
|
if (manifest === void 0) throw new ScaffoldError("TARGET", `The target at ${target} carries no readable manifest.`, { target });
|
|
1595
1972
|
return manifest;
|
|
1596
1973
|
}
|
|
1597
|
-
#probe(target, axis) {
|
|
1598
|
-
return ENVIRONMENTS.filter((environment) => {
|
|
1599
|
-
const full = resolveContainedPath(target, `${axis}/${environment}`);
|
|
1600
|
-
return full !== void 0 && isPhysicalDirectory(full);
|
|
1601
|
-
});
|
|
1602
|
-
}
|
|
1603
|
-
#previous(target) {
|
|
1604
|
-
const text = readFileText(target, CATALOG_AGENT_PATH);
|
|
1605
|
-
if (text === void 0) return [];
|
|
1606
|
-
const names = [];
|
|
1607
|
-
for (const table of createMarkdown(text).filter(isTableNode)) for (const row of table.rows) {
|
|
1608
|
-
const [cell] = row;
|
|
1609
|
-
if (cell === void 0) continue;
|
|
1610
|
-
const name = cell.map(flattenText).join("").trim();
|
|
1611
|
-
if (DEPENDENCY_NAME_PATTERN.test(name)) names.push(name);
|
|
1612
|
-
}
|
|
1613
|
-
return names;
|
|
1614
|
-
}
|
|
1615
1974
|
#worktree(target) {
|
|
1616
|
-
const tracked =
|
|
1617
|
-
const dirty =
|
|
1975
|
+
const tracked = readGitRecords(target, ["ls-files", "-z"]);
|
|
1976
|
+
const dirty = readGitRecords(target, [
|
|
1618
1977
|
"status",
|
|
1619
1978
|
"--porcelain=v1",
|
|
1620
1979
|
"--untracked-files=all",
|
|
@@ -1631,47 +1990,6 @@ var CLI = class CLI {
|
|
|
1631
1990
|
});
|
|
1632
1991
|
return state;
|
|
1633
1992
|
}
|
|
1634
|
-
#inventory(target, args) {
|
|
1635
|
-
const result = executeSync({
|
|
1636
|
-
file: "git",
|
|
1637
|
-
arguments: [...args]
|
|
1638
|
-
}, {
|
|
1639
|
-
workspace: target,
|
|
1640
|
-
limit: MAX_MANIFEST_BYTES,
|
|
1641
|
-
strict: false
|
|
1642
|
-
});
|
|
1643
|
-
if (result.failed) throw new ScaffoldError("TARGET", `The target at ${target} is not a git repository.`, { target });
|
|
1644
|
-
return result.stdout.split("\0").filter((record) => record.length > 0);
|
|
1645
|
-
}
|
|
1646
|
-
#environments(selection, axis) {
|
|
1647
|
-
if (selection === void 0) return [];
|
|
1648
|
-
const requested = selection.split(",");
|
|
1649
|
-
const refused = requested.filter((name) => !ENVIRONMENTS.some((environment) => environment === name));
|
|
1650
|
-
if (refused.length > 0) throw new UsageError(`'--${axis}' does not take ${refused.join(", ")}. It takes ${ENVIRONMENTS.join(", ")}.`);
|
|
1651
|
-
return ENVIRONMENTS.filter((environment) => requested.includes(environment));
|
|
1652
|
-
}
|
|
1653
|
-
#groups(selection) {
|
|
1654
|
-
if (selection === void 0) return void 0;
|
|
1655
|
-
const requested = selection.split(",");
|
|
1656
|
-
const refused = requested.filter((name) => !GROUPS.some((group) => group === name));
|
|
1657
|
-
if (refused.length > 0) throw new UsageError(`'--groups' does not take ${refused.join(", ")}. It takes ${GROUPS.join(", ")}.`);
|
|
1658
|
-
return GROUPS.filter((group) => requested.includes(group));
|
|
1659
|
-
}
|
|
1660
|
-
#packages(selection) {
|
|
1661
|
-
if (selection === void 0) return [];
|
|
1662
|
-
const requested = selection.split(",");
|
|
1663
|
-
const refused = requested.filter((name) => !DEPENDENCY_NAME_PATTERN.test(name));
|
|
1664
|
-
if (refused.length > 0) throw new UsageError(`'--deps' does not take ${refused.join(", ")}. Every name is a published @orkestrel package.`);
|
|
1665
|
-
return requested;
|
|
1666
|
-
}
|
|
1667
|
-
#merge(first, second) {
|
|
1668
|
-
return {
|
|
1669
|
-
target: first.target,
|
|
1670
|
-
written: [...first.written, ...second.written],
|
|
1671
|
-
skipped: [...first.skipped, ...second.skipped],
|
|
1672
|
-
removed: [...first.removed, ...second.removed]
|
|
1673
|
-
};
|
|
1674
|
-
}
|
|
1675
1993
|
#reportBaselines(provenance) {
|
|
1676
1994
|
const baselines = [];
|
|
1677
1995
|
if (provenance.versions !== void 0) baselines.push(`versions=${provenance.versions}`);
|
|
@@ -1714,8 +2032,8 @@ var CLI = class CLI {
|
|
|
1714
2032
|
if (finding.drift !== "stale") continue;
|
|
1715
2033
|
const current = terminal.get(finding.path);
|
|
1716
2034
|
if (current?.drift !== "aligned" || current.observed === void 0) continue;
|
|
1717
|
-
const previousLines = Buffer.from(finding.observed, "hex").toString("utf8").split(/\r\n|\
|
|
1718
|
-
const delta = Buffer.from(current.observed, "hex").toString("utf8").split(/\r\n|\
|
|
2035
|
+
const previousLines = Buffer.from(finding.observed, "hex").toString("utf8").split(/\r\n|\n/u).length;
|
|
2036
|
+
const delta = Buffer.from(current.observed, "hex").toString("utf8").split(/\r\n|\n/u).length - previousLines;
|
|
1719
2037
|
if (delta === 0) {
|
|
1720
2038
|
this.#say(`${finding.path} replaced (0-line delta).`);
|
|
1721
2039
|
continue;
|
|
@@ -1725,13 +2043,10 @@ var CLI = class CLI {
|
|
|
1725
2043
|
}
|
|
1726
2044
|
}
|
|
1727
2045
|
#recount(result) {
|
|
1728
|
-
this.#say(
|
|
2046
|
+
this.#say(resultToTally(result));
|
|
1729
2047
|
this.#say(`${String(result.entries.length)} published, ${String(result.mirrors.filter((mirror) => mirror.lookup === "found").length)} guide${result.mirrors.length === 1 ? "" : "s"} fetched, ${String(result.dropped.length)} no longer listed.`);
|
|
1730
2048
|
for (const mirror of result.mirrors) if (mirror.lookup !== "found") this.#warn(`${mirror.name}: ${mirror.note}`);
|
|
1731
2049
|
}
|
|
1732
|
-
#tally(result) {
|
|
1733
|
-
return `${String(result.written.length)} written, ${String(result.skipped.length)} unchanged, ${String(result.removed.length)} removed in ${result.target}.`;
|
|
1734
|
-
}
|
|
1735
2050
|
#report(value) {
|
|
1736
2051
|
this.#say(JSON.stringify(value));
|
|
1737
2052
|
}
|
|
@@ -1742,13 +2057,10 @@ var CLI = class CLI {
|
|
|
1742
2057
|
return isUsageError(error) ? 2 : 1;
|
|
1743
2058
|
}
|
|
1744
2059
|
#say(line) {
|
|
1745
|
-
this.#output(
|
|
2060
|
+
this.#output(sanitizeLine(line));
|
|
1746
2061
|
}
|
|
1747
2062
|
#warn(line) {
|
|
1748
|
-
this.#diagnostic(
|
|
1749
|
-
}
|
|
1750
|
-
#sanitize(line) {
|
|
1751
|
-
return stripControls(strip(line)).split(/\r?\n/).join(" ");
|
|
2063
|
+
this.#diagnostic(sanitizeLine(line));
|
|
1752
2064
|
}
|
|
1753
2065
|
};
|
|
1754
2066
|
//#endregion
|