@orkestrel/scaffold 0.0.46 → 0.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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, DISTRIBUTION_TEST_PATH, ENVIRONMENTS, GLOBAL_SETUP_PATH, GROUPS, GUIDES_TEST_PATH, INTEGRATION_TEST_PATH, MAX_MANIFEST_BYTES, SERVICE_SETUP_PATH, SHOWCASE_CONFIG_PATH, ScaffoldError, blueprintToDevDependencies, blueprintToRootVite, blueprintToScripts, createBlueprint, isScaffoldError, manifestToDependencies, manifestToName, nameToGuide } from "../src/core/index.js";
6
+ import { BIN_ENTRY_PATH, CATALOG_AGENT_PATH, CONFORMANCE_TEST_PATH, Compiler, DEPENDENCY_NAME_PATTERN, DISTRIBUTION_TEST_PATH, ENVIRONMENTS, GLOBAL_SETUP_PATH, GROUPS, GUIDES_TEST_PATH, INTEGRATION_TEST_PATH, MAX_MANIFEST_BYTES, SERVICE_SETUP_PATH, SHOWCASE_CONFIG_PATH, ScaffoldError, blueprintToDevDependencies, blueprintToRootVite, blueprintToScripts, compareVersions, createBlueprint, extractRangeMajor, extractVersion, isScaffoldError, manifestToDependencies, manifestToName, nameToGuide, replacePlanRanges } from "../src/core/index.js";
7
7
  import { Materializer, Upstream, isExactCaseFile, isPhysicalDirectory, isRepository, listFiles, readFileText, readSnapshot, resolveContainedPath } from "../src/server/index.js";
8
8
  import { parseArgs } from "node:util";
9
9
  //#region src/bin/constants.ts
@@ -111,7 +111,8 @@ var OPTION_SUMMARY = Object.freeze({
111
111
  "--dirty": "delete from a tree carrying uncommitted changes",
112
112
  "--from <path>": "read the data root from a local path instead of the bundled one; catalog alone accepts it more than once",
113
113
  "--target <path>": "the directory the verb operates on; the working directory when absent",
114
- "--json": "emit one machine-readable value instead of a report"
114
+ "--json": "emit one machine-readable value instead of a report",
115
+ ORKESTREL_SCAFFOLD_REGISTRY: "the registry base mapped to upstream.registry.base"
115
116
  });
116
117
  /**
117
118
  * The options each verb takes, in usage order, frozen.
@@ -434,6 +435,77 @@ function auditToExit(audit) {
434
435
  return blocked || drifted ? 1 : 0;
435
436
  }
436
437
  /**
438
+ * Read the fleet rows and planned foreign tools a target manifest declares.
439
+ *
440
+ * @param manifest - The target manifest text.
441
+ * @param blueprint - The workspace shape that supplies the planned tool set.
442
+ * @returns The declared fleet rows followed by declared planned foreign rows.
443
+ */
444
+ function manifestToPlannedDependencies(manifest, blueprint) {
445
+ const dependencies = [...dependenciesToFleet(manifestToDependencies(manifest))];
446
+ const parsed = parseJSON(manifest);
447
+ if (!isRecord(parsed)) return dependencies;
448
+ const runtime = isRecord(parsed.dependencies) ? parsed.dependencies : {};
449
+ const development = isRecord(parsed.devDependencies) ? parsed.devDependencies : {};
450
+ for (const name of Object.keys(blueprintToDevDependencies(blueprint)).sort()) {
451
+ if (name.startsWith("@orkestrel/")) continue;
452
+ const range = runtime[name] ?? development[name];
453
+ if (isString(range)) dependencies.push({
454
+ name,
455
+ range
456
+ });
457
+ }
458
+ return dependencies;
459
+ }
460
+ /**
461
+ * Keep only dependencies published by the Orkestrel fleet.
462
+ *
463
+ * @param dependencies - The declared dependencies to inspect.
464
+ * @returns The fleet declarations in their original order.
465
+ */
466
+ function dependenciesToFleet(dependencies) {
467
+ return dependencies.filter((dependency) => dependency.name.startsWith("@orkestrel/"));
468
+ }
469
+ /**
470
+ * Read the exit code registry release evidence earns.
471
+ *
472
+ * @param releases - The release verdicts to measure.
473
+ * @returns `EXIT_DRIFT` for a failed lookup or an exact fleet mismatch; `EXIT_CLEAN` otherwise.
474
+ */
475
+ function releasesToExit(releases) {
476
+ return releases.some((release) => release.lookup !== "found" || release.name.startsWith("@orkestrel/") && release.range !== `^${release.latest}`) ? 1 : 0;
477
+ }
478
+ /**
479
+ * Project foreign floor and supported-major drift into audit questions.
480
+ *
481
+ * @param releases - The release verdicts to measure.
482
+ * @param served - Alternate release verdicts used to detect a newer served major.
483
+ * @returns One non-blocking question for each stale floor and each crossed major.
484
+ */
485
+ function releasesToQuestions(releases, served = releases) {
486
+ const questions = [];
487
+ for (const release of releases) {
488
+ if (release.name.startsWith("@orkestrel/")) continue;
489
+ const declared = extractRangeMajor(release.range);
490
+ const current = release.lookup === "found" ? release.latest : void 0;
491
+ const resolved = current === void 0 ? void 0 : extractVersion(current);
492
+ const floor = extractVersion(release.range.startsWith("^") || release.range.startsWith("~") ? release.range.slice(1) : release.range);
493
+ if (declared !== void 0 && current !== void 0 && resolved !== void 0 && floor !== void 0 && resolved[0] === declared && compareVersions(current, floor.join(".")) > 0) questions.push({
494
+ field: "dependencies",
495
+ message: `${release.name} declares the floor ${release.range}, while the registry serves ${current} within major ${String(declared)}.`,
496
+ blocking: false
497
+ });
498
+ const alternate = served.find((candidate) => candidate.name === release.name);
499
+ const published = release.major ?? (alternate?.lookup === "found" ? extractVersion(alternate.latest)?.[0] : void 0);
500
+ if (declared !== void 0 && published !== void 0 && declared < published) questions.push({
501
+ field: "dependencies",
502
+ message: `${release.name} declares major ${String(declared)}, while the registry serves major ${String(published)}.`,
503
+ blocking: false
504
+ });
505
+ }
506
+ return questions;
507
+ }
508
+ /**
437
509
  * Project an audit into the human summary of its outcome.
438
510
  *
439
511
  * @param audit - The comparison to summarize.
@@ -602,12 +674,20 @@ var CLI = class CLI {
602
674
  app: this.#environments(command.app, "app"),
603
675
  bin: command.bin === true,
604
676
  setup: false,
605
- dependencies: await this.#resolve(this.#packages(command.dependencies))
677
+ dependencies: this.#packages(command.dependencies).map((name) => ({
678
+ name,
679
+ range: "^0.0.0"
680
+ }))
606
681
  });
607
682
  const plan = this.#compile(blueprint);
683
+ const manifest = plan.artifacts.find((artifact) => artifact.path === "package.json" && artifact.origin !== "host");
684
+ if (manifest === void 0 || manifest.origin === "host") throw new ScaffoldError("BLOCKED", "The compiled plan carries no replaceable manifest.");
685
+ const releases = await this.#lookup(dependenciesToFleet(manifestToDependencies(manifest.content)));
686
+ const resolved = replacePlanRanges(plan, this.#pin(releases));
687
+ if (resolved === void 0) throw new ScaffoldError("BLOCKED", "The compiled plan ranges could not be replaced.");
608
688
  const materializer = new Materializer(command.from === void 0 ? void 0 : { host: command.from });
609
689
  try {
610
- const result = materializer.materialize(plan, target);
690
+ const result = materializer.materialize(resolved, target);
611
691
  if (command.json === true) this.#report(result);
612
692
  else {
613
693
  this.#say(`Scaffolded ${blueprint.name} into ${result.target}.`);
@@ -618,10 +698,13 @@ var CLI = class CLI {
618
698
  materializer.destroy();
619
699
  }
620
700
  }
621
- #inspect(command) {
701
+ async #inspect(command) {
622
702
  const target = command.target ?? ".";
703
+ const manifest = this.#manifest(target);
623
704
  const blueprint = this.#derive(target);
624
- const questions = this.#targetQuestions(target, blueprint);
705
+ const declared = manifestToPlannedDependencies(manifest, blueprint);
706
+ const releases = await this.#lookup(declared);
707
+ const questions = [...this.#targetQuestions(target, blueprint), ...releasesToQuestions(releases)];
625
708
  const materializer = new Materializer(command.from === void 0 ? void 0 : { host: command.from });
626
709
  try {
627
710
  const [measured] = this.#survey(materializer, blueprint, target, this.#groups(command.groups));
@@ -629,14 +712,21 @@ var CLI = class CLI {
629
712
  ...measured,
630
713
  questions: [...measured.questions, ...questions]
631
714
  };
632
- if (command.json === true) this.#report(audit);
633
- else this.#present(audit);
634
- return auditToExit(audit);
715
+ const outcome = {
716
+ ...audit,
717
+ releases
718
+ };
719
+ if (command.json === true) this.#report(outcome);
720
+ else {
721
+ this.#present(audit);
722
+ this.#reportReleases(releases);
723
+ }
724
+ return Math.max(auditToExit(audit), releasesToExit(releases));
635
725
  } finally {
636
726
  materializer.destroy();
637
727
  }
638
728
  }
639
- #restore(command) {
729
+ async #restore(command) {
640
730
  const target = command.target ?? ".";
641
731
  const groups = this.#groups(command.groups);
642
732
  const blueprint = this.#derive(target);
@@ -649,11 +739,14 @@ var CLI = class CLI {
649
739
  else this.#present(audit);
650
740
  return 1;
651
741
  }
652
- const result = materializer.repair(plan, audit, target);
653
- const [terminal] = this.#survey(materializer, blueprint, target, groups);
742
+ const releases = await this.#lookup(manifestToPlannedDependencies(this.#manifest(target), blueprint));
743
+ const pins = this.#pin(releases);
744
+ const result = this.#merge(materializer.repair(plan, audit, target), materializer.declare(pins, target));
745
+ const [terminal] = this.#survey(materializer, this.#derive(target), target, groups);
654
746
  const outcome = {
655
747
  ...result,
656
- audit: terminal
748
+ audit: terminal,
749
+ releases
657
750
  };
658
751
  if (command.json === true) this.#report(outcome);
659
752
  else {
@@ -672,10 +765,13 @@ var CLI = class CLI {
672
765
  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.`);
673
766
  const previous = this.#previous(target);
674
767
  const fetched = await this.#fetch(target, command.all === true);
768
+ const releases = this.#catalogReleases(dependenciesToFleet(manifestToDependencies(this.#manifest(target))), fetched.entries);
769
+ const pins = this.#pin(releases);
770
+ this.#assertFetched(fetched.entries, fetched.mirrors);
675
771
  const materializer = new Materializer(host === void 0 ? void 0 : { host });
676
772
  let result;
677
773
  try {
678
- result = this.#publish(materializer, target, fetched.entries, fetched.mirrors);
774
+ result = this.#merge(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare(pins, target));
679
775
  } finally {
680
776
  materializer.destroy();
681
777
  }
@@ -683,11 +779,12 @@ var CLI = class CLI {
683
779
  ...result,
684
780
  entries: fetched.entries,
685
781
  mirrors: fetched.mirrors,
686
- dropped: previous.filter((name) => !fetched.entries.some((entry) => entry.name === name))
782
+ dropped: previous.filter((name) => !fetched.entries.some((entry) => entry.name === name)),
783
+ releases
687
784
  };
688
785
  if (command.json === true) this.#report(outcome);
689
786
  else this.#recount(outcome);
690
- return fetched.mirrors.some((mirror) => mirror.lookup === "failed") ? 1 : 0;
787
+ return 0;
691
788
  }
692
789
  async #replace(command) {
693
790
  const target = command.target ?? ".";
@@ -713,7 +810,7 @@ var CLI = class CLI {
713
810
  dirty: []
714
811
  } : repository, target);
715
812
  const offline = this.#merge(repaired, removed);
716
- const online = await this.#reconcile(materializer, target, blueprint.dependencies);
813
+ const online = await this.#reconcile(materializer, target, manifestToPlannedDependencies(this.#manifest(target), blueprint));
717
814
  const [terminal] = this.#survey(materializer, blueprint, target, groups);
718
815
  const outcome = {
719
816
  ...online,
@@ -735,11 +832,14 @@ var CLI = class CLI {
735
832
  }
736
833
  async #reconcile(materializer, target, declared) {
737
834
  const previous = this.#previous(target);
835
+ let releases = [];
738
836
  try {
739
- const releases = await this.#lookup(declared);
837
+ releases = await this.#lookup(declared);
838
+ const pins = this.#pin(releases);
740
839
  const fetched = await this.#fetch(target, false);
840
+ this.#assertFetched(fetched.entries, fetched.mirrors);
741
841
  return {
742
- ...this.#merge(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare(this.#pin(releases), target)),
842
+ ...this.#merge(this.#publish(materializer, target, fetched.entries, fetched.mirrors), materializer.declare(pins, target)),
743
843
  entries: fetched.entries,
744
844
  mirrors: fetched.mirrors,
745
845
  dropped: previous.filter((name) => !fetched.entries.some((entry) => entry.name === name)),
@@ -754,15 +854,34 @@ var CLI = class CLI {
754
854
  entries: [],
755
855
  mirrors: [],
756
856
  dropped: [],
757
- releases: [],
857
+ releases,
758
858
  note: `The catalog step did not complete: ${errorToEnvelope(error).error.message}`
759
859
  };
760
860
  }
761
861
  }
762
862
  async #lookup(declared) {
863
+ if (declared.length === 0) return [];
763
864
  const upstream = new Upstream(this.#upstream);
764
865
  try {
765
- return await upstream.lookup(declared);
866
+ return (await upstream.lookup(declared.map((dependency) => {
867
+ const major = extractRangeMajor(dependency.range);
868
+ return {
869
+ name: dependency.name,
870
+ range: dependency.name.startsWith("@orkestrel/") ? "*" : major === void 0 ? dependency.range : `^${String(major)}`
871
+ };
872
+ }))).map((release, index) => {
873
+ const dependency = declared[index];
874
+ if (dependency === void 0) return {
875
+ name: release.name,
876
+ range: release.range,
877
+ lookup: "failed",
878
+ note: "the release answer has no matching declaration"
879
+ };
880
+ return {
881
+ ...release,
882
+ range: dependency.range
883
+ };
884
+ });
766
885
  } finally {
767
886
  upstream.destroy();
768
887
  }
@@ -786,13 +905,36 @@ var CLI = class CLI {
786
905
  #publish(materializer, target, entries, mirrors) {
787
906
  return this.#merge(materializer.mirror(mirrors, target), materializer.catalog(entries, target));
788
907
  }
908
+ #catalogReleases(declared, entries) {
909
+ return declared.map((dependency) => {
910
+ const entry = entries.find((candidate) => candidate.name === dependency.name);
911
+ if (entry?.lookup === "found") return {
912
+ ...dependency,
913
+ lookup: "found",
914
+ latest: entry.version
915
+ };
916
+ return {
917
+ ...dependency,
918
+ lookup: entry?.lookup ?? "missing",
919
+ note: entry?.note ?? "the organization catalog does not list the declared package"
920
+ };
921
+ });
922
+ }
923
+ #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)];
925
+ if (failed.length === 0) return;
926
+ throw new ScaffoldError("FETCH", `Upstream produced no complete catalog answer for ${failed.join(", ")}.`, { names: failed.length });
927
+ }
789
928
  #pin(releases) {
790
- const pinned = [];
791
- for (const release of releases) if (release.lookup === "found") pinned.push({
792
- name: release.name,
793
- range: `^${release.latest}`
929
+ const refused = releases.filter((release) => release.lookup !== "found");
930
+ if (refused.length > 0) throw new ScaffoldError("FETCH", `The registry named no release for ${refused.map((release) => release.name).join(", ")}.`, { names: refused.length });
931
+ return releases.map((release) => {
932
+ if (release.lookup !== "found") throw new ScaffoldError("FETCH", `The registry named no release for ${release.name}.`);
933
+ return {
934
+ name: release.name,
935
+ range: `^${release.latest}`
936
+ };
794
937
  });
795
- return pinned;
796
938
  }
797
939
  #compile(blueprint, groups) {
798
940
  const compiler = new Compiler();
@@ -1004,7 +1146,7 @@ var CLI = class CLI {
1004
1146
  const expected = blueprintToScripts(blueprint);
1005
1147
  const expectedLines = /* @__PURE__ */ new Map();
1006
1148
  for (const [name, script] of Object.entries(expected)) {
1007
- if (!name.startsWith("test:") || name === "test:probe") continue;
1149
+ if (!name.startsWith("test:")) continue;
1008
1150
  const invoked = this.#invocations(script);
1009
1151
  if (invoked === void 0) continue;
1010
1152
  for (const project of invoked.projects) {
@@ -1165,22 +1307,6 @@ var CLI = class CLI {
1165
1307
  if (refused.length > 0) throw new UsageError(`'--deps' does not take ${refused.join(", ")}. Every name is a published @orkestrel package.`);
1166
1308
  return requested;
1167
1309
  }
1168
- async #resolve(names) {
1169
- if (names.length === 0) return [];
1170
- const upstream = new Upstream(this.#upstream);
1171
- let releases;
1172
- try {
1173
- releases = await upstream.lookup(names.map((name) => ({
1174
- name,
1175
- range: "*"
1176
- })));
1177
- } finally {
1178
- upstream.destroy();
1179
- }
1180
- const refused = releases.filter((release) => release.lookup !== "found");
1181
- if (refused.length > 0) throw new ScaffoldError("FETCH", `The registry named no release for ${refused.map((release) => release.name).join(", ")}.`, { names: refused.length });
1182
- return this.#pin(releases);
1183
- }
1184
1310
  #merge(first, second) {
1185
1311
  return {
1186
1312
  target: first.target,
@@ -1209,6 +1335,15 @@ var CLI = class CLI {
1209
1335
  }
1210
1336
  this.#say(auditToSummary(audit));
1211
1337
  }
1338
+ #reportReleases(releases) {
1339
+ for (const release of releases) {
1340
+ if (release.lookup !== "found") {
1341
+ this.#warn(`${release.name}: ${release.note}`);
1342
+ continue;
1343
+ }
1344
+ if (release.name.startsWith("@orkestrel/") && release.range !== `^${release.latest}`) this.#warn(`${release.name}: ${release.range} differs from ^${release.latest}.`);
1345
+ }
1346
+ }
1212
1347
  #reportReplacements(before, after) {
1213
1348
  const terminal = new Map(after.findings.map((finding) => [finding.path, finding]));
1214
1349
  for (const finding of before.findings) {
@@ -1258,7 +1393,7 @@ process.stdout.on("error", (error) => {
1258
1393
  if ("code" in error && error.code === "EPIPE") return;
1259
1394
  throw error;
1260
1395
  });
1261
- process.exitCode = await new CLI().execute(process.argv.slice(2));
1396
+ process.exitCode = await new CLI(process.env.ORKESTREL_SCAFFOLD_REGISTRY === void 0 ? void 0 : { upstream: { registry: { base: process.env.ORKESTREL_SCAFFOLD_REGISTRY } } }).execute(process.argv.slice(2));
1262
1397
  //#endregion
1263
1398
 
1264
1399
  //# sourceMappingURL=main.js.map