@orkestrel/scaffold 0.0.49 → 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 +184 -72
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +28 -3
- package/dist/host/claude/agents/orkestrel.md +44 -44
- package/dist/host/claude/rules/tests.md +6 -2
- package/dist/host/guides/scaffold.md +328 -96
- package/dist/host/manifest.json +6 -6
- 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 +26 -5
- package/dist/src/core/index.cjs +1285 -80
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +200 -36
- package/dist/src/core/index.d.ts +200 -36
- package/dist/src/core/index.js +1280 -81
- 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,9 +826,12 @@ 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(
|
|
831
|
+
const result = this.#merge(host.materializer.repair(plan, audit, target), host.materializer.declare({
|
|
832
|
+
pins: versions.pins,
|
|
833
|
+
scripts: blueprintToWritableScripts(blueprint)
|
|
834
|
+
}, target));
|
|
812
835
|
const [terminal] = this.#survey(host.materializer, this.#derive(target), target, groups);
|
|
813
836
|
const outcome = {
|
|
814
837
|
...result,
|
|
@@ -838,14 +861,22 @@ var CLI = class CLI {
|
|
|
838
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.`);
|
|
839
862
|
const previous = this.#previous(target);
|
|
840
863
|
const fetched = await this.#fetch(target, command.all === true);
|
|
841
|
-
const
|
|
842
|
-
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);
|
|
843
871
|
this.#assertFetched(fetched.entries, fetched.mirrors);
|
|
844
872
|
const guides = fetched.mirrors.length === 0 ? void 0 : fetched.mirrors.some((mirror) => mirror.lookup === "failed") ? "floor" : "live";
|
|
845
873
|
const materializer = new Materializer({ host: host ?? readHostFloor() });
|
|
846
874
|
let result;
|
|
847
875
|
try {
|
|
848
|
-
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));
|
|
849
880
|
} finally {
|
|
850
881
|
materializer.destroy();
|
|
851
882
|
}
|
|
@@ -868,7 +899,7 @@ var CLI = class CLI {
|
|
|
868
899
|
const target = command.target ?? ".";
|
|
869
900
|
const groups = this.#groups(command.groups);
|
|
870
901
|
const blueprint = this.#derive(target);
|
|
871
|
-
this.#assertTarget(target, blueprint);
|
|
902
|
+
this.#assertTarget(target, blueprint, groups);
|
|
872
903
|
const worktree = this.#worktree(target);
|
|
873
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.`, {
|
|
874
905
|
target,
|
|
@@ -885,7 +916,10 @@ var CLI = class CLI {
|
|
|
885
916
|
else this.#present(audit);
|
|
886
917
|
return 1;
|
|
887
918
|
}
|
|
888
|
-
const declared =
|
|
919
|
+
const declared = {
|
|
920
|
+
pins: manifestToWritableDependencies(this.#manifest(target), blueprint),
|
|
921
|
+
scripts: blueprintToWritableScripts(blueprint)
|
|
922
|
+
};
|
|
889
923
|
const repaired = host.materializer.repair(plan, audit, target);
|
|
890
924
|
const removed = host.materializer.remove(plan, audit, command.dirty === true ? {
|
|
891
925
|
tracked: worktree.tracked,
|
|
@@ -913,10 +947,13 @@ var CLI = class CLI {
|
|
|
913
947
|
}
|
|
914
948
|
}
|
|
915
949
|
async #offline(materializer, target, declared, host) {
|
|
916
|
-
const versions = await this.#versions(declared, true);
|
|
950
|
+
const versions = await this.#versions(declared.pins, true);
|
|
917
951
|
this.#assertVersions(versions);
|
|
918
952
|
return {
|
|
919
|
-
...materializer.declare(
|
|
953
|
+
...materializer.declare({
|
|
954
|
+
pins: versions.pins,
|
|
955
|
+
scripts: declared.scripts
|
|
956
|
+
}, target),
|
|
920
957
|
entries: [],
|
|
921
958
|
mirrors: [],
|
|
922
959
|
dropped: [],
|
|
@@ -933,7 +970,7 @@ var CLI = class CLI {
|
|
|
933
970
|
let releases = [];
|
|
934
971
|
let provenance = { ...host === void 0 ? {} : { host } };
|
|
935
972
|
try {
|
|
936
|
-
const versions = await this.#versions(declared, false);
|
|
973
|
+
const versions = await this.#versions(declared.pins, false);
|
|
937
974
|
releases = versions.releases;
|
|
938
975
|
provenance = {
|
|
939
976
|
...versions.baseline === void 0 ? {} : { versions: versions.baseline },
|
|
@@ -948,7 +985,10 @@ var CLI = class CLI {
|
|
|
948
985
|
...guides === void 0 ? {} : { guides },
|
|
949
986
|
...host === void 0 ? {} : { host }
|
|
950
987
|
};
|
|
951
|
-
const written = this.#merge(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare(
|
|
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));
|
|
952
992
|
const floors = [];
|
|
953
993
|
if (hostForced) floors.push("host");
|
|
954
994
|
if (versions.forced) floors.push("versions");
|
|
@@ -1004,54 +1044,71 @@ var CLI = class CLI {
|
|
|
1004
1044
|
}
|
|
1005
1045
|
}
|
|
1006
1046
|
async #versions(declared, offline) {
|
|
1007
|
-
|
|
1047
|
+
const dependencies = [...declared.runtime, ...declared.development];
|
|
1048
|
+
if (dependencies.length === 0) return {
|
|
1008
1049
|
releases: [],
|
|
1009
|
-
pins:
|
|
1050
|
+
pins: {
|
|
1051
|
+
runtime: [],
|
|
1052
|
+
development: []
|
|
1053
|
+
},
|
|
1010
1054
|
forced: false,
|
|
1011
1055
|
complete: true
|
|
1012
1056
|
};
|
|
1013
|
-
const floors = dependenciesToFloors(
|
|
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
|
+
};
|
|
1014
1068
|
if (offline) {
|
|
1015
|
-
if (floors === void 0) return {
|
|
1069
|
+
if (floors === void 0 || floorPins === void 0) return {
|
|
1016
1070
|
releases: [],
|
|
1017
|
-
pins:
|
|
1071
|
+
pins: {
|
|
1072
|
+
runtime: [],
|
|
1073
|
+
development: []
|
|
1074
|
+
},
|
|
1018
1075
|
baseline: "floor",
|
|
1019
1076
|
forced: false,
|
|
1020
1077
|
complete: false
|
|
1021
1078
|
};
|
|
1022
1079
|
return {
|
|
1023
1080
|
releases: floors,
|
|
1024
|
-
pins:
|
|
1025
|
-
name: release.name,
|
|
1026
|
-
range: release.range
|
|
1027
|
-
})),
|
|
1081
|
+
pins: floorPins,
|
|
1028
1082
|
baseline: "floor",
|
|
1029
1083
|
forced: false,
|
|
1030
1084
|
complete: true
|
|
1031
1085
|
};
|
|
1032
1086
|
}
|
|
1033
|
-
const releases = await this.#lookup(
|
|
1087
|
+
const releases = await this.#lookup(dependencies);
|
|
1034
1088
|
if (releases.filter((release) => release.lookup === "missing" || release.lookup === "unmatched").length > 0) return {
|
|
1035
1089
|
releases,
|
|
1036
|
-
pins:
|
|
1090
|
+
pins: {
|
|
1091
|
+
runtime: [],
|
|
1092
|
+
development: []
|
|
1093
|
+
},
|
|
1037
1094
|
baseline: "live",
|
|
1038
1095
|
forced: false,
|
|
1039
1096
|
complete: false
|
|
1040
1097
|
};
|
|
1041
1098
|
if (releases.some((release) => release.lookup === "failed")) {
|
|
1042
|
-
if (
|
|
1099
|
+
if (floorPins === void 0) return {
|
|
1043
1100
|
releases,
|
|
1044
|
-
pins:
|
|
1101
|
+
pins: {
|
|
1102
|
+
runtime: [],
|
|
1103
|
+
development: []
|
|
1104
|
+
},
|
|
1045
1105
|
baseline: "floor",
|
|
1046
1106
|
forced: true,
|
|
1047
1107
|
complete: false
|
|
1048
1108
|
};
|
|
1049
1109
|
return {
|
|
1050
1110
|
releases,
|
|
1051
|
-
pins:
|
|
1052
|
-
name: release.name,
|
|
1053
|
-
range: release.range
|
|
1054
|
-
})),
|
|
1111
|
+
pins: floorPins,
|
|
1055
1112
|
baseline: "floor",
|
|
1056
1113
|
forced: true,
|
|
1057
1114
|
complete: true
|
|
@@ -1059,7 +1116,7 @@ var CLI = class CLI {
|
|
|
1059
1116
|
}
|
|
1060
1117
|
return {
|
|
1061
1118
|
releases,
|
|
1062
|
-
pins: this.#pin(releases),
|
|
1119
|
+
pins: this.#pin(releases, declared),
|
|
1063
1120
|
baseline: "live",
|
|
1064
1121
|
forced: false,
|
|
1065
1122
|
complete: true
|
|
@@ -1100,7 +1157,12 @@ var CLI = class CLI {
|
|
|
1100
1157
|
async #fetch(target, all) {
|
|
1101
1158
|
const manifest = this.#manifest(target);
|
|
1102
1159
|
const own = manifestToName(manifest);
|
|
1103
|
-
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))];
|
|
1104
1166
|
const upstream = new Upstream(this.#upstream);
|
|
1105
1167
|
try {
|
|
1106
1168
|
const entries = await upstream.catalog();
|
|
@@ -1136,16 +1198,26 @@ var CLI = class CLI {
|
|
|
1136
1198
|
if (failed.length === 0) return;
|
|
1137
1199
|
throw new ScaffoldError("FETCH", `Upstream produced no complete catalog answer for ${failed.join(", ")}.`, { names: failed.length });
|
|
1138
1200
|
}
|
|
1139
|
-
#pin(releases) {
|
|
1201
|
+
#pin(releases, declared) {
|
|
1140
1202
|
const refused = releases.filter((release) => release.lookup !== "found");
|
|
1141
1203
|
if (refused.length > 0) throw new ScaffoldError("FETCH", `The registry named no release for ${refused.map((release) => release.name).join(", ")}.`, { names: refused.length });
|
|
1142
|
-
|
|
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.");
|
|
1143
1210
|
if (release.lookup !== "found") throw new ScaffoldError("FETCH", `The registry named no release for ${release.name}.`);
|
|
1144
|
-
|
|
1145
|
-
name:
|
|
1211
|
+
pins.push({
|
|
1212
|
+
name: dependency.name,
|
|
1146
1213
|
range: `^${release.latest}`
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
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
|
+
};
|
|
1149
1221
|
}
|
|
1150
1222
|
#compile(blueprint, groups) {
|
|
1151
1223
|
const compiler = new Compiler();
|
|
@@ -1178,7 +1250,6 @@ var CLI = class CLI {
|
|
|
1178
1250
|
const bin = resolveContainedPath(target, BIN_ENTRY_PATH);
|
|
1179
1251
|
const tests = resolveContainedPath(target, "tests");
|
|
1180
1252
|
const guides = resolveContainedPath(target, GUIDES_TEST_PATH);
|
|
1181
|
-
const distribution = resolveContainedPath(target, DISTRIBUTION_TEST_PATH);
|
|
1182
1253
|
const integration = resolveContainedPath(target, INTEGRATION_TEST_PATH);
|
|
1183
1254
|
const conformance = resolveContainedPath(target, CONFORMANCE_TEST_PATH);
|
|
1184
1255
|
const service = resolveContainedPath(target, SERVICE_SETUP_PATH);
|
|
@@ -1187,7 +1258,7 @@ var CLI = class CLI {
|
|
|
1187
1258
|
return createBlueprint(declared.slice(declared.lastIndexOf("/") + 1), {
|
|
1188
1259
|
src: this.#probe(target, "src"),
|
|
1189
1260
|
app: this.#probe(target, "app"),
|
|
1190
|
-
dependencies: manifestToDependencies(manifest),
|
|
1261
|
+
dependencies: manifestToDependencies(manifest).runtime,
|
|
1191
1262
|
bin: bin !== void 0 && isExactCaseFile(bin),
|
|
1192
1263
|
setup: tests !== void 0 && listFiles(tests).some((path) => {
|
|
1193
1264
|
if (path.includes("/") || !path.startsWith("setup") || !path.endsWith(".test.ts")) return false;
|
|
@@ -1195,7 +1266,6 @@ var CLI = class CLI {
|
|
|
1195
1266
|
return proof !== void 0 && isExactCaseFile(proof);
|
|
1196
1267
|
}),
|
|
1197
1268
|
guides: guides !== void 0 && isExactCaseFile(guides),
|
|
1198
|
-
distribution: distribution !== void 0 && isExactCaseFile(distribution),
|
|
1199
1269
|
integration: integration !== void 0 && isExactCaseFile(integration),
|
|
1200
1270
|
conformance: conformance !== void 0 && isExactCaseFile(conformance),
|
|
1201
1271
|
service: service !== void 0 && isExactCaseFile(service),
|
|
@@ -1326,7 +1396,8 @@ var CLI = class CLI {
|
|
|
1326
1396
|
};
|
|
1327
1397
|
}
|
|
1328
1398
|
#projectQuestion(target, blueprint, writing = false) {
|
|
1329
|
-
const
|
|
1399
|
+
const text = this.#manifest(target);
|
|
1400
|
+
const manifest = replaceManifestScripts(text, blueprintToWritableScripts(blueprint)) ?? text;
|
|
1330
1401
|
const planned = blueprintToRootVite(blueprint);
|
|
1331
1402
|
const parsed = parseJSON(manifest);
|
|
1332
1403
|
const scripts = isRecord(parsed) && isRecord(parsed.scripts) ? parsed.scripts : {};
|
|
@@ -1346,13 +1417,15 @@ var CLI = class CLI {
|
|
|
1346
1417
|
const projects = [...absent].sort();
|
|
1347
1418
|
if (unresolved) return {
|
|
1348
1419
|
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
|
|
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"]
|
|
1351
1423
|
};
|
|
1352
1424
|
if (projects.length > 0) return {
|
|
1353
1425
|
field: "projects",
|
|
1354
|
-
message: writing ? `The manifest at ${target} names ${projects.length === 1 ? "a Vitest project" : "Vitest projects"} the planned
|
|
1355
|
-
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"]
|
|
1356
1429
|
};
|
|
1357
1430
|
const expected = blueprintToScripts(blueprint);
|
|
1358
1431
|
const expectedLines = /* @__PURE__ */ new Map();
|
|
@@ -1386,8 +1459,9 @@ var CLI = class CLI {
|
|
|
1386
1459
|
}
|
|
1387
1460
|
if (unresolved) return {
|
|
1388
1461
|
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
|
|
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"]
|
|
1391
1465
|
};
|
|
1392
1466
|
const missing = [...expectedLines].filter(([project]) => !reachable.has(project)).sort(([left], [right]) => left.localeCompare(right));
|
|
1393
1467
|
if (missing.length === 0) return void 0;
|
|
@@ -1405,8 +1479,9 @@ var CLI = class CLI {
|
|
|
1405
1479
|
}
|
|
1406
1480
|
return {
|
|
1407
1481
|
field: "projects",
|
|
1408
|
-
message:
|
|
1409
|
-
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"]
|
|
1410
1485
|
};
|
|
1411
1486
|
}
|
|
1412
1487
|
#dependencyQuestion(target, blueprint, writing = false) {
|
|
@@ -1415,8 +1490,9 @@ var CLI = class CLI {
|
|
|
1415
1490
|
const malformed = ["dependencies", "devDependencies"].filter((section) => parsed[section] !== void 0 && !isRecord(parsed[section]));
|
|
1416
1491
|
if (malformed.length > 0) return {
|
|
1417
1492
|
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
|
|
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"]
|
|
1420
1496
|
};
|
|
1421
1497
|
const dependencies = isRecord(parsed.dependencies) ? parsed.dependencies : {};
|
|
1422
1498
|
const development = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
|
|
@@ -1426,20 +1502,56 @@ var CLI = class CLI {
|
|
|
1426
1502
|
const lines = missing.map(([name, range]) => `${JSON.stringify(name)}: ${JSON.stringify(range)},`);
|
|
1427
1503
|
return {
|
|
1428
1504
|
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
|
|
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"]
|
|
1431
1508
|
};
|
|
1432
1509
|
}
|
|
1433
|
-
#
|
|
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"]
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
#targetQuestions(target, blueprint, groups, writing = false) {
|
|
1434
1537
|
const questions = [];
|
|
1435
1538
|
const project = this.#projectQuestion(target, blueprint, writing);
|
|
1436
1539
|
if (project !== void 0) questions.push(project);
|
|
1437
1540
|
const dependency = this.#dependencyQuestion(target, blueprint, writing);
|
|
1438
1541
|
if (dependency !== void 0) questions.push(dependency);
|
|
1439
|
-
|
|
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
|
+
}));
|
|
1440
1552
|
}
|
|
1441
|
-
#assertTarget(target, blueprint) {
|
|
1442
|
-
const questions = this.#targetQuestions(target, blueprint, true);
|
|
1553
|
+
#assertTarget(target, blueprint, groups) {
|
|
1554
|
+
const questions = this.#targetQuestions(target, blueprint, groups, true);
|
|
1443
1555
|
if (questions.length === 0) return;
|
|
1444
1556
|
throw new ScaffoldError("TARGET", questions.map((question) => question.message).join(" "), { target });
|
|
1445
1557
|
}
|