@orkestrel/scaffold 0.0.15 → 0.0.17

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.
@@ -296,6 +296,34 @@ function isMaterializerEventHooks(value) {
296
296
  }
297
297
  //#endregion
298
298
  //#region src/server/parsers.ts
299
+ /**
300
+ * Parse and semantically validate bare registry package names before Sync performs network I/O.
301
+ *
302
+ * @param value - Untrusted package-name collection.
303
+ * @returns A frozen owned snapshot of valid unique npm package names.
304
+ */
305
+ function parseSyncNames(value) {
306
+ const owned = (0, _orkestrel_contract.attempt)(() => {
307
+ if (!(0, _src_core.isDenseDataArray)(value, 1e3, (candidate) => typeof candidate === "string")) throw new Error("package names are malformed");
308
+ const length = Reflect.getOwnPropertyDescriptor(value, "length")?.value;
309
+ if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > 1e3) throw new Error("package name length is malformed");
310
+ const names = [];
311
+ for (let index = 0; index < length; index += 1) {
312
+ const descriptor = Reflect.getOwnPropertyDescriptor(value, String(index));
313
+ if (descriptor === void 0 || !Reflect.has(descriptor, "value") || typeof descriptor.value !== "string") throw new Error("package name is malformed");
314
+ names.push(descriptor.value);
315
+ }
316
+ return Object.freeze(names);
317
+ });
318
+ if (!owned.success) throw new _src_core.ScaffoldError("INVALID", "Sync package names are malformed", { error: owned.error });
319
+ const snapshot = owned.value;
320
+ const seen = /* @__PURE__ */ new Set();
321
+ for (const name of snapshot) {
322
+ if (name.length === 0 || name.length > _src_core.MAX_DEPENDENCY_NAME_LENGTH || !_src_core.EXTRA_NAME_PATTERN.test(name) || seen.has(name)) throw new _src_core.ScaffoldError("INVALID", "Sync package names are invalid", { name });
323
+ seen.add(name);
324
+ }
325
+ return snapshot;
326
+ }
299
327
  /** Parse and semantically validate dependency data before Sync performs network I/O. */
300
328
  function parseSyncDependencies(value, external) {
301
329
  const owned = (0, _orkestrel_contract.attempt)(() => {
@@ -929,16 +957,16 @@ function packageShortName(name) {
929
957
  return name.startsWith("@orkestrel/") ? name.slice(11) : name;
930
958
  }
931
959
  /**
932
- * Read bounded physical local guide mirrors for declared dependencies.
960
+ * Read bounded physical local guide mirrors for package names.
933
961
  *
934
962
  * @param target - The package root.
935
- * @param dependencies - The declared dependencies whose mirrors are eligible.
936
- * @returns Existing guide content keyed by dependency name.
963
+ * @param names - The package names whose mirrors are eligible.
964
+ * @returns Existing guide content keyed by package name.
937
965
  */
938
- function readGuideReferences(target, dependencies) {
966
+ function readGuideReferences(target, names) {
939
967
  const current = {};
940
- for (const dependency of dependencies) {
941
- const path = `guides/src/${packageShortName(dependency.name)}.md`;
968
+ for (const name of names) {
969
+ const path = `guides/src/${packageShortName(name)}.md`;
942
970
  const full = resolvePhysicalPath(target, path, "TARGET", "target");
943
971
  const status = (0, _orkestrel_contract.attempt)(() => (0, node_fs.lstatSync)(full));
944
972
  if (!status.success) {
@@ -949,7 +977,7 @@ function readGuideReferences(target, dependencies) {
949
977
  });
950
978
  }
951
979
  if (!status.value.isFile() || status.value.isSymbolicLink() || status.value.nlink !== 1) throw new _src_core.ScaffoldError("TARGET", `Local guide is not a physical file at ${path}`, { path });
952
- current[dependency.name] = readFileText(target, path, "TARGET", "target");
980
+ current[name] = readFileText(target, path, "TARGET", "target");
953
981
  }
954
982
  return Object.freeze(current);
955
983
  }
@@ -3488,8 +3516,10 @@ var Materializer = class {
3488
3516
  * map, a fetched guide byte-equal to its entry verdicts `current`, anything
3489
3517
  * differing or absent from the map verdicts `behind`; WITHOUT the map, every
3490
3518
  * successful fetch verdicts `behind` (no reference means it needs syncing).
3491
- * `pull` builds that map itself from the TARGET's own `guides/src/<short>.md`
3492
- * mirrors, so its verdicts are target-relative; `write` commits only the
3519
+ * `pull` builds that map itself from the TARGET's declared dependency mirrors;
3520
+ * `mirror` builds it from the registry's exact organization package list,
3521
+ * excluding the target's own guide and issuing no registry version reads. Both
3522
+ * verdicts are target-relative; `write` commits only the
3493
3523
  * `behind` guides (never `current`, `missing`, or `failed`, which carries no
3494
3524
  * trustworthy content) under the same realpath-anchored containment law
3495
3525
  * `Materializer` enforces. After `destroy()` every method throws `DESTROYED`;
@@ -3541,50 +3571,67 @@ var Sync = class Sync {
3541
3571
  get emitter() {
3542
3572
  return this.#emitter;
3543
3573
  }
3574
+ async lookup(names) {
3575
+ this.#ensureAlive();
3576
+ return this.#lookup(names, Float64Array.of(this.#budget));
3577
+ }
3544
3578
  async guides(deps, current) {
3545
3579
  this.#ensureAlive();
3546
- return this.#guides(deps, current, Float64Array.of(this.#budget));
3580
+ const parsed = parseSyncDependencies(deps, false);
3581
+ return this.#guides(parsed.map((dependency) => dependency.name), current, Float64Array.of(this.#budget));
3547
3582
  }
3548
3583
  async versions(deps) {
3549
3584
  this.#ensureAlive();
3550
3585
  return this.#versions(deps, Float64Array.of(this.#budget));
3551
3586
  }
3552
- #guides(deps, current, allowance) {
3553
- const parsed = parseSyncDependencies(deps, false);
3587
+ #guides(names, current, allowance) {
3588
+ const parsed = parseSyncNames(names);
3554
3589
  if (parsed.length > this.#items) throw new _src_core.ScaffoldError("INVALID", "Sync dependency count exceeds its item limit", {
3555
3590
  items: parsed.length,
3556
3591
  limit: this.#items
3557
3592
  });
3558
- const reference = parseSyncCurrent(current, parsed.map((dependency) => dependency.name), this.#budget);
3559
- return Sync.#runPool(parsed, this.#concurrency, async (dep) => {
3560
- const short = packageShortName(dep.name);
3593
+ const reference = parseSyncCurrent(current, parsed, this.#budget);
3594
+ return Sync.#runPool(parsed, this.#concurrency, async (name) => {
3595
+ const short = packageShortName(name);
3561
3596
  const url = this.#guideUrl(short);
3562
3597
  const outcome = await Sync.#fetchText(url, this.#guidesTimeout, this.#retries, this.#limit, allowance, this.#controller.signal);
3563
- const guide = Sync.#toGuideSync(dep.name, short, outcome, reference);
3598
+ const guide = Sync.#toGuideSync(name, short, outcome, reference);
3564
3599
  if (outcome.kind === "failed") this.#emitter.emit("error", outcome.error);
3565
- this.#emitter.emit("guide", dep.name);
3600
+ this.#emitter.emit("guide", name);
3566
3601
  if (this.#strict && (guide.freshness === "missing" || guide.freshness === "failed")) throw new _src_core.ScaffoldError("FETCH", `Failed to fetch guide at ${url}`, {
3567
3602
  url,
3568
- name: dep.name
3603
+ name
3569
3604
  });
3570
3605
  return guide;
3571
3606
  });
3572
3607
  }
3573
- #versions(deps, allowance) {
3608
+ async #versions(deps, allowance) {
3574
3609
  const parsed = parseSyncDependencies(deps, true);
3610
+ const lookups = await this.#lookup(parsed.map((dependency) => dependency.name), allowance);
3611
+ const versions = [];
3612
+ for (let index = 0; index < parsed.length; index += 1) {
3613
+ const dep = parsed[index];
3614
+ const lookup = lookups[index];
3615
+ if (dep === void 0 || lookup === void 0) throw new _src_core.ScaffoldError("INVALID", "Sync version lookup result is incomplete");
3616
+ versions.push(Sync.#toVersionSync(dep, lookup));
3617
+ }
3618
+ return Object.freeze(versions);
3619
+ }
3620
+ #lookup(names, allowance) {
3621
+ const parsed = parseSyncNames(names);
3575
3622
  if (parsed.length > this.#items) throw new _src_core.ScaffoldError("INVALID", "Sync dependency count exceeds its item limit", {
3576
3623
  items: parsed.length,
3577
3624
  limit: this.#items
3578
3625
  });
3579
- return Sync.#runPool(parsed, this.#concurrency, async (dep) => {
3580
- const url = this.#registryUrl(dep.name);
3626
+ return Sync.#runPool(parsed, this.#concurrency, async (name) => {
3627
+ const url = this.#registryUrl(name);
3581
3628
  const outcome = await Sync.#fetchText(url, this.#registryTimeout, this.#retries, this.#limit, allowance, this.#controller.signal);
3582
- const version = Sync.#toVersionSync(dep, outcome);
3629
+ const version = Sync.#toVersionLookup(name, outcome);
3583
3630
  if (outcome.kind === "failed") this.#emitter.emit("error", outcome.error);
3584
- this.#emitter.emit("version", dep.name);
3631
+ this.#emitter.emit("version", name);
3585
3632
  if (this.#strict && (version.freshness === "missing" || version.freshness === "failed")) throw new _src_core.ScaffoldError("FETCH", `Failed to fetch registry version at ${url}`, {
3586
3633
  url,
3587
- name: dep.name
3634
+ name
3588
3635
  });
3589
3636
  return version;
3590
3637
  });
@@ -3617,16 +3664,9 @@ var Sync = class Sync {
3617
3664
  */
3618
3665
  async catalog() {
3619
3666
  this.#ensureAlive();
3620
- const orgUrl = this.#orgUrl();
3621
3667
  const allowance = Float64Array.of(this.#budget);
3622
- const orgOutcome = await Sync.#fetchText(orgUrl, this.#registryTimeout, this.#retries, this.#limit, allowance, this.#controller.signal);
3623
- const names = Sync.#toOrgPackages(orgOutcome, orgUrl);
3624
- if (names.length > this.#items) throw new _src_core.ScaffoldError("FETCH", `Package list at ${orgUrl} exceeds the item limit`, {
3625
- url: orgUrl,
3626
- items: names.length,
3627
- limit: this.#items
3628
- });
3629
- return [...await Sync.#runPool(names, this.#concurrency, async (name) => {
3668
+ const names = await this.#packages(allowance);
3669
+ return await Sync.#runPool(names, this.#concurrency, async (name) => {
3630
3670
  const packumentOutcome = await Sync.#fetchText(this.#registryUrl(name), this.#registryTimeout, this.#retries, this.#limit, allowance, this.#controller.signal);
3631
3671
  const packument = Sync.#toPackument(packumentOutcome);
3632
3672
  const short = packageShortName(name);
@@ -3642,7 +3682,7 @@ var Sync = class Sync {
3642
3682
  version: packument.version,
3643
3683
  description
3644
3684
  };
3645
- })].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
3685
+ });
3646
3686
  }
3647
3687
  async pull(target, dependencies) {
3648
3688
  this.#ensureAlive();
@@ -3651,10 +3691,21 @@ var Sync = class Sync {
3651
3691
  const deps = dependencies === void 0 ? declared : parseSyncDependencies(dependencies, false);
3652
3692
  if (dependencies !== void 0 && deps.some((dependency) => !declared.some((candidate) => candidate.name === dependency.name && candidate.range === dependency.range))) throw new _src_core.ScaffoldError("INVALID", "Sync pull selection is not declared by the target");
3653
3693
  const name = (0, _src_core.manifestToName)(manifest);
3654
- const guideDependencies = name === void 0 ? deps : deps.filter((dependency) => dependency.name !== name);
3655
- const current = readGuideReferences(target, guideDependencies);
3694
+ const guideNames = deps.map((dependency) => dependency.name).filter((dependency) => dependency !== name);
3695
+ const current = readGuideReferences(target, guideNames);
3656
3696
  const allowance = Float64Array.of(this.#budget);
3657
- const report = syncReportOf(target, await this.#guides(guideDependencies, current, allowance), await this.#versions(deps, allowance));
3697
+ const report = syncReportOf(target, await this.#guides(guideNames, current, allowance), await this.#versions(deps, allowance));
3698
+ this.#emitter.emit("done", report);
3699
+ return report;
3700
+ }
3701
+ async mirror(target) {
3702
+ this.#ensureAlive();
3703
+ const manifest = readManifest(target);
3704
+ const name = (0, _src_core.manifestToName)(manifest);
3705
+ const allowance = Float64Array.of(this.#budget);
3706
+ const names = (await this.#packages(allowance)).filter((candidate) => candidate !== name);
3707
+ const current = readGuideReferences(target, names);
3708
+ const report = syncReportOf(target, await this.#guides(names, current, allowance), []);
3658
3709
  this.#emitter.emit("done", report);
3659
3710
  return report;
3660
3711
  }
@@ -3747,6 +3798,17 @@ var Sync = class Sync {
3747
3798
  #orgUrl() {
3748
3799
  return `${this.#registryBase}/-/org/orkestrel/package`;
3749
3800
  }
3801
+ async #packages(allowance) {
3802
+ const url = this.#orgUrl();
3803
+ const outcome = await Sync.#fetchText(url, this.#registryTimeout, this.#retries, this.#limit, allowance, this.#controller.signal);
3804
+ const names = Sync.#toOrgPackages(outcome, url);
3805
+ if (names.length > this.#items) throw new _src_core.ScaffoldError("FETCH", `Package list at ${url} exceeds the item limit`, {
3806
+ url,
3807
+ items: names.length,
3808
+ limit: this.#items
3809
+ });
3810
+ return [...names].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
3811
+ }
3750
3812
  static async #runPool(items, concurrency, worker) {
3751
3813
  const results = new Array(items.length);
3752
3814
  const state = {
@@ -3925,34 +3987,42 @@ var Sync = class Sync {
3925
3987
  ...baseline === void 0 ? {} : { baseline }
3926
3988
  };
3927
3989
  }
3928
- static #toVersionSync(dep, outcome) {
3990
+ static #toVersionLookup(name, outcome) {
3929
3991
  if (outcome.kind === "missing") return {
3930
- name: dep.name,
3931
- range: dep.range,
3932
- latest: "",
3992
+ name,
3933
3993
  freshness: "missing",
3934
3994
  note: "HTTP 404"
3935
3995
  };
3936
3996
  if (outcome.kind === "failed") return {
3937
- name: dep.name,
3938
- range: dep.range,
3939
- latest: "",
3997
+ name,
3940
3998
  freshness: "failed",
3941
3999
  note: outcome.note
3942
4000
  };
3943
4001
  const latest = Sync.#parseLatest(outcome.text);
3944
4002
  if (latest === void 0) return {
4003
+ name,
4004
+ freshness: "failed",
4005
+ note: "malformed registry response (missing dist-tags.latest)"
4006
+ };
4007
+ return {
4008
+ name,
4009
+ latest,
4010
+ freshness: "behind"
4011
+ };
4012
+ }
4013
+ static #toVersionSync(dep, lookup) {
4014
+ if (lookup.freshness !== "behind") return {
3945
4015
  name: dep.name,
3946
4016
  range: dep.range,
3947
4017
  latest: "",
3948
- freshness: "failed",
3949
- note: "malformed registry response (missing dist-tags.latest)"
4018
+ freshness: lookup.freshness,
4019
+ note: lookup.note
3950
4020
  };
3951
4021
  return {
3952
4022
  name: dep.name,
3953
4023
  range: dep.range,
3954
- latest,
3955
- freshness: (0, _src_core.rangeToFreshness)(dep.range, latest)
4024
+ latest: lookup.latest,
4025
+ freshness: (0, _src_core.rangeToFreshness)(dep.range, lookup.latest)
3956
4026
  };
3957
4027
  }
3958
4028
  static #toOrgPackages(outcome, url) {
@@ -4151,6 +4221,7 @@ exports.parseSyncBase = parseSyncBase;
4151
4221
  exports.parseSyncBranch = parseSyncBranch;
4152
4222
  exports.parseSyncCurrent = parseSyncCurrent;
4153
4223
  exports.parseSyncDependencies = parseSyncDependencies;
4224
+ exports.parseSyncNames = parseSyncNames;
4154
4225
  exports.parseSyncOptions = parseSyncOptions;
4155
4226
  exports.parseWritePreconditions = parseWritePreconditions;
4156
4227
  exports.pruneTargets = pruneTargets;