jostraca 0.38.0 → 0.39.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.
Files changed (64) hide show
  1. package/dist/build/BuildContext.js +3 -1
  2. package/dist/build/BuildContext.js.map +1 -1
  3. package/dist/build/BuildMeta.js +17 -6
  4. package/dist/build/BuildMeta.js.map +1 -1
  5. package/dist/build/FileHandler.d.ts +7 -1
  6. package/dist/build/FileHandler.js +130 -71
  7. package/dist/build/FileHandler.js.map +1 -1
  8. package/dist/check.d.ts +1 -1
  9. package/dist/check.js +7 -4
  10. package/dist/check.js.map +1 -1
  11. package/dist/cmp/CopyFiles.d.ts +2 -1
  12. package/dist/cmp/CopyFiles.js +7 -3
  13. package/dist/cmp/CopyFiles.js.map +1 -1
  14. package/dist/cmp/Fragment.d.ts +2 -1
  15. package/dist/cmp/Fragment.js +75 -6
  16. package/dist/cmp/Fragment.js.map +1 -1
  17. package/dist/diff.js +34 -18
  18. package/dist/diff.js.map +1 -1
  19. package/dist/jostraca.d.ts +10 -10
  20. package/dist/jostraca.js +59 -32
  21. package/dist/jostraca.js.map +1 -1
  22. package/dist/op/CopyOp.js +15 -4
  23. package/dist/op/CopyOp.js.map +1 -1
  24. package/dist/op/FileOp.js +14 -2
  25. package/dist/op/FileOp.js.map +1 -1
  26. package/dist/op/FolderOp.js +3 -2
  27. package/dist/op/FolderOp.js.map +1 -1
  28. package/dist/op/FragmentOp.js +2 -0
  29. package/dist/op/FragmentOp.js.map +1 -1
  30. package/dist/op/InjectOp.js +18 -5
  31. package/dist/op/InjectOp.js.map +1 -1
  32. package/dist/op/ProjectOp.d.ts +1 -1
  33. package/dist/op/ProjectOp.js +22 -4
  34. package/dist/op/ProjectOp.js.map +1 -1
  35. package/dist/op/SlotOp.js +2 -0
  36. package/dist/op/SlotOp.js.map +1 -1
  37. package/dist/tree.d.ts +2 -1
  38. package/dist/tree.js +21 -1
  39. package/dist/tree.js.map +1 -1
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/dist/util/basic.js +101 -68
  42. package/dist/util/basic.js.map +1 -1
  43. package/dist/util/bytes.d.ts +13 -0
  44. package/dist/util/bytes.js +122 -0
  45. package/dist/util/bytes.js.map +1 -0
  46. package/package.json +1 -1
  47. package/src/build/BuildContext.ts +3 -1
  48. package/src/build/BuildMeta.ts +21 -7
  49. package/src/build/FileHandler.ts +152 -79
  50. package/src/check.ts +8 -4
  51. package/src/cmp/CopyFiles.ts +9 -2
  52. package/src/cmp/Fragment.ts +82 -6
  53. package/src/diff.ts +38 -19
  54. package/src/jostraca.ts +69 -37
  55. package/src/op/CopyOp.ts +17 -4
  56. package/src/op/FileOp.ts +18 -2
  57. package/src/op/FolderOp.ts +4 -3
  58. package/src/op/FragmentOp.ts +3 -0
  59. package/src/op/InjectOp.ts +21 -6
  60. package/src/op/ProjectOp.ts +25 -4
  61. package/src/op/SlotOp.ts +3 -0
  62. package/src/tree.ts +25 -2
  63. package/src/util/basic.ts +113 -71
  64. package/src/util/bytes.ts +158 -0
@@ -98,8 +98,10 @@ class BuildContext {
98
98
  }
99
99
  duplicateFolder() {
100
100
  if (null == this.dfolder) {
101
+ // From the handler's canonical folder, not the raw option, so the
102
+ // baselines sit under the same folder the files do.
101
103
  this.dfolder =
102
- node_path_1.default.normalize(node_path_1.default.join(this.folder, this.bmeta.next.foldername, 'generated'));
104
+ node_path_1.default.normalize(node_path_1.default.join(this.fh.folder, this.bmeta.next.foldername, 'generated'));
103
105
  }
104
106
  return this.dfolder;
105
107
  }
@@ -1 +1 @@
1
- {"version":3,"file":"BuildContext.js","sourceRoot":"","sources":["../../src/build/BuildContext.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BuildContext.js","sourceRoot":"","sources":["../../src/build/BuildContext.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.BuildMeta = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
+ const FileHandler_1 = require("./FileHandler");
8
9
  const basic_1 = require("../util/basic");
9
10
  // Log non-fatal weirdness.
10
11
  const dlog = (0, basic_1.getdlog)('jostraca', __filename);
@@ -46,7 +47,7 @@ class BuildMeta {
46
47
  // save over previous
47
48
  saveMetaData(this.fh, this.next);
48
49
  if (false === this.fh.control.version) {
49
- this.fh.saveFile(node_path_1.default.join(this.fh.folder, this.next.foldername, '.gitignore'), `
50
+ this.fh.saveFile((0, FileHandler_1.canonPath)(node_path_1.default.join(this.fh.folder, this.next.foldername, '.gitignore')), `
50
51
  ${this.next.filename}
51
52
  generated
52
53
  `);
@@ -58,13 +59,13 @@ exports.BuildMeta = BuildMeta;
58
59
  function loadMetaData(fh, bmeta) {
59
60
  // Full (folder-prefixed) path: the FileHandler FS methods use paths
60
61
  // directly and no longer re-join `this.folder`.
61
- const metapath = node_path_1.default.join(fh.folder, bmeta.foldername, bmeta.filename);
62
+ const metapath = (0, FileHandler_1.canonPath)(node_path_1.default.join(fh.folder, bmeta.foldername, bmeta.filename));
62
63
  if (fh.existsFile(metapath)) {
63
64
  try {
64
65
  const json = fh.loadJSON(metapath);
65
- bmeta.last = null == json?.last ? -1 : json.last;
66
- bmeta.hlast = null == json?.hlast ? -1 : json.hlast;
67
- bmeta.files = json?.files || {};
66
+ bmeta.last = isTime(json?.last) ? json.last : -1;
67
+ bmeta.hlast = isTime(json?.hlast) ? json.hlast : -1;
68
+ bmeta.files = isPlainObject(json?.files) ? json.files : {};
68
69
  }
69
70
  catch (err) {
70
71
  // A truncated or hand-edited meta log used to throw straight out of
@@ -82,10 +83,20 @@ function loadMetaData(fh, bmeta) {
82
83
  }
83
84
  return bmeta;
84
85
  }
86
+ // A previous `last` is used only when a Date can carry it. A string, a
87
+ // boolean or 1e20 in a hand-edited log reached the merge labels and threw
88
+ // "Invalid time value"; anything else is treated as absent, as an
89
+ // unreadable log is.
90
+ function isTime(v) {
91
+ return 'number' === typeof v && Number.isFinite(v) && Math.abs(v) <= 8.64e15;
92
+ }
93
+ function isPlainObject(v) {
94
+ return null != v && 'object' === typeof v && !Array.isArray(v);
95
+ }
85
96
  function saveMetaData(fh, bmeta) {
86
97
  // Full (folder-prefixed) path: the FileHandler FS methods use paths
87
98
  // directly and no longer re-join `this.folder`.
88
- const metapath = node_path_1.default.join(fh.folder, bmeta.foldername, bmeta.filename);
99
+ const metapath = (0, FileHandler_1.canonPath)(node_path_1.default.join(fh.folder, bmeta.foldername, bmeta.filename));
89
100
  fh.saveJSON(metapath, bmeta);
90
101
  }
91
102
  //# sourceMappingURL=BuildMeta.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"BuildMeta.js","sourceRoot":"","sources":["../../src/build/BuildMeta.ts"],"names":[],"mappings":";;;;;;AACA,0DAA4B;AAI5B,yCAAiD;AAGjD,2BAA2B;AAC3B,MAAM,IAAI,GAAG,IAAA,eAAO,EAAC,UAAU,EAAE,UAAU,CAAC,CAAA;AAmB5C,0DAA0D;AAC1D,MAAM,SAAS;IAEb,EAAE,CAAa;IAEf,IAAI,CAAe;IACnB,IAAI,CAAe;IAEnB,YACE,EAAe;QAEf,IAAI,CAAC,EAAE,GAAG,EAAE,CAAA;QAEZ,+DAA+D;QAC/D,IAAI,CAAC,IAAI,GAAG;YACV,UAAU,EAAE,WAAW;YACvB,QAAQ,EAAE,mBAAmB;YAC7B,IAAI,EAAE,CAAC,CAAC;YACR,KAAK,EAAE,CAAC,CAAC;YACT,KAAK,EAAE,EAAE;SACV,CAAA;QAED,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAE5C,4BAA4B;QAE5B,IAAI,CAAC,IAAI,GAAG;YACV,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU;YAChC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ;YAC5B,IAAI,EAAE,CAAC,CAAC;YACR,KAAK,EAAE,CAAC,CAAC;YACT,KAAK,EAAE,EAAE;SACV,CAAA;IAEH,CAAC;IAGD,IAAI;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAA;IACvB,CAAC;IAGD,GAAG,CAAC,IAAY,EAAE,IAAS;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,CAAA;QACrD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;IAC/B,CAAC;IAGD,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,CAAA;QAC9B,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,IAAA,gBAAQ,EAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAE1C,qBAAqB;QACrB,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAEhC,IAAI,KAAK,KAAK,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACtC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,mBAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE;EACpF,IAAI,CAAC,IAAI,CAAC,QAAQ;;CAEnB,CAAC,CAAA;QACE,CAAC;QAED,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;CACF;QAyCC,SAAS;AAtCX,SAAS,YAAY,CAAC,EAAe,EAAE,KAAoB;IACzD,oEAAoE;IACpE,gDAAgD;IAChD,MAAM,QAAQ,GAAG,mBAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;IACvE,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;YAClC,KAAK,CAAC,IAAI,GAAG,IAAI,IAAI,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;YAChD,KAAK,CAAC,KAAK,GAAG,IAAI,IAAI,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAA;YACnD,KAAK,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,EAAE,CAAA;QACjC,CAAC;QACD,OAAO,GAAQ,EAAE,CAAC;YAChB,oEAAoE;YACpE,mEAAmE;YACnE,kEAAkE;YAClE,gEAAgE;YAChE,iEAAiE;YACjE,qCAAqC;YACrC,IAAI,CAAC,MAAM,EAAE,oDAAoD;gBAC/D,QAAQ,GAAG,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,CAAA;YACnC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAA;YACf,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;YAChB,KAAK,CAAC,KAAK,GAAG,EAAE,CAAA;QAClB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAGD,SAAS,YAAY,CAAC,EAAe,EAAE,KAAoB;IACzD,oEAAoE;IACpE,gDAAgD;IAChD,MAAM,QAAQ,GAAG,mBAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;IACvE,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;AAC9B,CAAC"}
1
+ {"version":3,"file":"BuildMeta.js","sourceRoot":"","sources":["../../src/build/BuildMeta.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { BuildContext } from './BuildContext';
2
2
  import { FST, Audit } from '../types';
3
3
  declare function canonPath(path: string): string;
4
+ declare function canonFolder(folder: string): string;
4
5
  declare class FileHandler {
5
6
  when: number;
6
7
  fs: () => FST;
@@ -32,6 +33,7 @@ declare class FileHandler {
32
33
  };
33
34
  createdDirs: Set<string>;
34
35
  savedPaths: Set<string>;
36
+ filelogged: Record<string, Set<string>>;
35
37
  constructor(bctx: BuildContext, existing: {
36
38
  txt: any;
37
39
  bin: any;
@@ -44,10 +46,12 @@ declare class FileHandler {
44
46
  withinFolder(path: string): boolean;
45
47
  saveBinary(path: string, newContentSource: string | Buffer, whence?: string): void;
46
48
  save(path: string, newContentSource: string | Buffer, write?: boolean | string, whence?: string, mode?: number, isText?: boolean): void;
49
+ private decision;
47
50
  copy(frompath: string, topath: string, write?: boolean | string, whence?: string): void;
48
51
  merge(generated: string, baseline: string, existing: string, why: string[]): {
49
52
  content: string;
50
53
  conflict: boolean;
54
+ outcome: string;
51
55
  };
52
56
  diff(generated: string, existing: string): string;
53
57
  existsFile(path: string, whence?: string): boolean;
@@ -55,6 +59,8 @@ declare class FileHandler {
55
59
  loadJSON(path: string, opts?: any | string, whence?: string): any;
56
60
  saveJSON(path: string, json: any, opts?: any | string, whence?: string): any;
57
61
  loadFile(path: string, opts?: any | string, whence?: string): string | Buffer;
62
+ loadSource(path: string, opts?: any | string, whence?: string): string | Buffer;
63
+ private load;
58
64
  ensureFolder(path: string): void;
59
65
  private ensureDir;
60
66
  private chmodUnchanged;
@@ -65,4 +71,4 @@ declare class FileHandler {
65
71
  declare function annotatedPath(target: string, kind: string): string;
66
72
  declare function validName(name: any, kind: string, errmark: string): void;
67
73
  declare function validPath(path: string, maxdepth: number, errmark: string): void;
68
- export { annotatedPath, canonPath, validName, validPath, FileHandler };
74
+ export { annotatedPath, canonFolder, canonPath, validName, validPath, FileHandler };
@@ -38,6 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.FileHandler = void 0;
40
40
  exports.annotatedPath = annotatedPath;
41
+ exports.canonFolder = canonFolder;
41
42
  exports.canonPath = canonPath;
42
43
  exports.validName = validName;
43
44
  exports.validPath = validPath;
@@ -51,8 +52,9 @@ const CN = 'FileHandler:';
51
52
  function fwd(p) {
52
53
  return p.includes('\\') ? p.replace(/\\/g, '/') : p;
53
54
  }
54
- // THE ONE CANONICAL FORM OF AN OUTPUT PATH: separators collapsed, `.`
55
- // and `..` segments resolved, forward slashes.
55
+ // THE ONE CANONICAL FORM OF AN OUTPUT PATH: separators folded, then `.`
56
+ // and `..` segments resolved, forward slashes. Folded FIRST, so that a
57
+ // backslash `..` segment resolves as a slash one does, as in Go.
56
58
  //
57
59
  // Exported because `save` is not the only place that has to agree about
58
60
  // what "the same file" means. `FileOp.before` composes a path from the
@@ -63,7 +65,21 @@ function fwd(p) {
63
65
  // case the guard exists to refuse. Go has never had it: `fileBefore`
64
66
  // does `path.Clean(fwd(raw))` BEFORE recording anything.
65
67
  function canonPath(path) {
66
- return fwd(node_path_1.default.normalize(path));
68
+ return fwd(node_path_1.default.normalize(fwd(path)));
69
+ }
70
+ // The output folder, canonicalised once as canonPath does, with trailing
71
+ // separators stripped except from a filesystem root. A kept slash (`out/`)
72
+ // or an unresolved backslash `..` (`o\..\p`, when this normalised before
73
+ // folding) left the folder unlike every file path under it, so no path was
74
+ // inside it: meta keys kept the folder prefix, no baseline was written,
75
+ // and a later merge found no ancestor and overwrote the user's edits.
76
+ function canonFolder(folder) {
77
+ const norm = canonPath(folder);
78
+ if ('/' === norm || /^[A-Za-z]:\/$/.test(norm)) {
79
+ return norm;
80
+ }
81
+ const stripped = norm.replace(/\/+$/, '');
82
+ return '' === stripped ? '.' : stripped;
67
83
  }
68
84
  const JOSTRACA_PROTECT = 'JOSTRACA_PROTECT';
69
85
  // Audit breadcrumb per merge outcome, so the `why` trail says which fast
@@ -126,11 +142,12 @@ class FileHandler {
126
142
  files;
127
143
  createdDirs;
128
144
  savedPaths;
145
+ filelogged;
129
146
  constructor(bctx, existing, control) {
130
147
  this.fs = bctx.fs;
131
148
  this.now = bctx.now;
132
149
  this.when = bctx.when;
133
- this.folder = fwd(node_path_1.default.normalize(bctx.folder));
150
+ this.folder = canonFolder(bctx.folder);
134
151
  this.audit = bctx.audit;
135
152
  this.existing = existing;
136
153
  this.control = control;
@@ -146,6 +163,7 @@ class FileHandler {
146
163
  };
147
164
  this.createdDirs = new Set();
148
165
  this.savedPaths = new Set();
166
+ this.filelogged = {};
149
167
  // Yikes!
150
168
  this.duplicateFolder = bctx.duplicateFolder.bind(bctx);
151
169
  this.last = () => bctx.bmeta.prev.last;
@@ -210,7 +228,7 @@ class FileHandler {
210
228
  // baseline — `.jostraca/generated` joined to the relative path — nor-
211
229
  // malize to a location outside the baseline directory entirely, and
212
230
  // silently overwrite whatever was there.
213
- const norm = fwd(node_path_1.default.normalize(path));
231
+ const norm = canonPath(path);
214
232
  return '..' !== norm && !norm.startsWith('../');
215
233
  }
216
234
  if ('/' === this.folder) {
@@ -302,22 +320,17 @@ class FileHandler {
302
320
  // Tracks whether the generated content actually differs from what is
303
321
  // already on disk, so the write path can skip a no-op rewrite.
304
322
  let unchanged = false;
323
+ // Whether the save ends in a write or skip record.
324
+ let final = false;
305
325
  if (exists) {
306
326
  why.push('exists-0');
307
- // Load the existing bytes in the SAME SHAPE as the incoming content.
308
- // `loadFile` decodes utf8 by default, so a Copy-routed binary file --
309
- // whose content arrives here as a Buffer -- was compared as
310
- // `string === Buffer`, which is false whatever the bytes are. A target
311
- // already holding exactly those bytes was therefore "changed" on every
312
- // run: `bin.preserve` wrote a `.old` backup of a file about to be
313
- // rewritten identically, `bin.present` wrote a `.new` sidecar of the
314
- // same bytes, and the unchanged-file optimisation was defeated for
315
- // every binary. Issue #30. Go has always used bytes.Equal.
316
- //
317
- // Decoding cannot be fixed by converting one side afterwards: utf8
318
- // decoding is lossy for binary, so the string no longer carries the
319
- // bytes to compare.
320
- let currentContent = this.loadFile(path, isTextFile ? undefined : { encoding: null });
327
+ // The existing file is handled as BYTES, text included, as Go does.
328
+ // Decoding it as UTF-8 turned every invalid byte into U+FFFD, so a
329
+ // file the user saved in Latin-1 was judged "unchanged" against a
330
+ // generate holding U+FFFD, and diff and merge wrote the replacement
331
+ // characters back over the user's bytes. Every comparison below is
332
+ // on bytes; diff and merge run over a byte-transparent latin1 form.
333
+ const currentContent = this.loadFile(path, { encoding: null });
321
334
  const protect = 0 <= currentContent.indexOf(JOSTRACA_PROTECT);
322
335
  meta.protect = protect;
323
336
  unchanged = sameContent(currentContent, newContentSource);
@@ -327,7 +340,7 @@ class FileHandler {
327
340
  why.push('protect-0');
328
341
  write = false;
329
342
  }
330
- else if (!sameContent(currentContent, newContentSource)) {
343
+ else if (!unchanged) {
331
344
  why.push('content-0');
332
345
  let oldpath = annotatedPath(path, 'old');
333
346
  this.copyFile(path, oldpath, whence + 'preserve:');
@@ -336,8 +349,7 @@ class FileHandler {
336
349
  meta.action = 'preserve';
337
350
  whenify(meta, this.now());
338
351
  meta.actions.push(meta.action);
339
- this.audit.push([CN + FN + wstr + meta.action,
340
- { ...meta, why, action: meta.action, path }]);
352
+ this.decision(CN + FN + wstr, meta, why, path);
341
353
  }
342
354
  }
343
355
  if (existing.write && !protect) {
@@ -346,7 +358,7 @@ class FileHandler {
346
358
  }
347
359
  else if (existing.present) {
348
360
  why.push('present-0');
349
- if (!sameContent(currentContent, newContentSource)) {
361
+ if (!unchanged) {
350
362
  why.push('content-1');
351
363
  let newpath = annotatedPath(path, 'new');
352
364
  this.saveFile(newpath, newContentSource, { flush: true }, whence + 'present:');
@@ -354,8 +366,7 @@ class FileHandler {
354
366
  meta.action = 'present';
355
367
  whenify(meta, this.now());
356
368
  meta.actions.push(meta.action);
357
- this.audit.push([CN + FN + wstr + meta.action,
358
- { ...meta, why, action: meta.action, path }]);
369
+ this.decision(CN + FN + wstr, meta, why, path);
359
370
  }
360
371
  }
361
372
  if (!protect) {
@@ -363,13 +374,12 @@ class FileHandler {
363
374
  if (existing.diff) {
364
375
  why.push('diff-0');
365
376
  write = false;
366
- if (!sameContent(currentContent, newContentSource)) {
377
+ if (!unchanged) {
367
378
  why.push('content-2');
368
379
  meta.action = 'diff';
369
- const newContent = 'string' === typeof newContentSource ? newContentSource :
370
- newContentSource.toString('utf8');
371
- const diffContent = this.diff(newContent, currentContent.toString());
372
- this.saveFile(path, diffContent, { encoding: 'utf8', ...modeopts() }, whence + meta.action);
380
+ const newContent = latin1(newContentSource);
381
+ const diffContent = this.diff(newContent, latin1(currentContent));
382
+ this.saveFile(path, Buffer.from(diffContent, 'latin1'), modeopts(), whence + meta.action);
373
383
  // this.files.diffed.push(path)
374
384
  this.filelog('diffed', path);
375
385
  const conflict = newContent !== diffContent;
@@ -380,8 +390,7 @@ class FileHandler {
380
390
  whenify(meta, this.now());
381
391
  meta.actions.push(meta.action);
382
392
  meta.conflict = conflict;
383
- this.audit.push([CN + FN + wstr + meta.action,
384
- { ...meta, why, action: meta.action, path }]);
393
+ this.decision(CN + FN + wstr, meta, why, path);
385
394
  }
386
395
  else {
387
396
  // Equal content is still not a no-op when an explicit mode was
@@ -396,7 +405,7 @@ class FileHandler {
396
405
  }
397
406
  else if (existing.merge) {
398
407
  why.push('merge-0');
399
- if (!sameContent(currentContent, newContentSource)) {
408
+ if (!unchanged) {
400
409
  why.push('content-3');
401
410
  if (this.control.duplicate) {
402
411
  why.push('duplicate-0');
@@ -406,16 +415,27 @@ class FileHandler {
406
415
  why.push('dupexists-0');
407
416
  write = false;
408
417
  meta.action = 'merge';
409
- const newContent = 'string' === typeof newContentSource ? newContentSource :
410
- newContentSource.toString('utf8');
411
- const prevGenContent = this.loadFile(dpath, { encoding: 'utf8' });
412
- const mergeres = this.merge(newContent, // generated
413
- prevGenContent, // baseline (last generate)
414
- currentContent.toString(), // existing (on disk)
418
+ const prevGenContent = this.loadFile(dpath, { encoding: null });
419
+ const mergeres = this.merge(latin1(newContentSource), // generated
420
+ latin1(prevGenContent), // baseline (last generate)
421
+ latin1(currentContent), // existing (on disk)
415
422
  why);
416
423
  const diffcontent = mergeres.content;
417
- const conflict = mergeres.conflict;
418
- this.saveFile(path, diffcontent, { encoding: 'utf8', ...modeopts() }, whence + meta.action);
424
+ // A file still holding an earlier merge's markers is left
425
+ // byte-for-byte untouched, and reported conflicted: it
426
+ // still carries markers, and the documented guard on
427
+ // files.conflicted must not pass over it. Rewriting it
428
+ // bumped the mtime for nothing.
429
+ const unresolved = 'unresolved' === mergeres.outcome;
430
+ const conflict = mergeres.conflict || unresolved;
431
+ if (unresolved) {
432
+ if (this.chmodUnchanged(path, mode)) {
433
+ why.push('chmod-0');
434
+ }
435
+ }
436
+ else {
437
+ this.saveFile(path, Buffer.from(diffcontent, 'latin1'), modeopts(), whence + meta.action);
438
+ }
419
439
  // this.files.merged.push(path)
420
440
  this.filelog('merged', path);
421
441
  if (conflict) {
@@ -425,8 +445,7 @@ class FileHandler {
425
445
  whenify(meta, this.now());
426
446
  meta.actions.push(meta.action);
427
447
  meta.conflict = conflict;
428
- this.audit.push([CN + FN + wstr + meta.action,
429
- { ...meta, why, action: meta.action, path }]);
448
+ this.decision(CN + FN + wstr, meta, why, path);
430
449
  }
431
450
  }
432
451
  }
@@ -473,15 +492,14 @@ class FileHandler {
473
492
  }
474
493
  meta.actions.push(meta.action);
475
494
  whenify(meta, this.now());
476
- this.audit.push([CN + FN + wstr + meta.action,
477
- { ...meta, why, action: meta.action, path }]);
495
+ final = true;
478
496
  }
479
497
  else if (0 === meta.actions.length) {
480
498
  why.push('skip-0');
481
499
  meta.action = 'skip';
482
500
  meta.actions.push(meta.action);
483
- this.audit.push([CN + FN + wstr + meta.action,
484
- { ...meta, why, action: meta.action, path }]);
501
+ whenify(meta, this.now());
502
+ final = true;
485
503
  }
486
504
  if (this.control.duplicate) {
487
505
  why.push('duplicate-1');
@@ -493,14 +511,23 @@ class FileHandler {
493
511
  this.ensureDir(fwd(node_path_1.default.dirname(dpath)));
494
512
  this.writeFileAtomic(dpath, newContentSource, { flush: true });
495
513
  }
496
- if (null == meta.when) {
497
- whenify(meta, this.now());
498
- }
499
514
  }
500
515
  }
501
- // console.log('WHY', path, why)
516
+ // The write or skip record is the save's last word, so it carries the
517
+ // baseline breadcrumbs too.
518
+ if (final) {
519
+ this.decision(CN + FN + wstr, meta, why, path);
520
+ }
502
521
  this.addmeta(path, meta);
503
522
  }
523
+ // Push a decision record as a SNAPSHOT. The record used to share the live
524
+ // `why` and `meta.actions` arrays, so an early record (a preserve) later
525
+ // reported the whole save's actions and breadcrumbs.
526
+ decision(tag, meta, why, path) {
527
+ this.audit.push([tag + meta.action, {
528
+ ...meta, actions: [...meta.actions], why: [...why], action: meta.action, path,
529
+ }]);
530
+ }
504
531
  copy(frompath, topath, write, whence) {
505
532
  const wstr = null == whence ? '' : whence + ':';
506
533
  const FN = 'copy:';
@@ -533,7 +560,7 @@ class FileHandler {
533
560
  //
534
561
  // CopyOp's own copyFile reads a Buffer and go/build.go reads bytes;
535
562
  // this is now the same shape as both, with no order to get wrong.
536
- const raw = this.loadFile(frompath, { encoding: null }, whence);
563
+ const raw = this.loadSource(frompath, { encoding: null }, whence);
537
564
  // The SOURCE decides: a binary source stays governed by `existing.bin`
538
565
  // even when copied to a destination whose extension is not on the
539
566
  // list. Same rule as CopyOp's own copy paths and go/build.go's
@@ -562,7 +589,7 @@ class FileHandler {
562
589
  kind: 'merge',
563
590
  });
564
591
  why.push(MERGE_WHY[res.outcome]);
565
- return { content: res.content, conflict: res.conflict };
592
+ return { content: res.content, conflict: res.conflict, outcome: res.outcome };
566
593
  }
567
594
  // Annotated two-way view of the difference between the new generate and
568
595
  // what is on disk.
@@ -582,7 +609,7 @@ class FileHandler {
582
609
  // Paths are canonical (already folder-prefixed by the build phase, or
583
610
  // absolute); use them directly. Do NOT re-join `this.folder`, which would
584
611
  // double-prefix relative non-`.` output folders. Matches the Go port.
585
- const fullpath = fwd(node_path_1.default.normalize(path));
612
+ const fullpath = canonPath(path);
586
613
  try {
587
614
  const exists = fs.existsSync(fullpath);
588
615
  this.audit.push([CN + FN + wstr,
@@ -605,8 +632,8 @@ class FileHandler {
605
632
  validPath(frompath, this.maxdepth, CN + FN + 'from:' + wstr);
606
633
  validPath(topath, this.maxdepth, CN + FN + 'to:' + wstr);
607
634
  // Canonical paths: use directly, do not re-join `this.folder` (see existsFile).
608
- const fulltopath = fwd(node_path_1.default.normalize(topath));
609
- const fullfrompath = fwd(node_path_1.default.normalize(frompath));
635
+ const fulltopath = canonPath(topath);
636
+ const fullfrompath = canonPath(frompath);
610
637
  try {
611
638
  const existed = fs.existsSync(fulltopath);
612
639
  // Copy BYTES, always — never decode as UTF-8 first.
@@ -657,7 +684,7 @@ class FileHandler {
657
684
  const cstr = 'string' === typeof content ? content : content.toString(opts.encoding);
658
685
  const json = JSON.parse(cstr);
659
686
  this.audit.push([CN + FN + wstr,
660
- { path, when, size: content.length }]);
687
+ { path, when, size: byteLength(content) }]);
661
688
  return json;
662
689
  }
663
690
  catch (err) {
@@ -683,7 +710,7 @@ class FileHandler {
683
710
  const jstr = 'string' === typeof json ? json : JSON.stringify(json, null, 2);
684
711
  this.saveFile(path, jstr, opts, whence);
685
712
  this.audit.push([CN + FN + wstr,
686
- { path, when, size: jstr.length }]);
713
+ { path, when, size: byteLength(jstr) }]);
687
714
  return jstr;
688
715
  }
689
716
  catch (err) {
@@ -694,6 +721,18 @@ class FileHandler {
694
721
  }
695
722
  }
696
723
  loadFile(path, opts, whence) {
724
+ return this.load(path, canonPath(path), opts, whence);
725
+ }
726
+ // loadFile for the SOURCE of a copy, read at the path as given. A source
727
+ // keeps its platform meaning, as every other source read does (CopyOp
728
+ // stats and reads its entries raw): on POSIX a backslash is an ordinary
729
+ // name character. Folding it here, as an output path is folded, sent a
730
+ // binary tree entry named `b\in.png` to `b/in.png`, and the copy failed
731
+ // with ENOENT while the text entry beside it was copied.
732
+ loadSource(path, opts, whence) {
733
+ return this.load(path, path, opts, whence);
734
+ }
735
+ load(path, fullpath, opts, whence) {
697
736
  const when = this.now();
698
737
  const wstr = null == whence ? '' : whence + ':';
699
738
  const fs = this.fs();
@@ -708,11 +747,10 @@ class FileHandler {
708
747
  opts.encoding = undefined === opts.encoding ? 'utf8' : opts.encoding;
709
748
  validPath(path, this.maxdepth, CN + FN + wstr);
710
749
  try {
711
- // Canonical path: use directly, do not re-join `this.folder` (see existsFile).
712
- const fullpath = fwd(node_path_1.default.normalize(path));
750
+ // Used directly, never re-joined to `this.folder` (see existsFile).
713
751
  const content = fs.readFileSync(fullpath, opts);
714
752
  this.audit.push([CN + FN + wstr,
715
- { path, when, size: content.length }]);
753
+ { path, when, size: byteLength(content) }]);
716
754
  return content;
717
755
  }
718
756
  catch (err) {
@@ -816,9 +854,10 @@ class FileHandler {
816
854
  // is the R12 case, and `created` must stay false so the cleanup
817
855
  // does not delete somebody else's file.
818
856
  //
819
- // Any OTHER error (ENOSPC, EIO) happened AFTER the create
820
- // succeeded, so a partial temp file is on disk and is ours to
821
- // remove. Without this it survived the failed build.
857
+ // Any OTHER error may have come AFTER the create succeeded
858
+ // (ENOSPC, EIO), leaving a partial temp file that is ours to
859
+ // remove. Without this it survived the failed build. If the
860
+ // create itself failed (EACCES), the cleanup finds nothing.
822
861
  if ('EEXIST' !== err?.code) {
823
862
  created = true;
824
863
  }
@@ -859,7 +898,10 @@ class FileHandler {
859
898
  }
860
899
  }
861
900
  catch (cleanuperr) {
862
- dlog('writeFileAtomic', 'temp cleanup failed: ' + tmppath);
901
+ // Already gone is not a failed cleanup: nothing was left behind.
902
+ if ('ENOENT' !== cleanuperr?.code) {
903
+ dlog('writeFileAtomic', 'temp cleanup failed: ' + tmppath);
904
+ }
863
905
  }
864
906
  throw err;
865
907
  }
@@ -884,7 +926,7 @@ class FileHandler {
884
926
  }
885
927
  try {
886
928
  // Canonical path: use directly, do not re-join `this.folder` (see existsFile).
887
- path = fwd(node_path_1.default.normalize(path));
929
+ path = canonPath(path);
888
930
  const fullpath = path;
889
931
  const parentfolder = fwd(node_path_1.default.dirname(fullpath));
890
932
  const existed = fs.existsSync(fullpath);
@@ -893,25 +935,29 @@ class FileHandler {
893
935
  this.writeFileAtomic(fullpath, content, opts, opts.mode);
894
936
  }
895
937
  this.audit.push([CN + FN + wstr,
896
- { path, when, existed, size: content.length }]);
938
+ { path, when, existed, size: byteLength(content) }]);
897
939
  }
898
940
  catch (err) {
899
941
  this.audit.push(['ERROR:' + CN + FN + wstr,
900
- { path, when, size: content.length, err }]);
942
+ { path, when, size: byteLength(content), err }]);
901
943
  err.message = CN + FN + wstr + ' path=' + path + ':' + err.message;
902
944
  throw err;
903
945
  }
904
946
  }
947
+ // A path appears at most once per kind, at its first position. Only the
948
+ // LAST entry used to be checked, so File t, File u, Inject t listed t
949
+ // twice and the list depended on sibling order.
905
950
  filelog(kind, path) {
906
951
  path = fwd(path);
907
952
  let files = this.files;
908
953
  if (files[kind]) {
909
- const kindlog = files[kind];
910
- if (path === kindlog[kindlog.length - 1]) {
954
+ const seen = this.filelogged[kind] = this.filelogged[kind] || new Set();
955
+ if (seen.has(path)) {
911
956
  dlog('filelog', kind, 'duplicate: ' + path);
912
957
  }
913
958
  else {
914
- kindlog.push(path);
959
+ seen.add(path);
960
+ files[kind].push(path);
915
961
  }
916
962
  }
917
963
  else {
@@ -920,6 +966,19 @@ class FileHandler {
920
966
  }
921
967
  }
922
968
  exports.FileHandler = FileHandler;
969
+ // The byte-transparent string form of content: one char per byte, so the
970
+ // diff engine's line splitting and equality are byte-exact and a round trip
971
+ // through Buffer.from(s, 'latin1') restores every byte. For valid UTF-8 the
972
+ // engine's output is byte-identical to running it on the decoded text.
973
+ function latin1(content) {
974
+ return (Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8'))
975
+ .toString('latin1');
976
+ }
977
+ // Audit sizes are byte lengths, whether the content is a string or a
978
+ // Buffer; `length` counted UTF-16 units for a string.
979
+ function byteLength(content) {
980
+ return 'string' === typeof content ? Buffer.byteLength(content, 'utf8') : content.length;
981
+ }
923
982
  function whenify(meta, now) {
924
983
  meta.when = now;
925
984
  meta.hwhen = (0, basic_1.humanify)(now);