@orkestrel/scaffold 0.0.49 → 0.0.51
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 +229 -88
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +2 -2
- package/dist/host/agents/orchestration.md +42 -15
- package/dist/host/claude/agents/orkestrel.md +54 -54
- package/dist/host/claude/rules/architecture.md +6 -6
- package/dist/host/claude/rules/tests.md +10 -4
- package/dist/host/claude/rules/workspace.md +1 -1
- package/dist/host/codex/config.toml +1 -1
- package/dist/host/guides/scaffold.md +353 -117
- package/dist/host/manifest.json +10 -10
- 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 +83 -5
- package/dist/src/core/index.cjs +1683 -431
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +222 -55
- package/dist/src/core/index.d.ts +222 -55
- package/dist/src/core/index.js +1678 -432
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +47 -15
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +42 -15
- package/dist/src/server/index.d.ts +42 -15
- package/dist/src/server/index.js +48 -17
- package/dist/src/server/index.js.map +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -55,8 +55,10 @@ npx scaffold repair
|
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Restores each planned path the target is missing or has let drift, then re-audits. A file the
|
|
58
|
-
workspace owns — its
|
|
59
|
-
is
|
|
58
|
+
workspace owns — its source, its own proofs, its README — is written once at creation and is never
|
|
59
|
+
rewritten here. Two paths are not owned that way: `tests/distribution.test.ts` is restored when it
|
|
60
|
+
is absent and left alone when the workspace has replaced it, and the manifest's script region is
|
|
61
|
+
rewritten when its chain is the one scaffold generated and refused without a write when it is not.
|
|
60
62
|
|
|
61
63
|
### `catalog` — refresh the package table and the guide mirrors
|
|
62
64
|
|
package/dist/bin/main.js
CHANGED
|
@@ -3,7 +3,7 @@ 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,
|
|
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
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
|
|
@@ -463,27 +463,43 @@ function auditToExit(audit) {
|
|
|
463
463
|
return blocked || drifted ? 1 : 0;
|
|
464
464
|
}
|
|
465
465
|
/**
|
|
466
|
-
* Read the
|
|
466
|
+
* Read 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.
|
|
470
|
-
* @returns The declared fleet rows
|
|
470
|
+
* @returns The declared fleet rows and planned foreign tools in their writable sections.
|
|
471
471
|
*/
|
|
472
|
-
function
|
|
473
|
-
const
|
|
472
|
+
function manifestToWritableDependencies(manifest, blueprint) {
|
|
473
|
+
const declared = manifestToDependencies(manifest);
|
|
474
|
+
const runtime = [...dependenciesToFleet(declared.runtime)];
|
|
475
|
+
const development = [...dependenciesToFleet(declared.development)];
|
|
474
476
|
const parsed = parseJSON(manifest);
|
|
475
|
-
if (!isRecord(parsed)) return
|
|
476
|
-
|
|
477
|
-
|
|
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 : {};
|
|
478
483
|
for (const name of Object.keys(blueprintToDevDependencies(blueprint)).sort()) {
|
|
479
484
|
if (name.startsWith("@orkestrel/")) continue;
|
|
480
|
-
const
|
|
481
|
-
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({
|
|
482
495
|
name,
|
|
483
|
-
range
|
|
496
|
+
range: developmentRange
|
|
484
497
|
});
|
|
485
498
|
}
|
|
486
|
-
return
|
|
499
|
+
return {
|
|
500
|
+
runtime,
|
|
501
|
+
development
|
|
502
|
+
};
|
|
487
503
|
}
|
|
488
504
|
/**
|
|
489
505
|
* Keep only dependencies published by the Orkestrel fleet.
|
|
@@ -730,7 +746,11 @@ var CLI = class CLI {
|
|
|
730
746
|
const plan = this.#compile(blueprint);
|
|
731
747
|
const manifest = plan.artifacts.find((artifact) => artifact.path === "package.json" && artifact.origin !== "host");
|
|
732
748
|
if (manifest === void 0 || manifest.origin === "host") throw new ScaffoldError("BLOCKED", "The compiled plan carries no replaceable manifest.");
|
|
733
|
-
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);
|
|
734
754
|
this.#assertVersions(versions);
|
|
735
755
|
const resolved = replacePlanRanges(plan, versions.pins);
|
|
736
756
|
if (resolved === void 0) throw new ScaffoldError("BLOCKED", "The compiled plan ranges could not be replaced.");
|
|
@@ -760,9 +780,9 @@ var CLI = class CLI {
|
|
|
760
780
|
const manifest = this.#manifest(target);
|
|
761
781
|
const blueprint = this.#derive(target);
|
|
762
782
|
const groups = this.#groups(command.groups);
|
|
763
|
-
const declared =
|
|
783
|
+
const declared = manifestToWritableDependencies(manifest, blueprint);
|
|
764
784
|
const versions = await this.#versions(declared, command.offline === true);
|
|
765
|
-
const questions = [...this.#targetQuestions(target, blueprint), ...releasesToQuestions(versions.releases)];
|
|
785
|
+
const questions = [...this.#targetQuestions(target, blueprint, groups), ...releasesToQuestions(versions.releases)];
|
|
766
786
|
const host = await this.#host(command.from, target, command.offline === true);
|
|
767
787
|
try {
|
|
768
788
|
const [measured] = this.#survey(host.materializer, blueprint, target, groups);
|
|
@@ -794,7 +814,7 @@ var CLI = class CLI {
|
|
|
794
814
|
const target = command.target ?? ".";
|
|
795
815
|
const groups = this.#groups(command.groups);
|
|
796
816
|
const blueprint = this.#derive(target);
|
|
797
|
-
this.#assertTarget(target, blueprint);
|
|
817
|
+
this.#assertTarget(target, blueprint, groups);
|
|
798
818
|
const host = await this.#host(command.from, target, command.offline === true);
|
|
799
819
|
try {
|
|
800
820
|
const [audit, plan] = this.#survey(host.materializer, blueprint, target, groups);
|
|
@@ -806,10 +826,15 @@ var CLI = class CLI {
|
|
|
806
826
|
else this.#present(audit);
|
|
807
827
|
return 1;
|
|
808
828
|
}
|
|
809
|
-
const versions = await this.#versions(
|
|
829
|
+
const versions = await this.#versions(manifestToWritableDependencies(this.#manifest(target), blueprint), command.offline === true);
|
|
810
830
|
this.#assertVersions(versions);
|
|
811
|
-
const result = this.#merge(host.materializer.repair(plan, audit, target), host.materializer.declare(
|
|
812
|
-
|
|
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 terminalBlueprint = this.#derive(target);
|
|
836
|
+
const [measured] = this.#survey(host.materializer, terminalBlueprint, target, groups);
|
|
837
|
+
const terminal = this.#appendQuestions(measured, target, terminalBlueprint, groups);
|
|
813
838
|
const outcome = {
|
|
814
839
|
...result,
|
|
815
840
|
audit: terminal,
|
|
@@ -838,14 +863,22 @@ var CLI = class CLI {
|
|
|
838
863
|
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.`);
|
|
839
864
|
const previous = this.#previous(target);
|
|
840
865
|
const fetched = await this.#fetch(target, command.all === true);
|
|
841
|
-
const
|
|
842
|
-
const
|
|
866
|
+
const declarations = manifestToDependencies(this.#manifest(target));
|
|
867
|
+
const writable = {
|
|
868
|
+
runtime: dependenciesToFleet(declarations.runtime),
|
|
869
|
+
development: dependenciesToFleet(declarations.development)
|
|
870
|
+
};
|
|
871
|
+
const releases = this.#catalogReleases([...writable.runtime, ...writable.development], fetched.entries);
|
|
872
|
+
const pins = this.#pin(releases, writable);
|
|
843
873
|
this.#assertFetched(fetched.entries, fetched.mirrors);
|
|
844
874
|
const guides = fetched.mirrors.length === 0 ? void 0 : fetched.mirrors.some((mirror) => mirror.lookup === "failed") ? "floor" : "live";
|
|
845
875
|
const materializer = new Materializer({ host: host ?? readHostFloor() });
|
|
846
876
|
let result;
|
|
847
877
|
try {
|
|
848
|
-
result = this.#merge(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare(
|
|
878
|
+
result = this.#merge(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare({
|
|
879
|
+
pins,
|
|
880
|
+
scripts: []
|
|
881
|
+
}, target));
|
|
849
882
|
} finally {
|
|
850
883
|
materializer.destroy();
|
|
851
884
|
}
|
|
@@ -868,7 +901,7 @@ var CLI = class CLI {
|
|
|
868
901
|
const target = command.target ?? ".";
|
|
869
902
|
const groups = this.#groups(command.groups);
|
|
870
903
|
const blueprint = this.#derive(target);
|
|
871
|
-
this.#assertTarget(target, blueprint);
|
|
904
|
+
this.#assertTarget(target, blueprint, groups);
|
|
872
905
|
const worktree = this.#worktree(target);
|
|
873
906
|
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.`, {
|
|
874
907
|
target,
|
|
@@ -885,7 +918,10 @@ var CLI = class CLI {
|
|
|
885
918
|
else this.#present(audit);
|
|
886
919
|
return 1;
|
|
887
920
|
}
|
|
888
|
-
const declared =
|
|
921
|
+
const declared = {
|
|
922
|
+
pins: manifestToWritableDependencies(this.#manifest(target), blueprint),
|
|
923
|
+
scripts: blueprintToWritableScripts(blueprint)
|
|
924
|
+
};
|
|
889
925
|
const repaired = host.materializer.repair(plan, audit, target);
|
|
890
926
|
const removed = host.materializer.remove(plan, audit, command.dirty === true ? {
|
|
891
927
|
tracked: worktree.tracked,
|
|
@@ -893,7 +929,8 @@ var CLI = class CLI {
|
|
|
893
929
|
} : worktree, target);
|
|
894
930
|
const offline = this.#merge(repaired, removed);
|
|
895
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);
|
|
896
|
-
const [
|
|
932
|
+
const [measured] = this.#survey(host.materializer, blueprint, target, groups);
|
|
933
|
+
const terminal = this.#appendQuestions(measured, target, blueprint, groups);
|
|
897
934
|
const outcome = {
|
|
898
935
|
...online,
|
|
899
936
|
...this.#merge(offline, online),
|
|
@@ -913,10 +950,13 @@ var CLI = class CLI {
|
|
|
913
950
|
}
|
|
914
951
|
}
|
|
915
952
|
async #offline(materializer, target, declared, host) {
|
|
916
|
-
const versions = await this.#versions(declared, true);
|
|
953
|
+
const versions = await this.#versions(declared.pins, true);
|
|
917
954
|
this.#assertVersions(versions);
|
|
918
955
|
return {
|
|
919
|
-
...materializer.declare(
|
|
956
|
+
...materializer.declare({
|
|
957
|
+
pins: versions.pins,
|
|
958
|
+
scripts: declared.scripts
|
|
959
|
+
}, target),
|
|
920
960
|
entries: [],
|
|
921
961
|
mirrors: [],
|
|
922
962
|
dropped: [],
|
|
@@ -933,7 +973,7 @@ var CLI = class CLI {
|
|
|
933
973
|
let releases = [];
|
|
934
974
|
let provenance = { ...host === void 0 ? {} : { host } };
|
|
935
975
|
try {
|
|
936
|
-
const versions = await this.#versions(declared, false);
|
|
976
|
+
const versions = await this.#versions(declared.pins, false);
|
|
937
977
|
releases = versions.releases;
|
|
938
978
|
provenance = {
|
|
939
979
|
...versions.baseline === void 0 ? {} : { versions: versions.baseline },
|
|
@@ -948,7 +988,10 @@ var CLI = class CLI {
|
|
|
948
988
|
...guides === void 0 ? {} : { guides },
|
|
949
989
|
...host === void 0 ? {} : { host }
|
|
950
990
|
};
|
|
951
|
-
const written = this.#merge(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare(
|
|
991
|
+
const written = this.#merge(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare({
|
|
992
|
+
pins: versions.pins,
|
|
993
|
+
scripts: declared.scripts
|
|
994
|
+
}, target));
|
|
952
995
|
const floors = [];
|
|
953
996
|
if (hostForced) floors.push("host");
|
|
954
997
|
if (versions.forced) floors.push("versions");
|
|
@@ -1004,54 +1047,71 @@ var CLI = class CLI {
|
|
|
1004
1047
|
}
|
|
1005
1048
|
}
|
|
1006
1049
|
async #versions(declared, offline) {
|
|
1007
|
-
|
|
1050
|
+
const dependencies = [...declared.runtime, ...declared.development];
|
|
1051
|
+
if (dependencies.length === 0) return {
|
|
1008
1052
|
releases: [],
|
|
1009
|
-
pins:
|
|
1053
|
+
pins: {
|
|
1054
|
+
runtime: [],
|
|
1055
|
+
development: []
|
|
1056
|
+
},
|
|
1010
1057
|
forced: false,
|
|
1011
1058
|
complete: true
|
|
1012
1059
|
};
|
|
1013
|
-
const floors = dependenciesToFloors(
|
|
1060
|
+
const floors = dependenciesToFloors(dependencies);
|
|
1061
|
+
const floorPins = floors === void 0 ? void 0 : {
|
|
1062
|
+
runtime: floors.slice(0, declared.runtime.length).map((release) => ({
|
|
1063
|
+
name: release.name,
|
|
1064
|
+
range: release.range
|
|
1065
|
+
})),
|
|
1066
|
+
development: floors.slice(declared.runtime.length).map((release) => ({
|
|
1067
|
+
name: release.name,
|
|
1068
|
+
range: release.range
|
|
1069
|
+
}))
|
|
1070
|
+
};
|
|
1014
1071
|
if (offline) {
|
|
1015
|
-
if (floors === void 0) return {
|
|
1072
|
+
if (floors === void 0 || floorPins === void 0) return {
|
|
1016
1073
|
releases: [],
|
|
1017
|
-
pins:
|
|
1074
|
+
pins: {
|
|
1075
|
+
runtime: [],
|
|
1076
|
+
development: []
|
|
1077
|
+
},
|
|
1018
1078
|
baseline: "floor",
|
|
1019
1079
|
forced: false,
|
|
1020
1080
|
complete: false
|
|
1021
1081
|
};
|
|
1022
1082
|
return {
|
|
1023
1083
|
releases: floors,
|
|
1024
|
-
pins:
|
|
1025
|
-
name: release.name,
|
|
1026
|
-
range: release.range
|
|
1027
|
-
})),
|
|
1084
|
+
pins: floorPins,
|
|
1028
1085
|
baseline: "floor",
|
|
1029
1086
|
forced: false,
|
|
1030
1087
|
complete: true
|
|
1031
1088
|
};
|
|
1032
1089
|
}
|
|
1033
|
-
const releases = await this.#lookup(
|
|
1090
|
+
const releases = await this.#lookup(dependencies);
|
|
1034
1091
|
if (releases.filter((release) => release.lookup === "missing" || release.lookup === "unmatched").length > 0) return {
|
|
1035
1092
|
releases,
|
|
1036
|
-
pins:
|
|
1093
|
+
pins: {
|
|
1094
|
+
runtime: [],
|
|
1095
|
+
development: []
|
|
1096
|
+
},
|
|
1037
1097
|
baseline: "live",
|
|
1038
1098
|
forced: false,
|
|
1039
1099
|
complete: false
|
|
1040
1100
|
};
|
|
1041
1101
|
if (releases.some((release) => release.lookup === "failed")) {
|
|
1042
|
-
if (
|
|
1102
|
+
if (floorPins === void 0) return {
|
|
1043
1103
|
releases,
|
|
1044
|
-
pins:
|
|
1104
|
+
pins: {
|
|
1105
|
+
runtime: [],
|
|
1106
|
+
development: []
|
|
1107
|
+
},
|
|
1045
1108
|
baseline: "floor",
|
|
1046
1109
|
forced: true,
|
|
1047
1110
|
complete: false
|
|
1048
1111
|
};
|
|
1049
1112
|
return {
|
|
1050
1113
|
releases,
|
|
1051
|
-
pins:
|
|
1052
|
-
name: release.name,
|
|
1053
|
-
range: release.range
|
|
1054
|
-
})),
|
|
1114
|
+
pins: floorPins,
|
|
1055
1115
|
baseline: "floor",
|
|
1056
1116
|
forced: true,
|
|
1057
1117
|
complete: true
|
|
@@ -1059,7 +1119,7 @@ var CLI = class CLI {
|
|
|
1059
1119
|
}
|
|
1060
1120
|
return {
|
|
1061
1121
|
releases,
|
|
1062
|
-
pins: this.#pin(releases),
|
|
1122
|
+
pins: this.#pin(releases, declared),
|
|
1063
1123
|
baseline: "live",
|
|
1064
1124
|
forced: false,
|
|
1065
1125
|
complete: true
|
|
@@ -1100,7 +1160,12 @@ var CLI = class CLI {
|
|
|
1100
1160
|
async #fetch(target, all) {
|
|
1101
1161
|
const manifest = this.#manifest(target);
|
|
1102
1162
|
const own = manifestToName(manifest);
|
|
1103
|
-
const
|
|
1163
|
+
const dependencies = manifestToDependencies(manifest);
|
|
1164
|
+
const declared = [...new Set([
|
|
1165
|
+
...dependencies.runtime,
|
|
1166
|
+
...dependencies.development,
|
|
1167
|
+
...dependencies.peer
|
|
1168
|
+
].map((dependency) => dependency.name))];
|
|
1104
1169
|
const upstream = new Upstream(this.#upstream);
|
|
1105
1170
|
try {
|
|
1106
1171
|
const entries = await upstream.catalog();
|
|
@@ -1136,16 +1201,26 @@ var CLI = class CLI {
|
|
|
1136
1201
|
if (failed.length === 0) return;
|
|
1137
1202
|
throw new ScaffoldError("FETCH", `Upstream produced no complete catalog answer for ${failed.join(", ")}.`, { names: failed.length });
|
|
1138
1203
|
}
|
|
1139
|
-
#pin(releases) {
|
|
1204
|
+
#pin(releases, declared) {
|
|
1140
1205
|
const refused = releases.filter((release) => release.lookup !== "found");
|
|
1141
1206
|
if (refused.length > 0) throw new ScaffoldError("FETCH", `The registry named no release for ${refused.map((release) => release.name).join(", ")}.`, { names: refused.length });
|
|
1142
|
-
|
|
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.");
|
|
1143
1213
|
if (release.lookup !== "found") throw new ScaffoldError("FETCH", `The registry named no release for ${release.name}.`);
|
|
1144
|
-
|
|
1145
|
-
name:
|
|
1214
|
+
pins.push({
|
|
1215
|
+
name: dependency.name,
|
|
1146
1216
|
range: `^${release.latest}`
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
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
|
+
};
|
|
1149
1224
|
}
|
|
1150
1225
|
#compile(blueprint, groups) {
|
|
1151
1226
|
const compiler = new Compiler();
|
|
@@ -1178,7 +1253,6 @@ var CLI = class CLI {
|
|
|
1178
1253
|
const bin = resolveContainedPath(target, BIN_ENTRY_PATH);
|
|
1179
1254
|
const tests = resolveContainedPath(target, "tests");
|
|
1180
1255
|
const guides = resolveContainedPath(target, GUIDES_TEST_PATH);
|
|
1181
|
-
const distribution = resolveContainedPath(target, DISTRIBUTION_TEST_PATH);
|
|
1182
1256
|
const integration = resolveContainedPath(target, INTEGRATION_TEST_PATH);
|
|
1183
1257
|
const conformance = resolveContainedPath(target, CONFORMANCE_TEST_PATH);
|
|
1184
1258
|
const service = resolveContainedPath(target, SERVICE_SETUP_PATH);
|
|
@@ -1187,7 +1261,7 @@ var CLI = class CLI {
|
|
|
1187
1261
|
return createBlueprint(declared.slice(declared.lastIndexOf("/") + 1), {
|
|
1188
1262
|
src: this.#probe(target, "src"),
|
|
1189
1263
|
app: this.#probe(target, "app"),
|
|
1190
|
-
dependencies: manifestToDependencies(manifest),
|
|
1264
|
+
dependencies: manifestToDependencies(manifest).runtime,
|
|
1191
1265
|
bin: bin !== void 0 && isExactCaseFile(bin),
|
|
1192
1266
|
setup: tests !== void 0 && listFiles(tests).some((path) => {
|
|
1193
1267
|
if (path.includes("/") || !path.startsWith("setup") || !path.endsWith(".test.ts")) return false;
|
|
@@ -1195,7 +1269,6 @@ var CLI = class CLI {
|
|
|
1195
1269
|
return proof !== void 0 && isExactCaseFile(proof);
|
|
1196
1270
|
}),
|
|
1197
1271
|
guides: guides !== void 0 && isExactCaseFile(guides),
|
|
1198
|
-
distribution: distribution !== void 0 && isExactCaseFile(distribution),
|
|
1199
1272
|
integration: integration !== void 0 && isExactCaseFile(integration),
|
|
1200
1273
|
conformance: conformance !== void 0 && isExactCaseFile(conformance),
|
|
1201
1274
|
service: service !== void 0 && isExactCaseFile(service),
|
|
@@ -1326,7 +1399,8 @@ var CLI = class CLI {
|
|
|
1326
1399
|
};
|
|
1327
1400
|
}
|
|
1328
1401
|
#projectQuestion(target, blueprint, writing = false) {
|
|
1329
|
-
const
|
|
1402
|
+
const text = this.#manifest(target);
|
|
1403
|
+
const manifest = replaceManifestScripts(text, blueprintToWritableScripts(blueprint)) ?? text;
|
|
1330
1404
|
const planned = blueprintToRootVite(blueprint);
|
|
1331
1405
|
const parsed = parseJSON(manifest);
|
|
1332
1406
|
const scripts = isRecord(parsed) && isRecord(parsed.scripts) ? parsed.scripts : {};
|
|
@@ -1346,13 +1420,15 @@ var CLI = class CLI {
|
|
|
1346
1420
|
const projects = [...absent].sort();
|
|
1347
1421
|
if (unresolved) return {
|
|
1348
1422
|
field: "projects",
|
|
1349
|
-
message: `The manifest at ${target} contains a Vitest project expression that cannot be resolved statically.${projects.length === 0 ? "" : ` It also names projects the planned
|
|
1350
|
-
blocking: false
|
|
1423
|
+
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."}`,
|
|
1424
|
+
blocking: false,
|
|
1425
|
+
groups: ["configs"]
|
|
1351
1426
|
};
|
|
1352
1427
|
if (projects.length > 0) return {
|
|
1353
1428
|
field: "projects",
|
|
1354
|
-
message: writing ? `The manifest at ${target} names ${projects.length === 1 ? "a Vitest project" : "Vitest projects"} the planned
|
|
1355
|
-
blocking: false
|
|
1429
|
+
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"}.`,
|
|
1430
|
+
blocking: false,
|
|
1431
|
+
groups: ["configs"]
|
|
1356
1432
|
};
|
|
1357
1433
|
const expected = blueprintToScripts(blueprint);
|
|
1358
1434
|
const expectedLines = /* @__PURE__ */ new Map();
|
|
@@ -1386,27 +1462,44 @@ var CLI = class CLI {
|
|
|
1386
1462
|
}
|
|
1387
1463
|
if (unresolved) return {
|
|
1388
1464
|
field: "projects",
|
|
1389
|
-
message: `The manifest at ${target} contains a ${gateNames} chain that cannot be resolved statically. ${writing ? "Replace it with literal npm run script names before
|
|
1390
|
-
blocking: false
|
|
1465
|
+
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."}`,
|
|
1466
|
+
blocking: false,
|
|
1467
|
+
groups: ["configs"]
|
|
1391
1468
|
};
|
|
1392
|
-
const
|
|
1393
|
-
if (
|
|
1394
|
-
const names =
|
|
1395
|
-
const
|
|
1396
|
-
const ungated = missing.filter(([project]) => isString(scripts[`test:${project}`]));
|
|
1397
|
-
const remedies = [];
|
|
1398
|
-
if (ungated.length > 0) {
|
|
1399
|
-
const declared = ungated.map(([project]) => `test:${project}`);
|
|
1400
|
-
remedies.push(`${declared.join(", ")} ${declared.length === 1 ? "is" : "are"} already declared, so the gate is missing rather than the script: invoke ${declared.length === 1 ? "it" : "each of them"} by name from the ${gateNames} chain.`);
|
|
1401
|
-
}
|
|
1402
|
-
if (unscripted.length > 0) {
|
|
1403
|
-
const lines = unscripted.map(([, line]) => line);
|
|
1404
|
-
remedies.push(`${writing ? "To continue, add" : "Add"} ${lines.length === 1 ? "this exact script line" : "these exact script lines"} to package.json: ${lines.join(" ")}`);
|
|
1405
|
-
}
|
|
1469
|
+
const ungated = [...expectedLines].filter(([project]) => !reachable.has(project)).sort(([left], [right]) => left.localeCompare(right)).filter(([project]) => isString(scripts[`test:${project}`]));
|
|
1470
|
+
if (ungated.length === 0) return void 0;
|
|
1471
|
+
const names = ungated.map(([project]) => project);
|
|
1472
|
+
const declared = ungated.map(([project]) => `test:${project}`);
|
|
1406
1473
|
return {
|
|
1407
1474
|
field: "projects",
|
|
1408
|
-
message:
|
|
1409
|
-
blocking: false
|
|
1475
|
+
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"}. ${declared.join(", ")} ${declared.length === 1 ? "is" : "are"} already declared, so the gate is missing rather than the script: invoke ${declared.length === 1 ? "it" : "each of them"} by name from the ${gateNames} chain.${writing ? " Exclude configs from --groups to write another group." : ""}`,
|
|
1476
|
+
blocking: false,
|
|
1477
|
+
groups: ["configs"]
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
#scriptQuestion(target, blueprint) {
|
|
1481
|
+
const text = this.#manifest(target);
|
|
1482
|
+
const writable = blueprintToWritableScripts(blueprint);
|
|
1483
|
+
const parsed = parseJSON(text);
|
|
1484
|
+
const scripts = isRecord(parsed) && isRecord(parsed.scripts) ? parsed.scripts : {};
|
|
1485
|
+
const missing = writable.filter((script) => !Object.hasOwn(scripts, script.name));
|
|
1486
|
+
const differing = writable.filter((script) => {
|
|
1487
|
+
const value = scripts[script.name];
|
|
1488
|
+
return isString(value) && value !== script.command && !script.accepted.includes(value);
|
|
1489
|
+
});
|
|
1490
|
+
if (missing.length === 0 && differing.length === 0) return void 0;
|
|
1491
|
+
const missingNames = missing.map((script) => script.name);
|
|
1492
|
+
const missingLines = missing.map((script) => `${JSON.stringify(script.name)}: ${JSON.stringify(script.command)},`);
|
|
1493
|
+
const differingNames = differing.map((script) => script.name);
|
|
1494
|
+
const differingLines = differing.map((script) => {
|
|
1495
|
+
const value = scripts[script.name];
|
|
1496
|
+
return `${JSON.stringify(script.name)} declares ${JSON.stringify(value)}; planned ${JSON.stringify(script.command)}.`;
|
|
1497
|
+
});
|
|
1498
|
+
return {
|
|
1499
|
+
field: "scripts",
|
|
1500
|
+
message: [missing.length === 0 ? "" : `The manifest at ${target} does not declare ${missingNames.length === 1 ? "a planned script" : "planned scripts"}: ${missingNames.join(", ")}. Add ${missingLines.length === 1 ? "this exact script line" : "these exact script lines"} to package.json: ${missingLines.join(" ")}`, differing.length === 0 ? "" : `The manifest at ${target} declares ${differingNames.length === 1 ? "a planned script" : "planned scripts"} with ${differingNames.length === 1 ? "a differing value" : "differing values"}: ${differingNames.join(", ")}. Keep ${differingNames.length === 1 ? "the declared value" : "each declared value"} unchanged or replace ${differingNames.length === 1 ? "it" : "them"} with the planned ${differingNames.length === 1 ? "value" : "values"}: ${differingLines.join(" ")}`].filter((message) => message !== "").join(" "),
|
|
1501
|
+
blocking: false,
|
|
1502
|
+
groups: ["manifest"]
|
|
1410
1503
|
};
|
|
1411
1504
|
}
|
|
1412
1505
|
#dependencyQuestion(target, blueprint, writing = false) {
|
|
@@ -1415,8 +1508,9 @@ var CLI = class CLI {
|
|
|
1415
1508
|
const malformed = ["dependencies", "devDependencies"].filter((section) => parsed[section] !== void 0 && !isRecord(parsed[section]));
|
|
1416
1509
|
if (malformed.length > 0) return {
|
|
1417
1510
|
field: "dependencies",
|
|
1418
|
-
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 ${
|
|
1419
|
-
blocking: false
|
|
1511
|
+
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.`}`,
|
|
1512
|
+
blocking: false,
|
|
1513
|
+
groups: ["configs", "tests"]
|
|
1420
1514
|
};
|
|
1421
1515
|
const dependencies = isRecord(parsed.dependencies) ? parsed.dependencies : {};
|
|
1422
1516
|
const development = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
|
|
@@ -1426,20 +1520,67 @@ var CLI = class CLI {
|
|
|
1426
1520
|
const lines = missing.map(([name, range]) => `${JSON.stringify(name)}: ${JSON.stringify(range)},`);
|
|
1427
1521
|
return {
|
|
1428
1522
|
field: "dependencies",
|
|
1429
|
-
message: `The manifest at ${target} does not declare ${names.length === 1 ? "a planned dependency" : "planned dependencies"}: ${names.join(", ")}. ${writing ? "
|
|
1430
|
-
blocking: false
|
|
1523
|
+
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." : ""}`,
|
|
1524
|
+
blocking: false,
|
|
1525
|
+
groups: ["configs", "tests"]
|
|
1526
|
+
};
|
|
1527
|
+
}
|
|
1528
|
+
#setupQuestion(target, blueprint) {
|
|
1529
|
+
const tests = resolveContainedPath(target, "tests");
|
|
1530
|
+
if (tests === void 0) return void 0;
|
|
1531
|
+
const entries = listFiles(tests).filter((path) => !path.includes("/"));
|
|
1532
|
+
const proofs = new Set(entries.filter((path) => path.endsWith(".test.ts")));
|
|
1533
|
+
const seeds = new Map(blueprintToTestArtifacts(blueprint).map(({ path, content }) => [path, content.trim()]));
|
|
1534
|
+
const modules = entries.filter((path) => {
|
|
1535
|
+
if (!path.startsWith("setup") || !path.endsWith(".ts")) return false;
|
|
1536
|
+
if (path.endsWith(".test.ts") || HOST_PATHS.includes(`tests/${path}`)) return false;
|
|
1537
|
+
if (proofs.has(`${path.slice(0, -3)}.test.ts`)) return false;
|
|
1538
|
+
if (resolveContainedPath(tests, path) === void 0) return false;
|
|
1539
|
+
const content = (readFileText(tests, path) ?? "").trim();
|
|
1540
|
+
return content !== "" && content !== (seeds.get(`tests/${path}`) ?? "");
|
|
1541
|
+
}).sort();
|
|
1542
|
+
if (modules.length === 0) return void 0;
|
|
1543
|
+
const single = modules.length === 1;
|
|
1544
|
+
const named = modules.map((path) => `tests/${path}`).join(", ");
|
|
1545
|
+
const remedies = modules.map((path) => `tests/${path.slice(0, -3)}.test.ts`).join(", ");
|
|
1546
|
+
const remedy = single ? `Add ${remedies} to cover it.` : `Add ${remedies}, each covering the module of the same name.`;
|
|
1547
|
+
return {
|
|
1548
|
+
field: "setup",
|
|
1549
|
+
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.`,
|
|
1550
|
+
blocking: false,
|
|
1551
|
+
groups: ["tests"]
|
|
1431
1552
|
};
|
|
1432
1553
|
}
|
|
1433
|
-
#targetQuestions(target, blueprint, writing = false) {
|
|
1554
|
+
#targetQuestions(target, blueprint, groups, writing = false) {
|
|
1434
1555
|
const questions = [];
|
|
1556
|
+
if (!writing) {
|
|
1557
|
+
const script = this.#scriptQuestion(target, blueprint);
|
|
1558
|
+
if (script !== void 0) questions.push(script);
|
|
1559
|
+
}
|
|
1435
1560
|
const project = this.#projectQuestion(target, blueprint, writing);
|
|
1436
1561
|
if (project !== void 0) questions.push(project);
|
|
1437
1562
|
const dependency = this.#dependencyQuestion(target, blueprint, writing);
|
|
1438
1563
|
if (dependency !== void 0) questions.push(dependency);
|
|
1439
|
-
|
|
1564
|
+
if (!writing) {
|
|
1565
|
+
const setup = this.#setupQuestion(target, blueprint);
|
|
1566
|
+
if (setup !== void 0) questions.push(setup);
|
|
1567
|
+
}
|
|
1568
|
+
return questions.filter((question) => groups === void 0 || question.groups.some((group) => groups.includes(group))).map(({ field, message, blocking, candidates }) => ({
|
|
1569
|
+
field,
|
|
1570
|
+
message,
|
|
1571
|
+
blocking,
|
|
1572
|
+
...candidates === void 0 ? {} : { candidates }
|
|
1573
|
+
}));
|
|
1574
|
+
}
|
|
1575
|
+
#appendQuestions(audit, target, blueprint, groups) {
|
|
1576
|
+
const questions = this.#targetQuestions(target, blueprint, groups);
|
|
1577
|
+
return questions.length === 0 ? audit : {
|
|
1578
|
+
...audit,
|
|
1579
|
+
questions: [...audit.questions, ...questions]
|
|
1580
|
+
};
|
|
1440
1581
|
}
|
|
1441
|
-
#assertTarget(target, blueprint) {
|
|
1442
|
-
const questions = this.#targetQuestions(target, blueprint, true);
|
|
1582
|
+
#assertTarget(target, blueprint, groups) {
|
|
1583
|
+
const questions = this.#targetQuestions(target, blueprint, groups, true);
|
|
1443
1584
|
if (questions.length === 0) return;
|
|
1444
1585
|
throw new ScaffoldError("TARGET", questions.map((question) => question.message).join(" "), { target });
|
|
1445
1586
|
}
|