@hawkeyexl/manni 2.10.1 → 2.11.0

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.
@@ -61,7 +61,7 @@ import {
61
61
  supportedExtensions,
62
62
  terminalConfirm,
63
63
  toJsonText
64
- } from "./chunk-O2V45AVG.js";
64
+ } from "./chunk-BUXVMARH.js";
65
65
 
66
66
  // src/meta/cli.ts
67
67
  import { existsSync } from "node:fs";
@@ -1517,4 +1517,4 @@ export {
1517
1517
  reportConfig,
1518
1518
  buildProgram
1519
1519
  };
1520
- //# sourceMappingURL=chunk-5GVRMS6B.js.map
1520
+ //# sourceMappingURL=chunk-V5NTFMUD.js.map
@@ -29,10 +29,13 @@ import {
29
29
  findConfigFile,
30
30
  findFamilyConfigFile,
31
31
  findGitRoot,
32
+ findPageManifests,
32
33
  findWindows,
33
34
  generateEncryptionKey,
35
+ gitIgnored,
34
36
  gitignoreOptions,
35
37
  hasFrontmatterFence,
38
+ hasPagePlaceholder,
36
39
  hashLines,
37
40
  hashRange,
38
41
  heldManifest,
@@ -41,6 +44,7 @@ import {
41
44
  isIgnoredByGit,
42
45
  isKeyedPin,
43
46
  isMember,
47
+ isMissing,
44
48
  isValidEncryptionKey,
45
49
  lineSpec,
46
50
  loadExternalMetadata,
@@ -51,6 +55,7 @@ import {
51
55
  orphanEntries,
52
56
  orphanError,
53
57
  orphanJoins,
58
+ pageManifestPath,
54
59
  palette,
55
60
  parseLines,
56
61
  pinOfLines,
@@ -72,10 +77,11 @@ import {
72
77
  supportedExtensions,
73
78
  toBodyLines,
74
79
  toFileLines,
80
+ warn,
75
81
  withValueAt,
76
82
  writeEncryptionKey,
77
83
  writeFileAtomic
78
- } from "./chunk-O2V45AVG.js";
84
+ } from "./chunk-BUXVMARH.js";
79
85
 
80
86
  // src/meta/core/reencrypt.ts
81
87
  import { extname } from "node:path";
@@ -2481,6 +2487,7 @@ function withClaimLines(entry, lines) {
2481
2487
  }
2482
2488
 
2483
2489
  // src/cite/core/sidecar.ts
2490
+ import { existsSync } from "node:fs";
2484
2491
  import { isAbsolute, relative, resolve, sep as sep2 } from "node:path";
2485
2492
  var CITATIONS_KEY2 = "citations";
2486
2493
  var toPosix = (path) => path.split(sep2).join("/");
@@ -2513,7 +2520,8 @@ async function loadCitationSidecars(opts) {
2513
2520
  collection: collection.name,
2514
2521
  path,
2515
2522
  file: reportedPath(path, opts.base),
2516
- join: externalMetadataJoin(manifest)
2523
+ join: externalMetadataJoin(manifest),
2524
+ ...hasPagePlaceholder(manifest.file) ? { pattern: manifest.file } : {}
2517
2525
  });
2518
2526
  }
2519
2527
  scoped.push({ ...collection, externalMetadata: owning });
@@ -2521,7 +2529,8 @@ async function loadCitationSidecars(opts) {
2521
2529
  if (manifests.length === 0) return null;
2522
2530
  const index = await loadExternalMetadata(scoped, {
2523
2531
  configDir: opts.configDir,
2524
- base: opts.base
2532
+ base: opts.base,
2533
+ ...opts.pages === void 0 ? {} : { pages: opts.pages }
2525
2534
  });
2526
2535
  if (index === null) return null;
2527
2536
  return sidecars(index, manifests, scoped, opts);
@@ -2529,15 +2538,23 @@ async function loadCitationSidecars(opts) {
2529
2538
  function sidecars(index, manifests, collections, opts) {
2530
2539
  const toError = opts.toError ?? ((message) => new CiteError(message));
2531
2540
  const { configDir, base } = opts;
2541
+ const pageOwner = (owner, label) => {
2542
+ if (owner.pattern === void 0) return owner;
2543
+ const resolved = pageManifestPath(owner.pattern, configDir, resolve(base, label));
2544
+ if ("outside" in resolved) return void 0;
2545
+ return { ...owner, path: resolved.abs, file: reportedPath(resolved.abs, base) };
2546
+ };
2532
2547
  const forPage = (label, content, format) => {
2533
2548
  const members = memberOf(collections, configDir, base, label);
2534
2549
  if (members.length === 0) return { joins: [] };
2535
2550
  const mine = manifests.filter((m) => members.includes(m.collection));
2536
- const [owner, second] = mine;
2537
- if (owner === void 0) return { joins: [] };
2551
+ const [declared, second] = mine;
2552
+ if (declared === void 0) return { joins: [] };
2538
2553
  if (second !== void 0) {
2539
- throw toError(twoManifestsRefusal(label, owner.collection, second.collection));
2554
+ throw toError(twoManifestsRefusal(label, declared.collection, second.collection));
2540
2555
  }
2556
+ const owner = pageOwner(declared, label);
2557
+ if (owner === void 0) return { joins: [] };
2541
2558
  const extractor = pickExtractor(label, format);
2542
2559
  const extracted = extractor.extract(content, label);
2543
2560
  const merged = mergeExternalMetadata(label, extracted, index, members, base, {
@@ -2571,15 +2588,33 @@ function sidecars(index, manifests, collections, opts) {
2571
2588
  }
2572
2589
  return out;
2573
2590
  };
2591
+ const ignoredManifests = async (loaded) => {
2592
+ const existing = /* @__PURE__ */ new Set();
2593
+ for (const label of loaded) {
2594
+ const members = memberOf(collections, configDir, base, label);
2595
+ for (const declared of manifests) {
2596
+ if (declared.pattern === void 0 || !members.includes(declared.collection)) continue;
2597
+ const owner = pageOwner(declared, label);
2598
+ if (owner !== void 0 && existsSync(owner.path)) existing.add(owner.file);
2599
+ }
2600
+ }
2601
+ if (existing.size === 0) return [];
2602
+ const { ignored } = await gitIgnored([...existing], base);
2603
+ return [...existing].filter((file) => ignored.has(file));
2604
+ };
2574
2605
  return {
2575
2606
  manifests,
2576
2607
  configDir,
2577
2608
  base,
2578
2609
  forPage,
2579
2610
  orphans: (loaded, covered) => orphanEntries(index, loaded, base, covered),
2580
- orphanJoins: (matched, covered) => orphanJoins(index, matched, covered)
2611
+ orphanJoins: (matched, covered) => orphanJoins(index, matched, covered),
2612
+ ignoredManifests
2581
2613
  };
2582
2614
  }
2615
+ function ignoredManifestWarning(file) {
2616
+ return `${file} is covered by .gitignore, so CI checks out a page with no citations.`;
2617
+ }
2583
2618
  function orphanRefusal(orphans, toError = (m) => new CiteError(m)) {
2584
2619
  return toError(orphanError(orphans).message);
2585
2620
  }
@@ -2596,7 +2631,8 @@ function sidecarsFor(run3, over = {}) {
2596
2631
  base: over.base ?? run3.base,
2597
2632
  configSource: run3.configSource ?? run3.configPath ?? "the config",
2598
2633
  ...run3.key === void 0 ? {} : { key: run3.key },
2599
- ...over.toError === void 0 ? {} : { toError: over.toError }
2634
+ ...over.toError === void 0 ? {} : { toError: over.toError },
2635
+ ...over.pages === void 0 ? {} : { pages: over.pages }
2600
2636
  });
2601
2637
  }
2602
2638
 
@@ -2875,13 +2911,18 @@ var ManifestSet = class {
2875
2911
  const already = this.held.get(manifest.path);
2876
2912
  if (already !== void 0) return already;
2877
2913
  let before;
2914
+ let absent = false;
2878
2915
  try {
2879
2916
  before = await readFile2(manifest.path, "utf8");
2880
2917
  } catch (error) {
2881
- const reason = error instanceof Error ? error.message : String(error);
2882
- throw new CiteError(`Manifest ${manifest.file} could not be read: ${reason}`);
2918
+ if (manifest.pattern === void 0 || !isMissing(error)) {
2919
+ const reason = error instanceof Error ? error.message : String(error);
2920
+ throw new CiteError(`Manifest ${manifest.file} could not be read: ${reason}`);
2921
+ }
2922
+ before = "";
2923
+ absent = true;
2883
2924
  }
2884
- const fresh = heldManifest(manifest.path, manifest.file, before);
2925
+ const fresh = heldManifest(manifest.path, manifest.file, before, absent);
2885
2926
  this.held.set(manifest.path, fresh);
2886
2927
  return fresh;
2887
2928
  }
@@ -2953,7 +2994,7 @@ var ManifestSet = class {
2953
2994
  * that may be seconds old.
2954
2995
  */
2955
2996
  async commit(io = {}) {
2956
- const write = io.write ?? writeFileAtomic;
2997
+ const write = io.write ?? ((at2, text) => writeFileAtomic(at2, text, { createParents: true }));
2957
2998
  const read = io.read ?? ((at2) => readFile2(at2, "utf8"));
2958
2999
  const landed = [];
2959
3000
  for (const held of this.held.values()) {
@@ -3488,6 +3529,15 @@ async function commitFor(opts) {
3488
3529
  if (client === void 0 || !await client.available()) return void 0;
3489
3530
  return await client.head() ?? void 0;
3490
3531
  }
3532
+ async function assertCommitHolds(opts, commit, path, range, pin, key) {
3533
+ const client = opts.gitClient;
3534
+ if (client === void 0 || !await client.available()) return;
3535
+ const history = await historyOf(client, commit, path, range, pin, key, void 0);
3536
+ if (history.kind !== "never-true") return;
3537
+ throw new CiteError(
3538
+ `${opts.src} is not committed: ${commit.slice(0, 7)} does not contain those lines. Commit the source, or use --no-commit-sha.`
3539
+ );
3540
+ }
3491
3541
  function refusalFor(reason, path, encrypted) {
3492
3542
  switch (reason) {
3493
3543
  case "untracked":
@@ -3521,7 +3571,11 @@ async function mintCitation(opts) {
3521
3571
  const file = pinKey !== void 0 && !range.encrypted ? encryptSourcePath(read.resolvedPath, pinKey) : range.path;
3522
3572
  const lines = rangeLines(range);
3523
3573
  const commitSha = await commitFor(opts);
3524
- const source = { file, integrity: hashLines(joined, pinKey) };
3574
+ const integrity = hashLines(joined, pinKey);
3575
+ if (commitSha !== void 0) {
3576
+ await assertCommitHolds(opts, commitSha, read.resolvedPath, range, integrity, pinKey);
3577
+ }
3578
+ const source = { file, integrity };
3525
3579
  const ordered = {
3526
3580
  file: source.file,
3527
3581
  ...lines === void 0 ? {} : { lines },
@@ -3718,7 +3772,7 @@ async function resolveCiteRun(opts) {
3718
3772
  }
3719
3773
 
3720
3774
  // src/cite/commands/check.ts
3721
- import { existsSync } from "node:fs";
3775
+ import { existsSync as existsSync2 } from "node:fs";
3722
3776
  import { readFile as readFile3 } from "node:fs/promises";
3723
3777
  import { resolve as resolve3 } from "node:path";
3724
3778
  function joinHits() {
@@ -3813,7 +3867,7 @@ async function prepareRun(opts, action, verb, sources = {}) {
3813
3867
  pageGitClient: pageGit
3814
3868
  };
3815
3869
  if (checkSources) pageOptions.sourceIndex = await sourceIndexFor(run3.root, git);
3816
- const sidecars2 = await sidecarsFor(run3);
3870
+ const sidecars2 = await sidecarsFor(run3, { pages: files.map((file) => resolve3(base, file)) });
3817
3871
  const setupFor = (label, content) => {
3818
3872
  const page = sidecars2?.forPage(label, content, forced?.name);
3819
3873
  if (page?.owner === void 0) return { options: pageOptions };
@@ -3847,11 +3901,13 @@ async function prepareRun(opts, action, verb, sources = {}) {
3847
3901
  ...covered === void 0 ? {} : { covered }
3848
3902
  };
3849
3903
  }
3850
- function assertNoOrphans(prepared) {
3904
+ async function assertNoOrphans(prepared) {
3851
3905
  const { sidecars: sidecars2 } = prepared;
3852
3906
  if (sidecars2 === null || prepared.scopedByFlags) return;
3853
3907
  const orphans = sidecars2.orphans(prepared.files, prepared.covered);
3854
3908
  if (orphans.length > 0) throw orphanRefusal(orphans);
3909
+ if (prepared.covered !== void 0) return;
3910
+ for (const file of await sidecars2.ignoredManifests(prepared.files)) warn(ignoredManifestWarning(file));
3855
3911
  }
3856
3912
  function assertNoOrphanJoins(prepared, hits) {
3857
3913
  const { sidecars: sidecars2 } = prepared;
@@ -3873,7 +3929,7 @@ function sayNotices(pages, onNotice, also = []) {
3873
3929
  async function runCheck(opts) {
3874
3930
  const prepared = await prepareRun(opts, "checked", "check");
3875
3931
  const { cwd, run: run3, files, gitignoreSkipped, usingStdin, forced, pageOptions, git } = prepared;
3876
- assertNoOrphans(prepared);
3932
+ await assertNoOrphans(prepared);
3877
3933
  const hits = joinHits();
3878
3934
  const pages = [];
3879
3935
  const checkOne = async (label, content) => {
@@ -3891,7 +3947,7 @@ async function runCheck(opts) {
3891
3947
  const request = resolveBaselineRequest(opts, run3.config?.baseline, run3.configDir, cwd, {
3892
3948
  current: DEFAULT_CITE_BASELINE_PATH
3893
3949
  });
3894
- if (request !== null && !request.write && !existsSync(request.absPath)) {
3950
+ if (request !== null && !request.write && !existsSync2(request.absPath)) {
3895
3951
  throw new CiteError(
3896
3952
  `Baseline "${request.label}" not found. Record one with \`manni cite check --write-baseline\`, or drop --baseline.`
3897
3953
  );
@@ -3987,7 +4043,11 @@ async function runAdd(opts) {
3987
4043
  const path = usingStdin ? void 0 : resolve4(cwd, opts.page);
3988
4044
  const label = path === void 0 ? STDIN_LABEL : toPosix2(relative2(cwd, path));
3989
4045
  const content = path === void 0 ? opts.stdinContent ?? "" : await readPageFile(path, label);
3990
- const sidecars2 = await sidecarsFor(run3, { base: cwd });
4046
+ const sidecars2 = await sidecarsFor(run3, {
4047
+ base: cwd,
4048
+ // A `{page}` manifest (0058) is read for the one page being cited.
4049
+ ...path === void 0 ? {} : { pages: [path] }
4050
+ });
3991
4051
  const firstManifest = sidecars2?.manifests[0];
3992
4052
  if (usingStdin && firstManifest !== void 0) {
3993
4053
  const keyedBy = firstManifest.join === "path" ? "path" : firstManifest.join;
@@ -4284,7 +4344,7 @@ async function runRemove(opts) {
4284
4344
  resolve: false
4285
4345
  });
4286
4346
  const { run: run3, files, usingStdin, forced } = prepared;
4287
- assertNoOrphans(prepared);
4347
+ await assertNoOrphans(prepared);
4288
4348
  const hits = joinHits();
4289
4349
  const manifests = new ManifestSet();
4290
4350
  const matched = /* @__PURE__ */ new Set();
@@ -4505,8 +4565,11 @@ function settles(plan) {
4505
4565
  // refusal prints, so it is not also reported as skipped.
4506
4566
  case "claim-replaced":
4507
4567
  return "claim-changed";
4508
- // A shifted entry was `current`, so it had no finding to settle.
4568
+ // A shifted entry was `current`, so it had no finding to settle. A
4569
+ // re-committed one was `current` too: `check` never reported it, which is
4570
+ // why the repair is asked for rather than offered.
4509
4571
  case "claim-shifted":
4572
+ case "source-recommitted":
4510
4573
  return "";
4511
4574
  }
4512
4575
  }
@@ -4544,6 +4607,8 @@ function apply2(content, format, plan) {
4544
4607
  }
4545
4608
  return out;
4546
4609
  }
4610
+ case "source-recommitted":
4611
+ return spliceEntryField(content, format, index, ["source", "commit-sha"], plan.commitSha);
4547
4612
  case "claim-accepted": {
4548
4613
  let out = spliceEntryField(content, format, index, ["claim", "integrity"], plan.pin);
4549
4614
  if (plan.lines !== void 0) {
@@ -4612,6 +4677,12 @@ function applyToEntry(entry, plan) {
4612
4677
  if (plan.commitSha !== void 0) source["commit-sha"] = plan.commitSha;
4613
4678
  return true;
4614
4679
  }
4680
+ case "source-recommitted": {
4681
+ const source = end("source");
4682
+ if (source === void 0) return false;
4683
+ source["commit-sha"] = plan.commitSha;
4684
+ return true;
4685
+ }
4615
4686
  case "claim-accepted": {
4616
4687
  const claim = end("claim");
4617
4688
  if (claim === void 0) return false;
@@ -4728,6 +4799,19 @@ function rewriteOf(plan) {
4728
4799
  from: source.src,
4729
4800
  to: plan.to
4730
4801
  };
4802
+ case "source-recommitted":
4803
+ return {
4804
+ ...base,
4805
+ end: "source",
4806
+ reason: "recommitted",
4807
+ // The source itself held. Only its date was wrong.
4808
+ status: "current",
4809
+ from: plan.from,
4810
+ to: plan.commitSha,
4811
+ fromCommit: plan.from,
4812
+ toCommit: plan.commitSha,
4813
+ src: source.src
4814
+ };
4731
4815
  case "claim-accepted": {
4732
4816
  const was = citation.claim?.integrity ?? "";
4733
4817
  const out = {
@@ -4921,12 +5005,32 @@ function citationsIn(rows) {
4921
5005
  async function runUpdate(opts) {
4922
5006
  const prepared = await prepareRun(opts, "updated", "update", { require: true });
4923
5007
  const { run: run3, files, usingStdin, forced, pageOptions, git } = prepared;
4924
- assertNoOrphans(prepared);
5008
+ await assertNoOrphans(prepared);
4925
5009
  const hits = joinHits();
4926
5010
  const manifests = new ManifestSet();
4927
5011
  const writes = new WrittenFiles();
4928
5012
  const only = opts.only !== void 0 && opts.only.length > 0 ? new Set(opts.only) : void 0;
4929
5013
  const accept = opts.accept === true;
5014
+ const recommit = opts.recommit === true;
5015
+ if (recommit && !await git.available()) {
5016
+ throw new CiteError(
5017
+ "--recommit needs git to verify recorded commits, and git is not available here."
5018
+ );
5019
+ }
5020
+ const recommitFor = async (result) => {
5021
+ const recorded = result.citation.source["commit-sha"];
5022
+ const path = result.source.resolvedPath;
5023
+ if (recorded === void 0 || path === void 0) return void 0;
5024
+ const range = sourceRange(result.citation.source);
5025
+ const pin = result.citation.source.integrity;
5026
+ const key = range.encrypted ? run3.key : void 0;
5027
+ const was = await historyOf(git, recorded, path, range, pin, key, void 0);
5028
+ if (was.kind !== "never-true") return void 0;
5029
+ const head = await git.head();
5030
+ if (head === null || head === recorded) return void 0;
5031
+ const now = await historyOf(git, head, path, range, pin, key, void 0);
5032
+ return now.kind === "original" ? head : void 0;
5033
+ };
4930
5034
  const plansFor = async (result, entry, page, lines, lineNow, declined) => {
4931
5035
  const out = [];
4932
5036
  const claim = result.claim;
@@ -5018,6 +5122,17 @@ async function runUpdate(opts) {
5018
5122
  }
5019
5123
  }
5020
5124
  }
5125
+ if (recommit && result.source.status === "current") {
5126
+ const to = await recommitFor(result);
5127
+ if (to !== void 0) {
5128
+ out.push({
5129
+ kind: "source-recommitted",
5130
+ result,
5131
+ from: result.citation.source["commit-sha"] ?? "",
5132
+ commitSha: to
5133
+ });
5134
+ }
5135
+ }
5021
5136
  if (!accept) return out;
5022
5137
  if (claim !== null && claim.status === "changed" && entry !== void 0 && result.anchor !== "marker") {
5023
5138
  const held = claim.fileLines === void 0 ? void 0 : parseLines(claim.fileLines);
@@ -5609,6 +5724,13 @@ function rowOrder(citations) {
5609
5724
  return left - right;
5610
5725
  });
5611
5726
  }
5727
+ function oneManifest(citations) {
5728
+ const files = new Set(
5729
+ citations.map((result) => result.origin.kind === "manifest" ? result.origin.file : void 0)
5730
+ );
5731
+ const [only, ...rest] = [...files];
5732
+ return rest.length === 0 ? only : void 0;
5733
+ }
5612
5734
  function padTo(text, width, plain) {
5613
5735
  return width <= plain ? text : text + " ".repeat(width - plain);
5614
5736
  }
@@ -5627,12 +5749,14 @@ function renderCheckPretty(run3, opts) {
5627
5749
  const errors = reported.filter((f) => f.severity === "error").length;
5628
5750
  const warned = reported.some((f) => f.severity === "warning");
5629
5751
  const forgiven = baselined2.length > 0 ? c.dim(` (${String(baselined2.length)} baselined)`) : "";
5752
+ const manifest = oneManifest(page.citations);
5753
+ const heading = manifest === void 0 ? page.file : `${page.file} ${c.dim(manifest)}`;
5630
5754
  if (reported.length === 0) {
5631
5755
  if (quiet) return;
5632
- lines.push(`${c.green("\u2713")} ${page.file}${forgiven}`);
5756
+ lines.push(`${c.green("\u2713")} ${heading}${forgiven}`);
5633
5757
  } else {
5634
5758
  const mark = errors > 0 ? c.red("\u2717") : warned ? c.yellow("\u26A0") : c.dim("\u2139");
5635
- lines.push(`${mark} ${page.file}${forgiven}`);
5759
+ lines.push(`${mark} ${heading}${forgiven}`);
5636
5760
  }
5637
5761
  const isBaselined = new Set(baselined2);
5638
5762
  const placed = /* @__PURE__ */ new Set();
@@ -5664,7 +5788,7 @@ function renderCheckPretty(run3, opts) {
5664
5788
  label: labelOf(result),
5665
5789
  claim: claimColumn(result),
5666
5790
  source: `${sourceColumn(result, opts, c.dim)}${forgivenEnd}`,
5667
- where: result.origin.kind === "manifest" ? `${result.origin.file}${result.origin.line === void 0 ? "" : `:${String(result.origin.line)}`}` : "",
5791
+ where: result.origin.kind === "manifest" && manifest === void 0 ? `${result.origin.file}${result.origin.line === void 0 ? "" : `:${String(result.origin.line)}`}` : "",
5668
5792
  under
5669
5793
  });
5670
5794
  }
@@ -5753,6 +5877,11 @@ function rewriteLine(rewrite) {
5753
5877
  if (rewrite.end === "marker") {
5754
5878
  return `marker line ${rewrite.from} -> ${rewrite.to} (misplaced)`;
5755
5879
  }
5880
+ if (rewrite.reason === "recommitted") {
5881
+ const from = shortCommit(rewrite.fromCommit ?? "");
5882
+ const to = shortCommit(rewrite.toCommit ?? "");
5883
+ return `source ${shortSrc(rewrite.src ?? "")} commit ${from} -> ${to} (did not contain the pinned lines)`;
5884
+ }
5756
5885
  if (rewrite.reason === "shifted") {
5757
5886
  return `claim ${word} ${rewrite.from} -> ${rewrite.to} (shifted by a marker)`;
5758
5887
  }
@@ -5968,7 +6097,7 @@ var KeyError = class extends ToolError {
5968
6097
  };
5969
6098
 
5970
6099
  // src/key/commands/rotate.ts
5971
- import { existsSync as existsSync2 } from "node:fs";
6100
+ import { existsSync as existsSync3 } from "node:fs";
5972
6101
  import { readFile as readFile5 } from "node:fs/promises";
5973
6102
  import { isAbsolute as isAbsolute3, join as join2, relative as relative3, resolve as resolve7, sep as sep3 } from "node:path";
5974
6103
  import { parse as parseYaml } from "yaml";
@@ -6089,31 +6218,63 @@ async function planManifests(opts) {
6089
6218
  )
6090
6219
  })).filter((c) => c.externalMetadata.length > 0);
6091
6220
  if (local.length === 0) return [];
6221
+ const pages = new Set(opts.pages);
6222
+ if (opts.whole) {
6223
+ for (const c of local) {
6224
+ for (const m of c.externalMetadata) {
6225
+ if (!hasPagePlaceholder(m.file)) continue;
6226
+ for (const found of findPageManifests(m.file, opts.configDir, (rel) => isMember(c, rel))) {
6227
+ for (const entry of found.entries) pages.add(entry.pageAbs);
6228
+ }
6229
+ }
6230
+ }
6231
+ }
6092
6232
  const index = await loadExternalMetadata(local, {
6093
6233
  configDir: opts.configDir,
6094
- base: opts.base
6234
+ base: opts.base,
6235
+ pages: [...pages]
6095
6236
  });
6096
6237
  if (index === null) return [];
6097
6238
  const plans = /* @__PURE__ */ new Map();
6098
6239
  const located = /* @__PURE__ */ new Map();
6240
+ const locate = (collection, abs, join4) => {
6241
+ located.set(`${collection}\0${reportedPath2(abs, opts.base)}\0${join4}`, { abs, collection });
6242
+ };
6099
6243
  for (const c of local) {
6100
6244
  for (const m of c.externalMetadata) {
6101
6245
  const join4 = externalMetadataJoin(m);
6102
- const abs = isAbsolute3(m.file) ? m.file : resolve7(opts.configDir, m.file);
6103
- const file = reportedPath2(abs, opts.base);
6104
- located.set(`${c.name}\0${file}\0${join4}`, abs);
6105
- if (plans.has(abs)) continue;
6106
- plans.set(abs, {
6107
- manifest: { file, collection: c.name, rewritten: [], skipped: [], written: false },
6108
- path: abs,
6109
- text: await readFile5(abs, "utf8"),
6110
- changed: false
6111
- });
6246
+ if (!hasPagePlaceholder(m.file)) {
6247
+ locate(c.name, isAbsolute3(m.file) ? m.file : resolve7(opts.configDir, m.file), join4);
6248
+ continue;
6249
+ }
6250
+ for (const page of pages) {
6251
+ const resolved = pageManifestPath(m.file, opts.configDir, page);
6252
+ if (!("outside" in resolved)) locate(c.name, resolved.abs, join4);
6253
+ }
6112
6254
  }
6113
6255
  }
6256
+ const planFor = async (key) => {
6257
+ const where = located.get(key);
6258
+ if (where === void 0) return void 0;
6259
+ const known = plans.get(where.abs);
6260
+ if (known !== void 0) return known;
6261
+ const plan = {
6262
+ manifest: {
6263
+ file: reportedPath2(where.abs, opts.base),
6264
+ collection: where.collection,
6265
+ rewritten: [],
6266
+ skipped: [],
6267
+ written: false
6268
+ },
6269
+ path: where.abs,
6270
+ text: await readFile5(where.abs, "utf8"),
6271
+ changed: false
6272
+ };
6273
+ plans.set(where.abs, plan);
6274
+ return plan;
6275
+ };
6114
6276
  for (const entry of index.entries) {
6115
- const abs = located.get(`${entry.collection}\0${entry.file}\0${entry.join}`);
6116
- const plan = abs === void 0 ? void 0 : plans.get(abs);
6277
+ const plan = await planFor(`${entry.collection}\0${entry.file}\0${entry.join}`);
6117
6278
  if (plan === void 0) continue;
6118
6279
  const data = {};
6119
6280
  for (const [key, supplied] of valuesOf(index, entry)) {
@@ -6222,7 +6383,9 @@ async function runKeyRotate(opts) {
6222
6383
  base,
6223
6384
  configSource: file.source,
6224
6385
  key: current,
6225
- toError: toKeyError
6386
+ toError: toKeyError,
6387
+ // A `{page}` manifest (0058) is read for exactly the pages rotated.
6388
+ pages: files.map((rel) => resolve7(base, rel))
6226
6389
  });
6227
6390
  const manifests = new ManifestSet();
6228
6391
  const planned = [];
@@ -6285,7 +6448,9 @@ async function runKeyRotate(opts) {
6285
6448
  configDir: file.dir,
6286
6449
  base,
6287
6450
  fromKey,
6288
- toKey
6451
+ toKey,
6452
+ pages: files.map((rel) => resolve7(base, rel)),
6453
+ whole: !narrowed
6289
6454
  });
6290
6455
  const reencrypted = planned.reduce((n, p) => n + p.page.rewritten.length, 0) + valueManifests.reduce((n, m) => n + m.manifest.rewritten.length, 0);
6291
6456
  const skipped = planned.reduce((n, p) => n + p.page.skipped.length, 0) + valueManifests.reduce((n, m) => n + m.manifest.skipped.length, 0);
@@ -6328,13 +6493,13 @@ async function runKeyRotate(opts) {
6328
6493
  keyWritten: outcome === "written" || outcome === "finished",
6329
6494
  outcome,
6330
6495
  ...keyFile === null ? {} : { configSource: keyFile.source },
6331
- baselineStale: wrote && existsSync2(baseline),
6496
+ baselineStale: wrote && existsSync3(baseline),
6332
6497
  exitCode: skipped > 0 ? 1 : 0
6333
6498
  };
6334
6499
  }
6335
6500
 
6336
6501
  // src/key/commands/set.ts
6337
- import { existsSync as existsSync3 } from "node:fs";
6502
+ import { existsSync as existsSync4 } from "node:fs";
6338
6503
  import { basename, dirname, join as join3, relative as relative4, resolve as resolve8 } from "node:path";
6339
6504
  var BAD_VALUE = "The key must be at least 32 hex or base64url characters. Run `manni key set` with no value to generate one.";
6340
6505
  var ENV_SET = `${ENCRYPTION_KEY_ENV} is set, so a key written to config would never be read. Unset it, or keep the key in the secret.`;
@@ -6344,7 +6509,7 @@ function refuseBesideLegacy(path, cwd) {
6344
6509
  const name = basename(path);
6345
6510
  for (const legacy of LEGACY_CONFIG_NAMES) {
6346
6511
  const found = join3(dir, legacy);
6347
- if (!existsSync3(found)) continue;
6512
+ if (!existsSync4(found)) continue;
6348
6513
  const source = relative4(cwd, found).replace(/\\/g, "/");
6349
6514
  throw new KeyError(
6350
6515
  `${source} is a single-tool config; a ${name} beside it would hide it. Move its keys under meta: in ${name} first.`
@@ -6413,4 +6578,4 @@ export {
6413
6578
  runKeyRotate,
6414
6579
  runKeySet
6415
6580
  };
6416
- //# sourceMappingURL=chunk-HA4CK35M.js.map
6581
+ //# sourceMappingURL=chunk-VYFRHPCL.js.map