@orkestrel/scaffold 0.0.48 → 0.0.50
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 +4 -2
- package/dist/bin/main.js +427 -97
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +35 -3
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +4 -0
- package/dist/host/claude/agents/orkestrel.md +44 -44
- package/dist/host/claude/rules/quality.md +4 -1
- package/dist/host/claude/rules/tests.md +6 -2
- package/dist/host/guides/scaffold.md +478 -161
- package/dist/host/manifest.json +217 -109
- 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/host/tests/config.test.ts +129 -6
- package/dist/src/core/index.cjs +1315 -82
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +265 -44
- package/dist/src/core/index.d.ts +265 -44
- package/dist/src/core/index.js +1308 -83
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +816 -152
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +723 -391
- package/dist/src/server/index.d.ts +723 -391
- package/dist/src/server/index.js +812 -156
- package/dist/src/server/index.js.map +1 -1
- package/package.json +10 -9
package/dist/bin/main.js
CHANGED
|
@@ -3,8 +3,8 @@ import { align, renderTable, strip, stripControls, width } from "@orkestrel/cons
|
|
|
3
3
|
import { attempt, isRecord, isString, parseJSON } from "@orkestrel/contract";
|
|
4
4
|
import { createMarkdown, flattenText, isTableNode } from "@orkestrel/markdown";
|
|
5
5
|
import { executeSync } from "@orkestrel/process/server";
|
|
6
|
-
import { BIN_ENTRY_PATH, CATALOG_AGENT_PATH, CONFORMANCE_TEST_PATH, Compiler, DEPENDENCY_NAME_PATTERN,
|
|
7
|
-
import { Materializer, Upstream, isExactCaseFile, isPhysicalDirectory,
|
|
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, 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
|
/**
|
|
@@ -86,6 +86,7 @@ var COMMAND_OPTIONS = Object.freeze({
|
|
|
86
86
|
groups: Object.freeze({ type: "string" }),
|
|
87
87
|
all: Object.freeze({ type: "boolean" }),
|
|
88
88
|
dirty: Object.freeze({ type: "boolean" }),
|
|
89
|
+
offline: Object.freeze({ type: "boolean" }),
|
|
89
90
|
from: Object.freeze({
|
|
90
91
|
type: "string",
|
|
91
92
|
multiple: true
|
|
@@ -109,10 +110,12 @@ var OPTION_SUMMARY = Object.freeze({
|
|
|
109
110
|
"--groups <list>": "the artifact groups to cover; every group when absent",
|
|
110
111
|
"--all": "fetch a guide for every package the organization publishes, not the declared ones alone",
|
|
111
112
|
"--dirty": "delete from a tree carrying uncommitted changes",
|
|
113
|
+
"--offline": "use the distributed dependency and vendored-host floors without reading upstream",
|
|
112
114
|
"--from <path>": "read the data root from a local path instead of the bundled one; catalog alone accepts it more than once",
|
|
113
115
|
"--target <path>": "the directory the verb operates on; the working directory when absent",
|
|
114
116
|
"--json": "emit one machine-readable value instead of a report",
|
|
115
|
-
ORKESTREL_SCAFFOLD_REGISTRY: "the registry base mapped to upstream.registry.base"
|
|
117
|
+
ORKESTREL_SCAFFOLD_REGISTRY: "the registry base mapped to upstream.registry.base",
|
|
118
|
+
ORKESTREL_SCAFFOLD_REPOSITORY: "the repository base mapped to upstream.repository.base"
|
|
116
119
|
});
|
|
117
120
|
/**
|
|
118
121
|
* The options each verb takes, in usage order, frozen.
|
|
@@ -129,18 +132,21 @@ var VERB_OPTIONS = Object.freeze({
|
|
|
129
132
|
"--app <list>",
|
|
130
133
|
"--bin",
|
|
131
134
|
"--deps <list>",
|
|
135
|
+
"--offline",
|
|
132
136
|
"--from <path>",
|
|
133
137
|
"--target <path>",
|
|
134
138
|
"--json"
|
|
135
139
|
]),
|
|
136
140
|
audit: Object.freeze([
|
|
137
141
|
"--groups <list>",
|
|
142
|
+
"--offline",
|
|
138
143
|
"--from <path>",
|
|
139
144
|
"--target <path>",
|
|
140
145
|
"--json"
|
|
141
146
|
]),
|
|
142
147
|
repair: Object.freeze([
|
|
143
148
|
"--groups <list>",
|
|
149
|
+
"--offline",
|
|
144
150
|
"--from <path>",
|
|
145
151
|
"--target <path>",
|
|
146
152
|
"--json"
|
|
@@ -154,6 +160,7 @@ var VERB_OPTIONS = Object.freeze({
|
|
|
154
160
|
overwrite: Object.freeze([
|
|
155
161
|
"--groups <list>",
|
|
156
162
|
"--dirty",
|
|
163
|
+
"--offline",
|
|
157
164
|
"--from <path>",
|
|
158
165
|
"--target <path>",
|
|
159
166
|
"--json"
|
|
@@ -256,6 +263,22 @@ function optionToName(option) {
|
|
|
256
263
|
return token.startsWith("--") ? token.slice(2) : token;
|
|
257
264
|
}
|
|
258
265
|
/**
|
|
266
|
+
* Project process environment endpoints into upstream reader options.
|
|
267
|
+
*
|
|
268
|
+
* @param environment - The process environment to read.
|
|
269
|
+
* @returns The configured endpoint groups, or `undefined` when neither endpoint
|
|
270
|
+
* is configured.
|
|
271
|
+
*/
|
|
272
|
+
function environmentToUpstream(environment) {
|
|
273
|
+
const registry = environment.ORKESTREL_SCAFFOLD_REGISTRY;
|
|
274
|
+
const repository = environment.ORKESTREL_SCAFFOLD_REPOSITORY;
|
|
275
|
+
if (registry === void 0 && repository === void 0) return void 0;
|
|
276
|
+
return {
|
|
277
|
+
...registry === void 0 ? {} : { registry: { base: registry } },
|
|
278
|
+
...repository === void 0 ? {} : { repository: { base: repository } }
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
259
282
|
* Render one verb's synopsis.
|
|
260
283
|
*
|
|
261
284
|
* @param verb - The verb to describe.
|
|
@@ -355,6 +378,7 @@ function argvToCommand(argv) {
|
|
|
355
378
|
const src = typeof values.src === "string" ? values.src : void 0;
|
|
356
379
|
const app = typeof values.app === "string" ? values.app : void 0;
|
|
357
380
|
const bin = values.bin === true;
|
|
381
|
+
const offline = values.offline === true;
|
|
358
382
|
const dependencies = typeof values.deps === "string" ? values.deps : void 0;
|
|
359
383
|
const target = typeof values.target === "string" ? values.target : void 0;
|
|
360
384
|
const json = values.json === true;
|
|
@@ -374,6 +398,7 @@ function argvToCommand(argv) {
|
|
|
374
398
|
...src === void 0 ? {} : { src },
|
|
375
399
|
...app === void 0 ? {} : { app },
|
|
376
400
|
...bin ? { bin } : {},
|
|
401
|
+
...offline ? { offline } : {},
|
|
377
402
|
...dependencies === void 0 ? {} : { dependencies }
|
|
378
403
|
};
|
|
379
404
|
case "audit": return {
|
|
@@ -381,14 +406,16 @@ function argvToCommand(argv) {
|
|
|
381
406
|
json,
|
|
382
407
|
...location,
|
|
383
408
|
...source,
|
|
384
|
-
...selection
|
|
409
|
+
...selection,
|
|
410
|
+
...offline ? { offline } : {}
|
|
385
411
|
};
|
|
386
412
|
case "repair": return {
|
|
387
413
|
verb,
|
|
388
414
|
json,
|
|
389
415
|
...location,
|
|
390
416
|
...source,
|
|
391
|
-
...selection
|
|
417
|
+
...selection,
|
|
418
|
+
...offline ? { offline } : {}
|
|
392
419
|
};
|
|
393
420
|
case "catalog": return {
|
|
394
421
|
verb,
|
|
@@ -401,6 +428,7 @@ function argvToCommand(argv) {
|
|
|
401
428
|
verb,
|
|
402
429
|
json,
|
|
403
430
|
dirty: values.dirty === true,
|
|
431
|
+
...offline ? { offline } : {},
|
|
404
432
|
...location,
|
|
405
433
|
...source,
|
|
406
434
|
...selection
|
|
@@ -435,27 +463,43 @@ function auditToExit(audit) {
|
|
|
435
463
|
return blocked || drifted ? 1 : 0;
|
|
436
464
|
}
|
|
437
465
|
/**
|
|
438
|
-
* Read the
|
|
466
|
+
* Read the runtime and development rows a writing verb may raise.
|
|
439
467
|
*
|
|
440
468
|
* @param manifest - The target manifest text.
|
|
441
469
|
* @param blueprint - The workspace shape that supplies the planned tool set.
|
|
442
|
-
* @returns The declared fleet rows
|
|
470
|
+
* @returns The declared fleet rows and planned foreign tools in their writable sections.
|
|
443
471
|
*/
|
|
444
|
-
function
|
|
445
|
-
const
|
|
472
|
+
function manifestToWritableDependencies(manifest, blueprint) {
|
|
473
|
+
const declared = manifestToDependencies(manifest);
|
|
474
|
+
const runtime = [...dependenciesToFleet(declared.runtime)];
|
|
475
|
+
const development = [...dependenciesToFleet(declared.development)];
|
|
446
476
|
const parsed = parseJSON(manifest);
|
|
447
|
-
if (!isRecord(parsed)) return
|
|
448
|
-
|
|
449
|
-
|
|
477
|
+
if (!isRecord(parsed)) return {
|
|
478
|
+
runtime,
|
|
479
|
+
development
|
|
480
|
+
};
|
|
481
|
+
const runtimeRecord = isRecord(parsed.dependencies) ? parsed.dependencies : {};
|
|
482
|
+
const developmentRecord = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
|
|
450
483
|
for (const name of Object.keys(blueprintToDevDependencies(blueprint)).sort()) {
|
|
451
484
|
if (name.startsWith("@orkestrel/")) continue;
|
|
452
|
-
const
|
|
453
|
-
if (isString(
|
|
485
|
+
const runtimeRange = runtimeRecord[name];
|
|
486
|
+
if (isString(runtimeRange)) {
|
|
487
|
+
runtime.push({
|
|
488
|
+
name,
|
|
489
|
+
range: runtimeRange
|
|
490
|
+
});
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const developmentRange = developmentRecord[name];
|
|
494
|
+
if (isString(developmentRange)) development.push({
|
|
454
495
|
name,
|
|
455
|
-
range
|
|
496
|
+
range: developmentRange
|
|
456
497
|
});
|
|
457
498
|
}
|
|
458
|
-
return
|
|
499
|
+
return {
|
|
500
|
+
runtime,
|
|
501
|
+
development
|
|
502
|
+
};
|
|
459
503
|
}
|
|
460
504
|
/**
|
|
461
505
|
* Keep only dependencies published by the Orkestrel fleet.
|
|
@@ -467,6 +511,26 @@ function dependenciesToFleet(dependencies) {
|
|
|
467
511
|
return dependencies.filter((dependency) => dependency.name.startsWith("@orkestrel/"));
|
|
468
512
|
}
|
|
469
513
|
/**
|
|
514
|
+
* Project declared concrete ranges to their distributed release floors.
|
|
515
|
+
*
|
|
516
|
+
* @param dependencies - The declarations whose floor versions to read.
|
|
517
|
+
* @returns One found floor per declaration, or `undefined` when any range names
|
|
518
|
+
* no concrete version.
|
|
519
|
+
*/
|
|
520
|
+
function dependenciesToFloors(dependencies) {
|
|
521
|
+
const releases = [];
|
|
522
|
+
for (const dependency of dependencies) {
|
|
523
|
+
const version = dependency.range.startsWith("^") || dependency.range.startsWith("~") ? dependency.range.slice(1) : dependency.range;
|
|
524
|
+
if (extractVersion(version) === void 0) return void 0;
|
|
525
|
+
releases.push({
|
|
526
|
+
...dependency,
|
|
527
|
+
lookup: "found",
|
|
528
|
+
latest: version
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
return releases;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
470
534
|
* Read the exit code registry release evidence earns.
|
|
471
535
|
*
|
|
472
536
|
* @param releases - The release verdicts to measure.
|
|
@@ -682,71 +746,101 @@ var CLI = class CLI {
|
|
|
682
746
|
const plan = this.#compile(blueprint);
|
|
683
747
|
const manifest = plan.artifacts.find((artifact) => artifact.path === "package.json" && artifact.origin !== "host");
|
|
684
748
|
if (manifest === void 0 || manifest.origin === "host") throw new ScaffoldError("BLOCKED", "The compiled plan carries no replaceable manifest.");
|
|
685
|
-
const
|
|
686
|
-
const
|
|
749
|
+
const declarations = manifestToDependencies(manifest.content);
|
|
750
|
+
const versions = await this.#versions({
|
|
751
|
+
runtime: dependenciesToFleet(declarations.runtime),
|
|
752
|
+
development: dependenciesToFleet(declarations.development)
|
|
753
|
+
}, command.offline === true);
|
|
754
|
+
this.#assertVersions(versions);
|
|
755
|
+
const resolved = replacePlanRanges(plan, versions.pins);
|
|
687
756
|
if (resolved === void 0) throw new ScaffoldError("BLOCKED", "The compiled plan ranges could not be replaced.");
|
|
688
|
-
const
|
|
757
|
+
const host = await this.#host(command.from, void 0, command.offline === true);
|
|
689
758
|
try {
|
|
690
|
-
const result = materializer.materialize(resolved, target);
|
|
691
|
-
|
|
759
|
+
const result = host.materializer.materialize(resolved, target);
|
|
760
|
+
const outcome = {
|
|
761
|
+
...result,
|
|
762
|
+
provenance: {
|
|
763
|
+
...versions.baseline === void 0 ? {} : { versions: versions.baseline },
|
|
764
|
+
...host.baseline === void 0 ? {} : { host: host.baseline }
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
if (command.json === true) this.#report(outcome);
|
|
692
768
|
else {
|
|
693
769
|
this.#say(`Scaffolded ${blueprint.name} into ${result.target}.`);
|
|
694
770
|
this.#say(this.#tally(result));
|
|
695
771
|
}
|
|
772
|
+
if (versions.forced || host.forced) this.#reportBaselines(outcome.provenance);
|
|
696
773
|
return 0;
|
|
697
774
|
} finally {
|
|
698
|
-
materializer.destroy();
|
|
775
|
+
host.materializer.destroy();
|
|
699
776
|
}
|
|
700
777
|
}
|
|
701
778
|
async #inspect(command) {
|
|
702
779
|
const target = command.target ?? ".";
|
|
703
780
|
const manifest = this.#manifest(target);
|
|
704
781
|
const blueprint = this.#derive(target);
|
|
705
|
-
const
|
|
706
|
-
const
|
|
707
|
-
const
|
|
708
|
-
const
|
|
782
|
+
const groups = this.#groups(command.groups);
|
|
783
|
+
const declared = manifestToWritableDependencies(manifest, blueprint);
|
|
784
|
+
const versions = await this.#versions(declared, command.offline === true);
|
|
785
|
+
const questions = [...this.#targetQuestions(target, blueprint, groups), ...releasesToQuestions(versions.releases)];
|
|
786
|
+
const host = await this.#host(command.from, target, command.offline === true);
|
|
709
787
|
try {
|
|
710
|
-
const [measured] = this.#survey(materializer, blueprint, target,
|
|
788
|
+
const [measured] = this.#survey(host.materializer, blueprint, target, groups);
|
|
711
789
|
const audit = questions.length === 0 ? measured : {
|
|
712
790
|
...measured,
|
|
713
791
|
questions: [...measured.questions, ...questions]
|
|
714
792
|
};
|
|
715
793
|
const outcome = {
|
|
716
794
|
...audit,
|
|
717
|
-
releases
|
|
795
|
+
releases: versions.releases,
|
|
796
|
+
provenance: {
|
|
797
|
+
...versions.baseline === void 0 ? {} : { versions: versions.baseline },
|
|
798
|
+
...host.baseline === void 0 ? {} : { host: host.baseline }
|
|
799
|
+
}
|
|
718
800
|
};
|
|
719
801
|
if (command.json === true) this.#report(outcome);
|
|
720
802
|
else {
|
|
721
803
|
this.#present(audit);
|
|
722
|
-
this.#reportReleases(releases);
|
|
804
|
+
if (command.offline !== true) this.#reportReleases(versions.releases);
|
|
723
805
|
}
|
|
724
|
-
|
|
806
|
+
if (versions.forced || host.forced) this.#reportBaselines(outcome.provenance);
|
|
807
|
+
if (command.offline === true) return auditToExit(audit);
|
|
808
|
+
return Math.max(auditToExit(audit), releasesToExit(versions.releases), versions.forced || host.forced ? 1 : 0);
|
|
725
809
|
} finally {
|
|
726
|
-
materializer.destroy();
|
|
810
|
+
host.materializer.destroy();
|
|
727
811
|
}
|
|
728
812
|
}
|
|
729
813
|
async #restore(command) {
|
|
730
814
|
const target = command.target ?? ".";
|
|
731
815
|
const groups = this.#groups(command.groups);
|
|
732
816
|
const blueprint = this.#derive(target);
|
|
733
|
-
this.#assertTarget(target, blueprint);
|
|
734
|
-
const
|
|
817
|
+
this.#assertTarget(target, blueprint, groups);
|
|
818
|
+
const host = await this.#host(command.from, target, command.offline === true);
|
|
735
819
|
try {
|
|
736
|
-
const [audit, plan] = this.#survey(materializer, blueprint, target, groups);
|
|
820
|
+
const [audit, plan] = this.#survey(host.materializer, blueprint, target, groups);
|
|
737
821
|
if (plan === void 0) {
|
|
738
|
-
if (command.json === true) this.#report(
|
|
822
|
+
if (command.json === true) this.#report({
|
|
823
|
+
...audit,
|
|
824
|
+
provenance: { ...host.baseline === void 0 ? {} : { host: host.baseline } }
|
|
825
|
+
});
|
|
739
826
|
else this.#present(audit);
|
|
740
827
|
return 1;
|
|
741
828
|
}
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
const result = this.#merge(materializer.repair(plan, audit, target), materializer.declare(
|
|
745
|
-
|
|
829
|
+
const versions = await this.#versions(manifestToWritableDependencies(this.#manifest(target), blueprint), command.offline === true);
|
|
830
|
+
this.#assertVersions(versions);
|
|
831
|
+
const result = this.#merge(host.materializer.repair(plan, audit, target), host.materializer.declare({
|
|
832
|
+
pins: versions.pins,
|
|
833
|
+
scripts: blueprintToWritableScripts(blueprint)
|
|
834
|
+
}, target));
|
|
835
|
+
const [terminal] = this.#survey(host.materializer, this.#derive(target), target, groups);
|
|
746
836
|
const outcome = {
|
|
747
837
|
...result,
|
|
748
838
|
audit: terminal,
|
|
749
|
-
releases
|
|
839
|
+
releases: versions.releases,
|
|
840
|
+
provenance: {
|
|
841
|
+
...versions.baseline === void 0 ? {} : { versions: versions.baseline },
|
|
842
|
+
...host.baseline === void 0 ? {} : { host: host.baseline }
|
|
843
|
+
}
|
|
750
844
|
};
|
|
751
845
|
if (command.json === true) this.#report(outcome);
|
|
752
846
|
else {
|
|
@@ -754,9 +848,11 @@ var CLI = class CLI {
|
|
|
754
848
|
this.#reportReplacements(audit, terminal);
|
|
755
849
|
this.#say(this.#tally(result));
|
|
756
850
|
}
|
|
851
|
+
if (versions.forced || host.forced) this.#reportBaselines(outcome.provenance);
|
|
852
|
+
if (command.offline !== true && (versions.forced || host.forced)) return 1;
|
|
757
853
|
return auditToExit(terminal);
|
|
758
854
|
} finally {
|
|
759
|
-
materializer.destroy();
|
|
855
|
+
host.materializer.destroy();
|
|
760
856
|
}
|
|
761
857
|
}
|
|
762
858
|
async #refresh(command) {
|
|
@@ -765,13 +861,22 @@ var CLI = class CLI {
|
|
|
765
861
|
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.`);
|
|
766
862
|
const previous = this.#previous(target);
|
|
767
863
|
const fetched = await this.#fetch(target, command.all === true);
|
|
768
|
-
const
|
|
769
|
-
const
|
|
864
|
+
const declarations = manifestToDependencies(this.#manifest(target));
|
|
865
|
+
const writable = {
|
|
866
|
+
runtime: dependenciesToFleet(declarations.runtime),
|
|
867
|
+
development: dependenciesToFleet(declarations.development)
|
|
868
|
+
};
|
|
869
|
+
const releases = this.#catalogReleases([...writable.runtime, ...writable.development], fetched.entries);
|
|
870
|
+
const pins = this.#pin(releases, writable);
|
|
770
871
|
this.#assertFetched(fetched.entries, fetched.mirrors);
|
|
771
|
-
const
|
|
872
|
+
const guides = fetched.mirrors.length === 0 ? void 0 : fetched.mirrors.some((mirror) => mirror.lookup === "failed") ? "floor" : "live";
|
|
873
|
+
const materializer = new Materializer({ host: host ?? readHostFloor() });
|
|
772
874
|
let result;
|
|
773
875
|
try {
|
|
774
|
-
result = this.#merge(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare(
|
|
876
|
+
result = this.#merge(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare({
|
|
877
|
+
pins,
|
|
878
|
+
scripts: []
|
|
879
|
+
}, target));
|
|
775
880
|
} finally {
|
|
776
881
|
materializer.destroy();
|
|
777
882
|
}
|
|
@@ -780,38 +885,49 @@ var CLI = class CLI {
|
|
|
780
885
|
entries: fetched.entries,
|
|
781
886
|
mirrors: fetched.mirrors,
|
|
782
887
|
dropped: previous.filter((name) => !fetched.entries.some((entry) => entry.name === name)),
|
|
783
|
-
releases
|
|
888
|
+
releases,
|
|
889
|
+
provenance: {
|
|
890
|
+
versions: "live",
|
|
891
|
+
...guides === void 0 ? {} : { guides }
|
|
892
|
+
}
|
|
784
893
|
};
|
|
785
894
|
if (command.json === true) this.#report(outcome);
|
|
786
895
|
else this.#recount(outcome);
|
|
787
|
-
return 0;
|
|
896
|
+
return guides === "floor" ? 1 : 0;
|
|
788
897
|
}
|
|
789
898
|
async #replace(command) {
|
|
790
899
|
const target = command.target ?? ".";
|
|
791
900
|
const groups = this.#groups(command.groups);
|
|
792
901
|
const blueprint = this.#derive(target);
|
|
793
|
-
this.#assertTarget(target, blueprint);
|
|
794
|
-
const
|
|
795
|
-
if (
|
|
902
|
+
this.#assertTarget(target, blueprint, groups);
|
|
903
|
+
const worktree = this.#worktree(target);
|
|
904
|
+
if (worktree.dirty.length > 0 && command.dirty !== true) throw new ScaffoldError("TARGET", `The target at ${target} carries ${String(worktree.dirty.length)} uncommitted change${worktree.dirty.length === 1 ? "" : "s"}. Commit them, or pass --dirty to waive the refusal.`, {
|
|
796
905
|
target,
|
|
797
|
-
dirty:
|
|
906
|
+
dirty: worktree.dirty.length
|
|
798
907
|
});
|
|
799
|
-
const
|
|
908
|
+
const host = await this.#host(command.from, target, command.offline === true);
|
|
800
909
|
try {
|
|
801
|
-
const [audit, plan] = this.#survey(materializer, blueprint, target, groups);
|
|
910
|
+
const [audit, plan] = this.#survey(host.materializer, blueprint, target, groups);
|
|
802
911
|
if (plan === void 0) {
|
|
803
|
-
if (command.json === true) this.#report(
|
|
912
|
+
if (command.json === true) this.#report({
|
|
913
|
+
...audit,
|
|
914
|
+
provenance: { ...host.baseline === void 0 ? {} : { host: host.baseline } }
|
|
915
|
+
});
|
|
804
916
|
else this.#present(audit);
|
|
805
917
|
return 1;
|
|
806
918
|
}
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
919
|
+
const declared = {
|
|
920
|
+
pins: manifestToWritableDependencies(this.#manifest(target), blueprint),
|
|
921
|
+
scripts: blueprintToWritableScripts(blueprint)
|
|
922
|
+
};
|
|
923
|
+
const repaired = host.materializer.repair(plan, audit, target);
|
|
924
|
+
const removed = host.materializer.remove(plan, audit, command.dirty === true ? {
|
|
925
|
+
tracked: worktree.tracked,
|
|
810
926
|
dirty: []
|
|
811
|
-
} :
|
|
927
|
+
} : worktree, target);
|
|
812
928
|
const offline = this.#merge(repaired, removed);
|
|
813
|
-
const online = await this.#
|
|
814
|
-
const [terminal] = this.#survey(materializer, blueprint, target, groups);
|
|
929
|
+
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);
|
|
930
|
+
const [terminal] = this.#survey(host.materializer, blueprint, target, groups);
|
|
815
931
|
const outcome = {
|
|
816
932
|
...online,
|
|
817
933
|
...this.#merge(offline, online),
|
|
@@ -827,23 +943,64 @@ var CLI = class CLI {
|
|
|
827
943
|
if (online.note !== void 0) return 1;
|
|
828
944
|
return auditToExit(terminal);
|
|
829
945
|
} finally {
|
|
830
|
-
materializer.destroy();
|
|
946
|
+
host.materializer.destroy();
|
|
831
947
|
}
|
|
832
948
|
}
|
|
833
|
-
async #
|
|
949
|
+
async #offline(materializer, target, declared, host) {
|
|
950
|
+
const versions = await this.#versions(declared.pins, true);
|
|
951
|
+
this.#assertVersions(versions);
|
|
952
|
+
return {
|
|
953
|
+
...materializer.declare({
|
|
954
|
+
pins: versions.pins,
|
|
955
|
+
scripts: declared.scripts
|
|
956
|
+
}, target),
|
|
957
|
+
entries: [],
|
|
958
|
+
mirrors: [],
|
|
959
|
+
dropped: [],
|
|
960
|
+
releases: versions.releases,
|
|
961
|
+
provenance: {
|
|
962
|
+
...versions.baseline === void 0 ? {} : { versions: versions.baseline },
|
|
963
|
+
...host === void 0 ? {} : { host }
|
|
964
|
+
},
|
|
965
|
+
note: "The catalog step did not complete: USAGE: 'catalog' does not take --offline."
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
async #reconcile(materializer, target, declared, host, hostForced) {
|
|
834
969
|
const previous = this.#previous(target);
|
|
835
970
|
let releases = [];
|
|
971
|
+
let provenance = { ...host === void 0 ? {} : { host } };
|
|
836
972
|
try {
|
|
837
|
-
|
|
838
|
-
|
|
973
|
+
const versions = await this.#versions(declared.pins, false);
|
|
974
|
+
releases = versions.releases;
|
|
975
|
+
provenance = {
|
|
976
|
+
...versions.baseline === void 0 ? {} : { versions: versions.baseline },
|
|
977
|
+
...host === void 0 ? {} : { host }
|
|
978
|
+
};
|
|
979
|
+
this.#assertVersions(versions);
|
|
839
980
|
const fetched = await this.#fetch(target, false);
|
|
840
981
|
this.#assertFetched(fetched.entries, fetched.mirrors);
|
|
982
|
+
const guides = fetched.mirrors.length === 0 ? void 0 : fetched.mirrors.some((mirror) => mirror.lookup === "failed") ? "floor" : "live";
|
|
983
|
+
provenance = {
|
|
984
|
+
...versions.baseline === void 0 ? {} : { versions: versions.baseline },
|
|
985
|
+
...guides === void 0 ? {} : { guides },
|
|
986
|
+
...host === void 0 ? {} : { host }
|
|
987
|
+
};
|
|
988
|
+
const written = this.#merge(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare({
|
|
989
|
+
pins: versions.pins,
|
|
990
|
+
scripts: declared.scripts
|
|
991
|
+
}, target));
|
|
992
|
+
const floors = [];
|
|
993
|
+
if (hostForced) floors.push("host");
|
|
994
|
+
if (versions.forced) floors.push("versions");
|
|
995
|
+
if (guides === "floor") floors.push("guides");
|
|
841
996
|
return {
|
|
842
|
-
...
|
|
997
|
+
...written,
|
|
843
998
|
entries: fetched.entries,
|
|
844
999
|
mirrors: fetched.mirrors,
|
|
845
1000
|
dropped: previous.filter((name) => !fetched.entries.some((entry) => entry.name === name)),
|
|
846
|
-
releases
|
|
1001
|
+
releases,
|
|
1002
|
+
provenance,
|
|
1003
|
+
...floors.length === 0 ? {} : { note: `The ${floors.join(", ")} step used the distributed floor after its upstream read did not complete.` }
|
|
847
1004
|
};
|
|
848
1005
|
} catch (error) {
|
|
849
1006
|
return {
|
|
@@ -855,10 +1012,121 @@ var CLI = class CLI {
|
|
|
855
1012
|
mirrors: [],
|
|
856
1013
|
dropped: [],
|
|
857
1014
|
releases,
|
|
1015
|
+
provenance,
|
|
858
1016
|
note: `The catalog step did not complete: ${errorToEnvelope(error).error.message}`
|
|
859
1017
|
};
|
|
860
1018
|
}
|
|
861
1019
|
}
|
|
1020
|
+
async #host(from, target, offline) {
|
|
1021
|
+
if (from !== void 0) return {
|
|
1022
|
+
materializer: new Materializer({ host: from }),
|
|
1023
|
+
forced: false
|
|
1024
|
+
};
|
|
1025
|
+
const floor = readHostFloor();
|
|
1026
|
+
if (offline) return {
|
|
1027
|
+
materializer: new Materializer({ host: floor }),
|
|
1028
|
+
baseline: "floor",
|
|
1029
|
+
forced: false
|
|
1030
|
+
};
|
|
1031
|
+
const paths = floor.manifest.entries.filter((entry) => !isDeferredPath(entry.destination)).map((entry) => entry.destination);
|
|
1032
|
+
const current = target === void 0 ? floor.bytes : readSnapshot(target, paths);
|
|
1033
|
+
const upstream = new Upstream(this.#upstream);
|
|
1034
|
+
try {
|
|
1035
|
+
const files = await upstream.read(paths, current);
|
|
1036
|
+
const live = filesToHost(files, floor);
|
|
1037
|
+
return {
|
|
1038
|
+
materializer: new Materializer({ host: live ?? floor }),
|
|
1039
|
+
baseline: live === void 0 ? "floor" : "live",
|
|
1040
|
+
forced: live === void 0
|
|
1041
|
+
};
|
|
1042
|
+
} finally {
|
|
1043
|
+
upstream.destroy();
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
async #versions(declared, offline) {
|
|
1047
|
+
const dependencies = [...declared.runtime, ...declared.development];
|
|
1048
|
+
if (dependencies.length === 0) return {
|
|
1049
|
+
releases: [],
|
|
1050
|
+
pins: {
|
|
1051
|
+
runtime: [],
|
|
1052
|
+
development: []
|
|
1053
|
+
},
|
|
1054
|
+
forced: false,
|
|
1055
|
+
complete: true
|
|
1056
|
+
};
|
|
1057
|
+
const floors = dependenciesToFloors(dependencies);
|
|
1058
|
+
const floorPins = floors === void 0 ? void 0 : {
|
|
1059
|
+
runtime: floors.slice(0, declared.runtime.length).map((release) => ({
|
|
1060
|
+
name: release.name,
|
|
1061
|
+
range: release.range
|
|
1062
|
+
})),
|
|
1063
|
+
development: floors.slice(declared.runtime.length).map((release) => ({
|
|
1064
|
+
name: release.name,
|
|
1065
|
+
range: release.range
|
|
1066
|
+
}))
|
|
1067
|
+
};
|
|
1068
|
+
if (offline) {
|
|
1069
|
+
if (floors === void 0 || floorPins === void 0) return {
|
|
1070
|
+
releases: [],
|
|
1071
|
+
pins: {
|
|
1072
|
+
runtime: [],
|
|
1073
|
+
development: []
|
|
1074
|
+
},
|
|
1075
|
+
baseline: "floor",
|
|
1076
|
+
forced: false,
|
|
1077
|
+
complete: false
|
|
1078
|
+
};
|
|
1079
|
+
return {
|
|
1080
|
+
releases: floors,
|
|
1081
|
+
pins: floorPins,
|
|
1082
|
+
baseline: "floor",
|
|
1083
|
+
forced: false,
|
|
1084
|
+
complete: true
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
const releases = await this.#lookup(dependencies);
|
|
1088
|
+
if (releases.filter((release) => release.lookup === "missing" || release.lookup === "unmatched").length > 0) return {
|
|
1089
|
+
releases,
|
|
1090
|
+
pins: {
|
|
1091
|
+
runtime: [],
|
|
1092
|
+
development: []
|
|
1093
|
+
},
|
|
1094
|
+
baseline: "live",
|
|
1095
|
+
forced: false,
|
|
1096
|
+
complete: false
|
|
1097
|
+
};
|
|
1098
|
+
if (releases.some((release) => release.lookup === "failed")) {
|
|
1099
|
+
if (floorPins === void 0) return {
|
|
1100
|
+
releases,
|
|
1101
|
+
pins: {
|
|
1102
|
+
runtime: [],
|
|
1103
|
+
development: []
|
|
1104
|
+
},
|
|
1105
|
+
baseline: "floor",
|
|
1106
|
+
forced: true,
|
|
1107
|
+
complete: false
|
|
1108
|
+
};
|
|
1109
|
+
return {
|
|
1110
|
+
releases,
|
|
1111
|
+
pins: floorPins,
|
|
1112
|
+
baseline: "floor",
|
|
1113
|
+
forced: true,
|
|
1114
|
+
complete: true
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
return {
|
|
1118
|
+
releases,
|
|
1119
|
+
pins: this.#pin(releases, declared),
|
|
1120
|
+
baseline: "live",
|
|
1121
|
+
forced: false,
|
|
1122
|
+
complete: true
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
#assertVersions(versions) {
|
|
1126
|
+
if (versions.complete) return;
|
|
1127
|
+
const names = versions.releases.filter((release) => release.lookup !== "found").map((release) => release.name);
|
|
1128
|
+
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 });
|
|
1129
|
+
}
|
|
862
1130
|
async #lookup(declared) {
|
|
863
1131
|
if (declared.length === 0) return [];
|
|
864
1132
|
const upstream = new Upstream(this.#upstream);
|
|
@@ -889,7 +1157,12 @@ var CLI = class CLI {
|
|
|
889
1157
|
async #fetch(target, all) {
|
|
890
1158
|
const manifest = this.#manifest(target);
|
|
891
1159
|
const own = manifestToName(manifest);
|
|
892
|
-
const
|
|
1160
|
+
const dependencies = manifestToDependencies(manifest);
|
|
1161
|
+
const declared = [...new Set([
|
|
1162
|
+
...dependencies.runtime,
|
|
1163
|
+
...dependencies.development,
|
|
1164
|
+
...dependencies.peer
|
|
1165
|
+
].map((dependency) => dependency.name))];
|
|
893
1166
|
const upstream = new Upstream(this.#upstream);
|
|
894
1167
|
try {
|
|
895
1168
|
const entries = await upstream.catalog();
|
|
@@ -921,20 +1194,30 @@ var CLI = class CLI {
|
|
|
921
1194
|
});
|
|
922
1195
|
}
|
|
923
1196
|
#assertFetched(entries, mirrors) {
|
|
924
|
-
const failed = [...entries.filter((entry) => entry.lookup !== "found").map((entry) => entry.name), ...mirrors.filter((mirror) => mirror.lookup !== "found").map((mirror) => mirror.name)];
|
|
1197
|
+
const failed = [...entries.filter((entry) => entry.lookup !== "found").map((entry) => entry.name), ...mirrors.filter((mirror) => mirror.lookup !== "found" && mirror.lookup !== "failed").map((mirror) => mirror.name)];
|
|
925
1198
|
if (failed.length === 0) return;
|
|
926
1199
|
throw new ScaffoldError("FETCH", `Upstream produced no complete catalog answer for ${failed.join(", ")}.`, { names: failed.length });
|
|
927
1200
|
}
|
|
928
|
-
#pin(releases) {
|
|
1201
|
+
#pin(releases, declared) {
|
|
929
1202
|
const refused = releases.filter((release) => release.lookup !== "found");
|
|
930
1203
|
if (refused.length > 0) throw new ScaffoldError("FETCH", `The registry named no release for ${refused.map((release) => release.name).join(", ")}.`, { names: refused.length });
|
|
931
|
-
|
|
1204
|
+
const dependencies = [...declared.runtime, ...declared.development];
|
|
1205
|
+
const pins = [];
|
|
1206
|
+
for (let index = 0; index < releases.length; index += 1) {
|
|
1207
|
+
const release = releases[index];
|
|
1208
|
+
const dependency = dependencies[index];
|
|
1209
|
+
if (release === void 0 || dependency === void 0) throw new ScaffoldError("FETCH", "The release answer has no matching declaration.");
|
|
932
1210
|
if (release.lookup !== "found") throw new ScaffoldError("FETCH", `The registry named no release for ${release.name}.`);
|
|
933
|
-
|
|
934
|
-
name:
|
|
1211
|
+
pins.push({
|
|
1212
|
+
name: dependency.name,
|
|
935
1213
|
range: `^${release.latest}`
|
|
936
|
-
};
|
|
937
|
-
}
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
if (pins.length !== dependencies.length) throw new ScaffoldError("FETCH", "The release answer does not match the declaration set.");
|
|
1217
|
+
return {
|
|
1218
|
+
runtime: pins.slice(0, declared.runtime.length),
|
|
1219
|
+
development: pins.slice(declared.runtime.length)
|
|
1220
|
+
};
|
|
938
1221
|
}
|
|
939
1222
|
#compile(blueprint, groups) {
|
|
940
1223
|
const compiler = new Compiler();
|
|
@@ -967,7 +1250,6 @@ var CLI = class CLI {
|
|
|
967
1250
|
const bin = resolveContainedPath(target, BIN_ENTRY_PATH);
|
|
968
1251
|
const tests = resolveContainedPath(target, "tests");
|
|
969
1252
|
const guides = resolveContainedPath(target, GUIDES_TEST_PATH);
|
|
970
|
-
const distribution = resolveContainedPath(target, DISTRIBUTION_TEST_PATH);
|
|
971
1253
|
const integration = resolveContainedPath(target, INTEGRATION_TEST_PATH);
|
|
972
1254
|
const conformance = resolveContainedPath(target, CONFORMANCE_TEST_PATH);
|
|
973
1255
|
const service = resolveContainedPath(target, SERVICE_SETUP_PATH);
|
|
@@ -976,7 +1258,7 @@ var CLI = class CLI {
|
|
|
976
1258
|
return createBlueprint(declared.slice(declared.lastIndexOf("/") + 1), {
|
|
977
1259
|
src: this.#probe(target, "src"),
|
|
978
1260
|
app: this.#probe(target, "app"),
|
|
979
|
-
dependencies: manifestToDependencies(manifest),
|
|
1261
|
+
dependencies: manifestToDependencies(manifest).runtime,
|
|
980
1262
|
bin: bin !== void 0 && isExactCaseFile(bin),
|
|
981
1263
|
setup: tests !== void 0 && listFiles(tests).some((path) => {
|
|
982
1264
|
if (path.includes("/") || !path.startsWith("setup") || !path.endsWith(".test.ts")) return false;
|
|
@@ -984,7 +1266,6 @@ var CLI = class CLI {
|
|
|
984
1266
|
return proof !== void 0 && isExactCaseFile(proof);
|
|
985
1267
|
}),
|
|
986
1268
|
guides: guides !== void 0 && isExactCaseFile(guides),
|
|
987
|
-
distribution: distribution !== void 0 && isExactCaseFile(distribution),
|
|
988
1269
|
integration: integration !== void 0 && isExactCaseFile(integration),
|
|
989
1270
|
conformance: conformance !== void 0 && isExactCaseFile(conformance),
|
|
990
1271
|
service: service !== void 0 && isExactCaseFile(service),
|
|
@@ -1115,7 +1396,8 @@ var CLI = class CLI {
|
|
|
1115
1396
|
};
|
|
1116
1397
|
}
|
|
1117
1398
|
#projectQuestion(target, blueprint, writing = false) {
|
|
1118
|
-
const
|
|
1399
|
+
const text = this.#manifest(target);
|
|
1400
|
+
const manifest = replaceManifestScripts(text, blueprintToWritableScripts(blueprint)) ?? text;
|
|
1119
1401
|
const planned = blueprintToRootVite(blueprint);
|
|
1120
1402
|
const parsed = parseJSON(manifest);
|
|
1121
1403
|
const scripts = isRecord(parsed) && isRecord(parsed.scripts) ? parsed.scripts : {};
|
|
@@ -1135,13 +1417,15 @@ var CLI = class CLI {
|
|
|
1135
1417
|
const projects = [...absent].sort();
|
|
1136
1418
|
if (unresolved) return {
|
|
1137
1419
|
field: "projects",
|
|
1138
|
-
message: `The manifest at ${target} contains a Vitest project expression that cannot be resolved statically.${projects.length === 0 ? "" : ` It also names projects the planned
|
|
1139
|
-
blocking: false
|
|
1420
|
+
message: `The manifest at ${target} contains a Vitest project expression that cannot be resolved statically.${projects.length === 0 ? "" : ` It also names projects the planned vite.config.ts does not register: ${projects.join(", ")}.`} ${writing ? "The configs group is blocked. Replace the expression with a literal --project value before selecting configs, or exclude configs from --groups." : "Replace it with a literal --project value before relying on the planned configuration."}`,
|
|
1421
|
+
blocking: false,
|
|
1422
|
+
groups: ["configs"]
|
|
1140
1423
|
};
|
|
1141
1424
|
if (projects.length > 0) return {
|
|
1142
1425
|
field: "projects",
|
|
1143
|
-
message: writing ? `The manifest at ${target} names ${projects.length === 1 ? "a Vitest project" : "Vitest projects"} the planned
|
|
1144
|
-
blocking: false
|
|
1426
|
+
message: writing ? `The configs group is blocked because the manifest at ${target} names ${projects.length === 1 ? "a Vitest project" : "Vitest projects"} the planned vite.config.ts does not register: ${projects.join(", ")}. Remove the ${projects.length === 1 ? "script that names it" : "scripts that name them"} before selecting configs, or exclude configs from --groups.` : `The manifest at ${target} names ${projects.length === 1 ? "a Vitest project" : "Vitest projects"} the planned configuration does not register: ${projects.join(", ")}. Add ${projects.length === 1 ? "the project" : "each project"} to vite.config.ts or remove the ${projects.length === 1 ? "script that names it" : "scripts that name them"}.`,
|
|
1427
|
+
blocking: false,
|
|
1428
|
+
groups: ["configs"]
|
|
1145
1429
|
};
|
|
1146
1430
|
const expected = blueprintToScripts(blueprint);
|
|
1147
1431
|
const expectedLines = /* @__PURE__ */ new Map();
|
|
@@ -1175,8 +1459,9 @@ var CLI = class CLI {
|
|
|
1175
1459
|
}
|
|
1176
1460
|
if (unresolved) return {
|
|
1177
1461
|
field: "projects",
|
|
1178
|
-
message: `The manifest at ${target} contains a ${gateNames} chain that cannot be resolved statically. ${writing ? "Replace it with literal npm run script names before
|
|
1179
|
-
blocking: false
|
|
1462
|
+
message: `The manifest at ${target} contains a ${gateNames} chain that cannot be resolved statically. ${writing ? "The configs group is blocked. Replace it with literal npm run script names before selecting configs, or exclude configs from --groups." : "Replace it with literal npm run script names before relying on the planned configuration."}`,
|
|
1463
|
+
blocking: false,
|
|
1464
|
+
groups: ["configs"]
|
|
1180
1465
|
};
|
|
1181
1466
|
const missing = [...expectedLines].filter(([project]) => !reachable.has(project)).sort(([left], [right]) => left.localeCompare(right));
|
|
1182
1467
|
if (missing.length === 0) return void 0;
|
|
@@ -1194,8 +1479,9 @@ var CLI = class CLI {
|
|
|
1194
1479
|
}
|
|
1195
1480
|
return {
|
|
1196
1481
|
field: "projects",
|
|
1197
|
-
message:
|
|
1198
|
-
blocking: false
|
|
1482
|
+
message: `${writing ? "The configs group is blocked because " : ""}the manifest at ${target} does not reach ${names.length === 1 ? "a Vitest project" : "Vitest projects"} the planned configuration registers: ${names.join(", ")}. No chain from ${gateNames} invokes ${names.length === 1 ? "it" : "them"}. ${remedies.join(" ")}${writing ? " Exclude configs from --groups to write another group." : ""}`,
|
|
1483
|
+
blocking: false,
|
|
1484
|
+
groups: ["configs"]
|
|
1199
1485
|
};
|
|
1200
1486
|
}
|
|
1201
1487
|
#dependencyQuestion(target, blueprint, writing = false) {
|
|
@@ -1204,8 +1490,9 @@ var CLI = class CLI {
|
|
|
1204
1490
|
const malformed = ["dependencies", "devDependencies"].filter((section) => parsed[section] !== void 0 && !isRecord(parsed[section]));
|
|
1205
1491
|
if (malformed.length > 0) return {
|
|
1206
1492
|
field: "dependencies",
|
|
1207
|
-
message: `The manifest at ${target} declares ${malformed.join(" and ")} as ${malformed.length === 1 ? "a value" : "values"} that ${malformed.length === 1 ? "is" : "are"} not ${malformed.length === 1 ? "an object" : "objects"}. Replace ${malformed.length === 1 ? "it" : "them"} with ${malformed.length === 1 ? "an object" : "objects"} before ${
|
|
1208
|
-
blocking: false
|
|
1493
|
+
message: `The manifest at ${target} declares ${malformed.join(" and ")} as ${malformed.length === 1 ? "a value" : "values"} that ${malformed.length === 1 ? "is" : "are"} not ${malformed.length === 1 ? "an object" : "objects"}. ${writing ? `The configs and tests groups are blocked. Replace ${malformed.length === 1 ? "it" : "them"} with ${malformed.length === 1 ? "an object" : "objects"} before selecting configs or tests, or exclude those groups from --groups.` : `Replace ${malformed.length === 1 ? "it" : "them"} with ${malformed.length === 1 ? "an object" : "objects"} before relying on the planned dependency set.`}`,
|
|
1494
|
+
blocking: false,
|
|
1495
|
+
groups: ["configs", "tests"]
|
|
1209
1496
|
};
|
|
1210
1497
|
const dependencies = isRecord(parsed.dependencies) ? parsed.dependencies : {};
|
|
1211
1498
|
const development = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
|
|
@@ -1215,20 +1502,56 @@ var CLI = class CLI {
|
|
|
1215
1502
|
const lines = missing.map(([name, range]) => `${JSON.stringify(name)}: ${JSON.stringify(range)},`);
|
|
1216
1503
|
return {
|
|
1217
1504
|
field: "dependencies",
|
|
1218
|
-
message: `The manifest at ${target} does not declare ${names.length === 1 ? "a planned dependency" : "planned dependencies"}: ${names.join(", ")}. ${writing ? "
|
|
1219
|
-
blocking: false
|
|
1505
|
+
message: `The manifest at ${target} does not declare ${names.length === 1 ? "a planned dependency" : "planned dependencies"}: ${names.join(", ")}. ${writing ? "The configs and tests groups are blocked. Add" : "Add"} ${lines.length === 1 ? "this exact dependency line" : "these exact dependency lines"} to dependencies or devDependencies in package.json: ${lines.join(" ")}${writing ? " Add the dependency before selecting configs or tests, or exclude those groups from --groups." : ""}`,
|
|
1506
|
+
blocking: false,
|
|
1507
|
+
groups: ["configs", "tests"]
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
#setupQuestion(target, blueprint) {
|
|
1511
|
+
const tests = resolveContainedPath(target, "tests");
|
|
1512
|
+
if (tests === void 0) return void 0;
|
|
1513
|
+
const entries = listFiles(tests).filter((path) => !path.includes("/"));
|
|
1514
|
+
const proofs = new Set(entries.filter((path) => path.endsWith(".test.ts")));
|
|
1515
|
+
const seeds = new Map(blueprintToTestArtifacts(blueprint).map(({ path, content }) => [path, content.trim()]));
|
|
1516
|
+
const modules = entries.filter((path) => {
|
|
1517
|
+
if (!path.startsWith("setup") || !path.endsWith(".ts")) return false;
|
|
1518
|
+
if (path.endsWith(".test.ts") || HOST_PATHS.includes(`tests/${path}`)) return false;
|
|
1519
|
+
if (proofs.has(`${path.slice(0, -3)}.test.ts`)) return false;
|
|
1520
|
+
if (resolveContainedPath(tests, path) === void 0) return false;
|
|
1521
|
+
const content = (readFileText(tests, path) ?? "").trim();
|
|
1522
|
+
return content !== "" && content !== (seeds.get(`tests/${path}`) ?? "");
|
|
1523
|
+
}).sort();
|
|
1524
|
+
if (modules.length === 0) return void 0;
|
|
1525
|
+
const single = modules.length === 1;
|
|
1526
|
+
const named = modules.map((path) => `tests/${path}`).join(", ");
|
|
1527
|
+
const remedies = modules.map((path) => `tests/${path.slice(0, -3)}.test.ts`).join(", ");
|
|
1528
|
+
const remedy = single ? `Add ${remedies} to cover it.` : `Add ${remedies}, each covering the module of the same name.`;
|
|
1529
|
+
return {
|
|
1530
|
+
field: "setup",
|
|
1531
|
+
message: `The target at ${target} carries ${single ? "a test setup module" : "test setup modules"} that no proof covers: ${named}. ${remedy} The proof's subject is behavior only this workspace can assert, so scaffold does not write it.`,
|
|
1532
|
+
blocking: false,
|
|
1533
|
+
groups: ["tests"]
|
|
1220
1534
|
};
|
|
1221
1535
|
}
|
|
1222
|
-
#targetQuestions(target, blueprint, writing = false) {
|
|
1536
|
+
#targetQuestions(target, blueprint, groups, writing = false) {
|
|
1223
1537
|
const questions = [];
|
|
1224
1538
|
const project = this.#projectQuestion(target, blueprint, writing);
|
|
1225
1539
|
if (project !== void 0) questions.push(project);
|
|
1226
1540
|
const dependency = this.#dependencyQuestion(target, blueprint, writing);
|
|
1227
1541
|
if (dependency !== void 0) questions.push(dependency);
|
|
1228
|
-
|
|
1542
|
+
if (!writing) {
|
|
1543
|
+
const setup = this.#setupQuestion(target, blueprint);
|
|
1544
|
+
if (setup !== void 0) questions.push(setup);
|
|
1545
|
+
}
|
|
1546
|
+
return questions.filter((question) => groups === void 0 || question.groups.some((group) => groups.includes(group))).map(({ field, message, blocking, candidates }) => ({
|
|
1547
|
+
field,
|
|
1548
|
+
message,
|
|
1549
|
+
blocking,
|
|
1550
|
+
...candidates === void 0 ? {} : { candidates }
|
|
1551
|
+
}));
|
|
1229
1552
|
}
|
|
1230
|
-
#assertTarget(target, blueprint) {
|
|
1231
|
-
const questions = this.#targetQuestions(target, blueprint, true);
|
|
1553
|
+
#assertTarget(target, blueprint, groups) {
|
|
1554
|
+
const questions = this.#targetQuestions(target, blueprint, groups, true);
|
|
1232
1555
|
if (questions.length === 0) return;
|
|
1233
1556
|
throw new ScaffoldError("TARGET", questions.map((question) => question.message).join(" "), { target });
|
|
1234
1557
|
}
|
|
@@ -1255,7 +1578,7 @@ var CLI = class CLI {
|
|
|
1255
1578
|
}
|
|
1256
1579
|
return names;
|
|
1257
1580
|
}
|
|
1258
|
-
#
|
|
1581
|
+
#worktree(target) {
|
|
1259
1582
|
const tracked = this.#inventory(target, ["ls-files", "-z"]);
|
|
1260
1583
|
const dirty = this.#inventory(target, [
|
|
1261
1584
|
"status",
|
|
@@ -1267,7 +1590,7 @@ var CLI = class CLI {
|
|
|
1267
1590
|
tracked,
|
|
1268
1591
|
dirty
|
|
1269
1592
|
};
|
|
1270
|
-
if (!
|
|
1593
|
+
if (!isWorktree(state)) throw new ScaffoldError("TARGET", `The git state at ${target} is not a readable inventory.`, {
|
|
1271
1594
|
target,
|
|
1272
1595
|
tracked: tracked.length,
|
|
1273
1596
|
dirty: dirty.length
|
|
@@ -1315,6 +1638,13 @@ var CLI = class CLI {
|
|
|
1315
1638
|
removed: [...first.removed, ...second.removed]
|
|
1316
1639
|
};
|
|
1317
1640
|
}
|
|
1641
|
+
#reportBaselines(provenance) {
|
|
1642
|
+
const baselines = [];
|
|
1643
|
+
if (provenance.versions !== void 0) baselines.push(`versions=${provenance.versions}`);
|
|
1644
|
+
if (provenance.guides !== void 0) baselines.push(`guides=${provenance.guides}`);
|
|
1645
|
+
if (provenance.host !== void 0) baselines.push(`host=${provenance.host}`);
|
|
1646
|
+
this.#warn(`Upstream fallback selected the distributed baseline: ${baselines.join(", ")}.`);
|
|
1647
|
+
}
|
|
1318
1648
|
#present(audit) {
|
|
1319
1649
|
for (const question of audit.questions) this.#warn(`${question.field}: ${question.message}`);
|
|
1320
1650
|
const rows = audit.findings.filter((finding) => finding.drift !== "aligned").map((finding) => [
|
|
@@ -1393,7 +1723,7 @@ process.stdout.on("error", (error) => {
|
|
|
1393
1723
|
if ("code" in error && error.code === "EPIPE") return;
|
|
1394
1724
|
throw error;
|
|
1395
1725
|
});
|
|
1396
|
-
process.exitCode = await new CLI(
|
|
1726
|
+
process.exitCode = await new CLI({ upstream: environmentToUpstream(process.env) }).execute(process.argv.slice(2));
|
|
1397
1727
|
//#endregion
|
|
1398
1728
|
|
|
1399
1729
|
//# sourceMappingURL=main.js.map
|