@hawkeyexl/manni 2.8.1 → 2.8.2

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.
@@ -55,6 +55,7 @@ import {
55
55
  pinOfLines,
56
56
  readConfigFile,
57
57
  readFamilyConfigFile,
58
+ readManifestValue,
58
59
  removeManifestKey,
59
60
  resolveBaselineRequest,
60
61
  resolveEncryptionKey,
@@ -71,7 +72,7 @@ import {
71
72
  withValueAt,
72
73
  writeEncryptionKey,
73
74
  writeFileAtomic
74
- } from "./chunk-RLHN765L.js";
75
+ } from "./chunk-KEW557TN.js";
75
76
 
76
77
  // src/meta/core/reencrypt.ts
77
78
  import { extname } from "node:path";
@@ -151,15 +152,15 @@ function reencryptWith(extractor, page, opts) {
151
152
  }
152
153
  };
153
154
  const data = read(page.content);
154
- const apply2 = extractor.apply;
155
- const plan = reencryptData(data, { ...opts, writable: apply2 !== void 0 });
155
+ const apply3 = extractor.apply;
156
+ const plan = reencryptData(data, { ...opts, writable: apply3 !== void 0 });
156
157
  const { rewritten, skipped } = plan;
157
- if (rewritten.length === 0 || apply2 === void 0) {
158
+ if (rewritten.length === 0 || apply3 === void 0) {
158
159
  return { content: page.content, rewritten: [], skipped };
159
160
  }
160
161
  const patch = {};
161
162
  for (const top of plan.keys) patch[top] = plan.data[top];
162
- const content = apply2(page.content, patch, { filePath: page.file });
163
+ const content = apply3(page.content, patch, { filePath: page.file });
163
164
  const reread = tokensIn(read(content));
164
165
  for (const r of rewritten) {
165
166
  if (!reread.some((f) => f.pointer === r.pointer && f.token === r.to)) {
@@ -2662,6 +2663,13 @@ function unifiedDiff(label, before, after) {
2662
2663
  }
2663
2664
 
2664
2665
  // src/cite/core/manifest.ts
2666
+ var COMMIT_ATTEMPTS = 5;
2667
+ function conflictRefusal(file) {
2668
+ return `${file} changed under the command while it was being written. Nothing was written to it. Re-run the command.`;
2669
+ }
2670
+ function encodeValue(value) {
2671
+ return value === void 0 ? "absent" : JSON.stringify(value);
2672
+ }
2665
2673
  var ManifestSet = class {
2666
2674
  held = /* @__PURE__ */ new Map();
2667
2675
  /** Read a manifest, or hand back the copy this run has already rewritten. */
@@ -2675,7 +2683,14 @@ var ManifestSet = class {
2675
2683
  const reason = error instanceof Error ? error.message : String(error);
2676
2684
  throw new CiteError(`Manifest ${manifest.file} could not be read: ${reason}`);
2677
2685
  }
2678
- const fresh = { path: manifest.path, file: manifest.file, before, text: before };
2686
+ const fresh = {
2687
+ path: manifest.path,
2688
+ file: manifest.file,
2689
+ before,
2690
+ text: before,
2691
+ ops: [],
2692
+ bases: /* @__PURE__ */ new Map()
2693
+ };
2679
2694
  this.held.set(manifest.path, fresh);
2680
2695
  return fresh;
2681
2696
  }
@@ -2686,16 +2701,36 @@ var ManifestSet = class {
2686
2701
  */
2687
2702
  async write(manifest, entry, citations, index) {
2688
2703
  const held = await this.hold(manifest);
2704
+ const op = {
2705
+ kind: "splice",
2706
+ entry,
2707
+ join: manifest.join,
2708
+ file: manifest.file,
2709
+ value: [...citations]
2710
+ };
2711
+ rebaseable(held, op);
2689
2712
  const spliced = splice(held.text, {
2690
2713
  entry,
2691
2714
  key: CITATIONS_KEY2,
2692
- value: [...citations],
2715
+ value: op.value,
2693
2716
  join: manifest.join,
2694
2717
  file: manifest.file
2695
2718
  });
2696
2719
  held.text = spliced.text;
2720
+ held.ops.push(op);
2697
2721
  return { file: manifest.file, line: itemLine(spliced.text, entry, index, manifest.join) ?? spliced.line };
2698
2722
  }
2723
+ /**
2724
+ * Where one item of an entry's `citations` sits in the manifest as this run
2725
+ * will write it. `add` names a line in its report, and `commit` may have
2726
+ * replayed the entry onto bytes that moved it, so the line is read back from
2727
+ * the settled text rather than kept from the splice.
2728
+ */
2729
+ lineOf(manifest, entry, index) {
2730
+ const held = this.held.get(manifest.path);
2731
+ if (held === void 0) return void 0;
2732
+ return itemLine(held.text, entry, index, manifest.join);
2733
+ }
2699
2734
  /**
2700
2735
  * Take a page's `citations` key out of the manifest, for the removal that
2701
2736
  * leaves it with no entries. The entry itself goes when that key was all it
@@ -2703,23 +2738,114 @@ var ManifestSet = class {
2703
2738
  */
2704
2739
  async remove(manifest, entry) {
2705
2740
  const held = await this.hold(manifest);
2706
- try {
2707
- held.text = removeManifestKey(held.text, {
2708
- entry,
2709
- key: CITATIONS_KEY2,
2710
- join: manifest.join,
2711
- file: manifest.file
2712
- }).text;
2713
- } catch (error) {
2714
- if (error instanceof DocmetaError) throw new CiteError(error.message);
2715
- throw error;
2716
- }
2741
+ const op = { kind: "remove", entry, join: manifest.join, file: manifest.file };
2742
+ rebaseable(held, op);
2743
+ held.text = apply(held.text, op);
2744
+ held.ops.push(op);
2717
2745
  }
2718
2746
  /** The manifests this run rewrote, with the diff of each. */
2719
2747
  changed() {
2720
2748
  return [...this.held.values()].filter((m) => m.text !== m.before).map((m) => ({ ...m, diff: unifiedDiff(m.file, m.before, m.text) }));
2721
2749
  }
2750
+ /**
2751
+ * Write every manifest this run rewrote, and report what landed.
2752
+ *
2753
+ * The write is the last thing a run does, so this is the one place that
2754
+ * knows a manifest is about to be replaced by bytes derived from a read
2755
+ * that may be seconds old.
2756
+ */
2757
+ async commit(io = {}) {
2758
+ const write = io.write ?? writeFileAtomic;
2759
+ const read = io.read ?? ((at2) => readFile2(at2, "utf8"));
2760
+ const landed = [];
2761
+ for (const held of this.held.values()) {
2762
+ if (held.text === held.before) continue;
2763
+ try {
2764
+ await settle(held, write, read);
2765
+ } catch (error) {
2766
+ if (io.onError !== void 0) await io.onError(changeOf(held), error);
2767
+ throw error;
2768
+ }
2769
+ const change = changeOf(held);
2770
+ landed.push(change);
2771
+ io.after?.(change);
2772
+ }
2773
+ return landed;
2774
+ }
2722
2775
  };
2776
+ function changeOf(held) {
2777
+ return {
2778
+ path: held.path,
2779
+ file: held.file,
2780
+ before: held.before,
2781
+ text: held.text,
2782
+ diff: unifiedDiff(held.file, held.before, held.text)
2783
+ };
2784
+ }
2785
+ function rebaseable(held, op) {
2786
+ const at2 = op.join === "path" ? posix.normalize(op.entry) : op.entry;
2787
+ if (held.bases.has(at2)) return;
2788
+ held.bases.set(at2, {
2789
+ entry: op.entry,
2790
+ join: op.join,
2791
+ value: encodeValue(
2792
+ readManifestValue(held.before, { entry: op.entry, key: CITATIONS_KEY2, join: op.join })
2793
+ )
2794
+ });
2795
+ }
2796
+ function apply(text, op) {
2797
+ if (op.kind === "splice") {
2798
+ return splice(text, {
2799
+ entry: op.entry,
2800
+ key: CITATIONS_KEY2,
2801
+ value: op.value,
2802
+ join: op.join,
2803
+ file: op.file
2804
+ }).text;
2805
+ }
2806
+ try {
2807
+ return removeManifestKey(text, {
2808
+ entry: op.entry,
2809
+ key: CITATIONS_KEY2,
2810
+ join: op.join,
2811
+ file: op.file
2812
+ }).text;
2813
+ } catch (error) {
2814
+ if (error instanceof DocmetaError) throw new CiteError(error.message);
2815
+ throw error;
2816
+ }
2817
+ }
2818
+ function rebase(held, current) {
2819
+ for (const base of held.bases.values()) {
2820
+ const now = encodeValue(
2821
+ readManifestValue(current, { entry: base.entry, key: CITATIONS_KEY2, join: base.join })
2822
+ );
2823
+ if (now !== base.value) return void 0;
2824
+ }
2825
+ let text = current;
2826
+ for (const op of held.ops) text = apply(text, op);
2827
+ return text;
2828
+ }
2829
+ async function settle(held, write, read) {
2830
+ for (let attempt = 1; attempt <= COMMIT_ATTEMPTS; attempt++) {
2831
+ let current;
2832
+ try {
2833
+ current = await read(held.path);
2834
+ } catch (error) {
2835
+ const reason = error instanceof Error ? error.message : String(error);
2836
+ throw new CiteError(`Manifest ${held.file} could not be read: ${reason}`);
2837
+ }
2838
+ if (current === held.before) {
2839
+ await write(held.path, held.text);
2840
+ return;
2841
+ }
2842
+ const replayed = rebase(held, current);
2843
+ if (replayed === void 0) throw new CiteError(conflictRefusal(held.file));
2844
+ held.before = current;
2845
+ held.text = replayed;
2846
+ }
2847
+ throw new CiteError(conflictRefusal(held.file));
2848
+ }
2723
2849
  function splice(text, options) {
2724
2850
  try {
2725
2851
  return spliceManifestValue(text, options);
@@ -3846,8 +3972,8 @@ async function runAdd(opts) {
3846
3972
  if (unit === void 0 || pin === void 0) {
3847
3973
  throw new CiteError(`${at2} has no paragraph or block for a marker to anchor.`);
3848
3974
  }
3849
- for (const { index, lines: moved } of shifted.frontmatter) {
3850
- body = spliceEntryField(body, format, index, ["claim", "lines"], moved);
3975
+ for (const { index: index2, lines: moved } of shifted.frontmatter) {
3976
+ body = spliceEntryField(body, format, index2, ["claim", "lines"], moved);
3851
3977
  }
3852
3978
  claimSpan = unit;
3853
3979
  claim = { integrity: pin };
@@ -3903,18 +4029,20 @@ async function runAdd(opts) {
3903
4029
  }
3904
4030
  const manifests = new ManifestSet();
3905
4031
  let placed;
4032
+ let index = 0;
3906
4033
  if (owner !== void 0) {
3907
4034
  if (sidecar?.entry === void 0) {
3908
4035
  throw new CiteError(
3909
4036
  `${label} carries no ${owner.join}: value, so its citations cannot be keyed in ${owner.file}.`
3910
4037
  );
3911
4038
  }
3912
- const existing = (sidecar.citations ?? []).map((input, index) => {
3913
- const moved = shifted.manifest.get(index);
4039
+ const existing = (sidecar.citations ?? []).map((input, index2) => {
4040
+ const moved = shifted.manifest.get(index2);
3914
4041
  return moved === void 0 ? input.entry : withClaimLines(input.entry, moved);
3915
4042
  });
3916
4043
  const list = [...existing, entryObject(citation)];
3917
- const at3 = await manifests.write(owner, sidecar.entry, list, list.length - 1);
4044
+ index = list.length - 1;
4045
+ const at3 = await manifests.write(owner, sidecar.entry, list, index);
3918
4046
  const [changed] = manifests.changed();
3919
4047
  placed = {
3920
4048
  file: at3.file,
@@ -3943,7 +4071,14 @@ async function runAdd(opts) {
3943
4071
  result.written = path !== void 0 && opts.dryRun !== true && after !== content;
3944
4072
  if (result.written && path !== void 0) await writeFileAtomic(path, after);
3945
4073
  if (result.manifest !== void 0 && opts.dryRun !== true) {
3946
- for (const changed of manifests.changed()) await writeFileAtomic(changed.path, changed.text);
4074
+ const [settled] = await manifests.commit();
4075
+ if (owner !== void 0 && sidecar?.entry !== void 0) {
4076
+ result.manifest.line = manifests.lineOf(owner, sidecar.entry, index) ?? result.manifest.line;
4077
+ }
4078
+ if (settled !== void 0) {
4079
+ result.manifest.content = settled.text;
4080
+ result.manifest.diff = settled.diff;
4081
+ }
3947
4082
  result.manifest.written = true;
3948
4083
  }
3949
4084
  if (elsewhere.length > 0) {
@@ -4114,10 +4249,11 @@ async function runRemove(opts) {
4114
4249
  item.page.written = write;
4115
4250
  }
4116
4251
  const rewritten = [];
4117
- for (const changed of manifests.changed()) {
4118
- if (write) await writeFileAtomic(changed.path, changed.text);
4252
+ const record = (changed) => {
4119
4253
  rewritten.push({ file: changed.file, diff: changed.diff, written: write });
4120
- }
4254
+ };
4255
+ if (write) await manifests.commit({ after: record });
4256
+ else for (const changed of manifests.changed()) record(changed);
4121
4257
  const pages = planned.map((item) => item.page);
4122
4258
  return {
4123
4259
  pages,
@@ -4171,7 +4307,7 @@ function settles(plan) {
4171
4307
  return "";
4172
4308
  }
4173
4309
  }
4174
- function apply(content, format, plan) {
4310
+ function apply2(content, format, plan) {
4175
4311
  const index = plan.result.origin.index;
4176
4312
  switch (plan.kind) {
4177
4313
  case "claim-moved":
@@ -4825,7 +4961,7 @@ async function runUpdate(opts) {
4825
4961
  if (!applyToEntry(held, plan)) continue;
4826
4962
  manifestDirty = true;
4827
4963
  } else {
4828
- after = apply(after, format, plan);
4964
+ after = apply2(after, format, plan);
4829
4965
  }
4830
4966
  rewritten2.push(rewriteOf(plan));
4831
4967
  settled.add(`${String(result.origin.index)}\0${settles(plan)}`);
@@ -4879,20 +5015,25 @@ async function runUpdate(opts) {
4879
5015
  reports.push(await updateOne(file, await readTarget(run3, file), resolve6(run3.base, file)));
4880
5016
  }
4881
5017
  assertNoOrphanJoins(prepared, hits);
4882
- const changedManifests = manifests.changed();
5018
+ const write = opts.dryRun !== true;
4883
5019
  const rewrittenManifests = [];
4884
- for (const changed of changedManifests) {
4885
- const write = opts.dryRun !== true;
4886
- if (write) {
4887
- try {
4888
- await writeFileAtomic(changed.path, changed.text);
4889
- } catch (error) {
5020
+ const record = (changed) => {
5021
+ rewrittenManifests.push({ file: changed.file, diff: changed.diff, written: write });
5022
+ };
5023
+ if (write) {
5024
+ await manifests.commit({
5025
+ // A manifest joins the run's written files the moment it lands, so a
5026
+ // later manifest's failure puts this one back with the pages. Without
5027
+ // it the tree would keep pins no page content matches.
5028
+ after: (changed) => {
5029
+ writes.record(changed.path, changed.file, changed.before);
5030
+ record(changed);
5031
+ },
5032
+ onError: async (changed, error) => {
4890
5033
  throw await writes.restore(changed.file, error);
4891
5034
  }
4892
- writes.record(changed.path, changed.file, changed.before);
4893
- }
4894
- rewrittenManifests.push({ file: changed.file, diff: changed.diff, written: write });
4895
- }
5035
+ });
5036
+ } else for (const changed of manifests.changed()) record(changed);
4896
5037
  const reminted = pages.some(
4897
5038
  (page) => page.rewritten.some((r) => r.reason === "accepted" && r.end === "source")
4898
5039
  );
@@ -5973,4 +6114,4 @@ export {
5973
6114
  runKeyRotate,
5974
6115
  runKeySet
5975
6116
  };
5976
- //# sourceMappingURL=chunk-FWB6LGBB.js.map
6117
+ //# sourceMappingURL=chunk-WCNU53MJ.js.map