@hawkeyexl/manni 2.10.2 → 2.11.1

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.
@@ -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-KJLPOY2I.js";
84
+ } from "./chunk-7ODP2AB3.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()) {
@@ -3731,7 +3772,7 @@ async function resolveCiteRun(opts) {
3731
3772
  }
3732
3773
 
3733
3774
  // src/cite/commands/check.ts
3734
- import { existsSync } from "node:fs";
3775
+ import { existsSync as existsSync2 } from "node:fs";
3735
3776
  import { readFile as readFile3 } from "node:fs/promises";
3736
3777
  import { resolve as resolve3 } from "node:path";
3737
3778
  function joinHits() {
@@ -3826,7 +3867,7 @@ async function prepareRun(opts, action, verb, sources = {}) {
3826
3867
  pageGitClient: pageGit
3827
3868
  };
3828
3869
  if (checkSources) pageOptions.sourceIndex = await sourceIndexFor(run3.root, git);
3829
- const sidecars2 = await sidecarsFor(run3);
3870
+ const sidecars2 = await sidecarsFor(run3, { pages: files.map((file) => resolve3(base, file)) });
3830
3871
  const setupFor = (label, content) => {
3831
3872
  const page = sidecars2?.forPage(label, content, forced?.name);
3832
3873
  if (page?.owner === void 0) return { options: pageOptions };
@@ -3860,11 +3901,13 @@ async function prepareRun(opts, action, verb, sources = {}) {
3860
3901
  ...covered === void 0 ? {} : { covered }
3861
3902
  };
3862
3903
  }
3863
- function assertNoOrphans(prepared) {
3904
+ async function assertNoOrphans(prepared) {
3864
3905
  const { sidecars: sidecars2 } = prepared;
3865
3906
  if (sidecars2 === null || prepared.scopedByFlags) return;
3866
3907
  const orphans = sidecars2.orphans(prepared.files, prepared.covered);
3867
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));
3868
3911
  }
3869
3912
  function assertNoOrphanJoins(prepared, hits) {
3870
3913
  const { sidecars: sidecars2 } = prepared;
@@ -3886,7 +3929,7 @@ function sayNotices(pages, onNotice, also = []) {
3886
3929
  async function runCheck(opts) {
3887
3930
  const prepared = await prepareRun(opts, "checked", "check");
3888
3931
  const { cwd, run: run3, files, gitignoreSkipped, usingStdin, forced, pageOptions, git } = prepared;
3889
- assertNoOrphans(prepared);
3932
+ await assertNoOrphans(prepared);
3890
3933
  const hits = joinHits();
3891
3934
  const pages = [];
3892
3935
  const checkOne = async (label, content) => {
@@ -3904,7 +3947,7 @@ async function runCheck(opts) {
3904
3947
  const request = resolveBaselineRequest(opts, run3.config?.baseline, run3.configDir, cwd, {
3905
3948
  current: DEFAULT_CITE_BASELINE_PATH
3906
3949
  });
3907
- if (request !== null && !request.write && !existsSync(request.absPath)) {
3950
+ if (request !== null && !request.write && !existsSync2(request.absPath)) {
3908
3951
  throw new CiteError(
3909
3952
  `Baseline "${request.label}" not found. Record one with \`manni cite check --write-baseline\`, or drop --baseline.`
3910
3953
  );
@@ -4000,7 +4043,11 @@ async function runAdd(opts) {
4000
4043
  const path = usingStdin ? void 0 : resolve4(cwd, opts.page);
4001
4044
  const label = path === void 0 ? STDIN_LABEL : toPosix2(relative2(cwd, path));
4002
4045
  const content = path === void 0 ? opts.stdinContent ?? "" : await readPageFile(path, label);
4003
- 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
+ });
4004
4051
  const firstManifest = sidecars2?.manifests[0];
4005
4052
  if (usingStdin && firstManifest !== void 0) {
4006
4053
  const keyedBy = firstManifest.join === "path" ? "path" : firstManifest.join;
@@ -4297,7 +4344,7 @@ async function runRemove(opts) {
4297
4344
  resolve: false
4298
4345
  });
4299
4346
  const { run: run3, files, usingStdin, forced } = prepared;
4300
- assertNoOrphans(prepared);
4347
+ await assertNoOrphans(prepared);
4301
4348
  const hits = joinHits();
4302
4349
  const manifests = new ManifestSet();
4303
4350
  const matched = /* @__PURE__ */ new Set();
@@ -4958,7 +5005,7 @@ function citationsIn(rows) {
4958
5005
  async function runUpdate(opts) {
4959
5006
  const prepared = await prepareRun(opts, "updated", "update", { require: true });
4960
5007
  const { run: run3, files, usingStdin, forced, pageOptions, git } = prepared;
4961
- assertNoOrphans(prepared);
5008
+ await assertNoOrphans(prepared);
4962
5009
  const hits = joinHits();
4963
5010
  const manifests = new ManifestSet();
4964
5011
  const writes = new WrittenFiles();
@@ -5677,6 +5724,13 @@ function rowOrder(citations) {
5677
5724
  return left - right;
5678
5725
  });
5679
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
+ }
5680
5734
  function padTo(text, width, plain) {
5681
5735
  return width <= plain ? text : text + " ".repeat(width - plain);
5682
5736
  }
@@ -5695,12 +5749,14 @@ function renderCheckPretty(run3, opts) {
5695
5749
  const errors = reported.filter((f) => f.severity === "error").length;
5696
5750
  const warned = reported.some((f) => f.severity === "warning");
5697
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)}`;
5698
5754
  if (reported.length === 0) {
5699
5755
  if (quiet) return;
5700
- lines.push(`${c.green("\u2713")} ${page.file}${forgiven}`);
5756
+ lines.push(`${c.green("\u2713")} ${heading}${forgiven}`);
5701
5757
  } else {
5702
5758
  const mark = errors > 0 ? c.red("\u2717") : warned ? c.yellow("\u26A0") : c.dim("\u2139");
5703
- lines.push(`${mark} ${page.file}${forgiven}`);
5759
+ lines.push(`${mark} ${heading}${forgiven}`);
5704
5760
  }
5705
5761
  const isBaselined = new Set(baselined2);
5706
5762
  const placed = /* @__PURE__ */ new Set();
@@ -5732,7 +5788,7 @@ function renderCheckPretty(run3, opts) {
5732
5788
  label: labelOf(result),
5733
5789
  claim: claimColumn(result),
5734
5790
  source: `${sourceColumn(result, opts, c.dim)}${forgivenEnd}`,
5735
- 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)}`}` : "",
5736
5792
  under
5737
5793
  });
5738
5794
  }
@@ -6041,7 +6097,7 @@ var KeyError = class extends ToolError {
6041
6097
  };
6042
6098
 
6043
6099
  // src/key/commands/rotate.ts
6044
- import { existsSync as existsSync2 } from "node:fs";
6100
+ import { existsSync as existsSync3 } from "node:fs";
6045
6101
  import { readFile as readFile5 } from "node:fs/promises";
6046
6102
  import { isAbsolute as isAbsolute3, join as join2, relative as relative3, resolve as resolve7, sep as sep3 } from "node:path";
6047
6103
  import { parse as parseYaml } from "yaml";
@@ -6162,31 +6218,63 @@ async function planManifests(opts) {
6162
6218
  )
6163
6219
  })).filter((c) => c.externalMetadata.length > 0);
6164
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
+ }
6165
6232
  const index = await loadExternalMetadata(local, {
6166
6233
  configDir: opts.configDir,
6167
- base: opts.base
6234
+ base: opts.base,
6235
+ pages: [...pages]
6168
6236
  });
6169
6237
  if (index === null) return [];
6170
6238
  const plans = /* @__PURE__ */ new Map();
6171
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
+ };
6172
6243
  for (const c of local) {
6173
6244
  for (const m of c.externalMetadata) {
6174
6245
  const join4 = externalMetadataJoin(m);
6175
- const abs = isAbsolute3(m.file) ? m.file : resolve7(opts.configDir, m.file);
6176
- const file = reportedPath2(abs, opts.base);
6177
- located.set(`${c.name}\0${file}\0${join4}`, abs);
6178
- if (plans.has(abs)) continue;
6179
- plans.set(abs, {
6180
- manifest: { file, collection: c.name, rewritten: [], skipped: [], written: false },
6181
- path: abs,
6182
- text: await readFile5(abs, "utf8"),
6183
- changed: false
6184
- });
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
+ }
6185
6254
  }
6186
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
+ };
6187
6276
  for (const entry of index.entries) {
6188
- const abs = located.get(`${entry.collection}\0${entry.file}\0${entry.join}`);
6189
- const plan = abs === void 0 ? void 0 : plans.get(abs);
6277
+ const plan = await planFor(`${entry.collection}\0${entry.file}\0${entry.join}`);
6190
6278
  if (plan === void 0) continue;
6191
6279
  const data = {};
6192
6280
  for (const [key, supplied] of valuesOf(index, entry)) {
@@ -6295,7 +6383,9 @@ async function runKeyRotate(opts) {
6295
6383
  base,
6296
6384
  configSource: file.source,
6297
6385
  key: current,
6298
- 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))
6299
6389
  });
6300
6390
  const manifests = new ManifestSet();
6301
6391
  const planned = [];
@@ -6358,7 +6448,9 @@ async function runKeyRotate(opts) {
6358
6448
  configDir: file.dir,
6359
6449
  base,
6360
6450
  fromKey,
6361
- toKey
6451
+ toKey,
6452
+ pages: files.map((rel) => resolve7(base, rel)),
6453
+ whole: !narrowed
6362
6454
  });
6363
6455
  const reencrypted = planned.reduce((n, p) => n + p.page.rewritten.length, 0) + valueManifests.reduce((n, m) => n + m.manifest.rewritten.length, 0);
6364
6456
  const skipped = planned.reduce((n, p) => n + p.page.skipped.length, 0) + valueManifests.reduce((n, m) => n + m.manifest.skipped.length, 0);
@@ -6401,13 +6493,13 @@ async function runKeyRotate(opts) {
6401
6493
  keyWritten: outcome === "written" || outcome === "finished",
6402
6494
  outcome,
6403
6495
  ...keyFile === null ? {} : { configSource: keyFile.source },
6404
- baselineStale: wrote && existsSync2(baseline),
6496
+ baselineStale: wrote && existsSync3(baseline),
6405
6497
  exitCode: skipped > 0 ? 1 : 0
6406
6498
  };
6407
6499
  }
6408
6500
 
6409
6501
  // src/key/commands/set.ts
6410
- import { existsSync as existsSync3 } from "node:fs";
6502
+ import { existsSync as existsSync4 } from "node:fs";
6411
6503
  import { basename, dirname, join as join3, relative as relative4, resolve as resolve8 } from "node:path";
6412
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.";
6413
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.`;
@@ -6417,7 +6509,7 @@ function refuseBesideLegacy(path, cwd) {
6417
6509
  const name = basename(path);
6418
6510
  for (const legacy of LEGACY_CONFIG_NAMES) {
6419
6511
  const found = join3(dir, legacy);
6420
- if (!existsSync3(found)) continue;
6512
+ if (!existsSync4(found)) continue;
6421
6513
  const source = relative4(cwd, found).replace(/\\/g, "/");
6422
6514
  throw new KeyError(
6423
6515
  `${source} is a single-tool config; a ${name} beside it would hide it. Move its keys under meta: in ${name} first.`
@@ -6486,4 +6578,4 @@ export {
6486
6578
  runKeyRotate,
6487
6579
  runKeySet
6488
6580
  };
6489
- //# sourceMappingURL=chunk-4HKOQIJL.js.map
6581
+ //# sourceMappingURL=chunk-CZGAZGDN.js.map