@ricsam/r5d-worker 0.0.97 → 0.0.100

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.
@@ -151,14 +151,55 @@ function addParentDirectories(entries, root) {
151
151
  }
152
152
  }
153
153
  }
154
- function inspectWorkingTree(root, mode) {
154
+ function gitlinkPaths(root) {
155
+ const result = Bun.spawnSync(
156
+ [
157
+ "git",
158
+ "-c",
159
+ "submodule.recurse=false",
160
+ "-c",
161
+ "fetch.recurseSubmodules=false",
162
+ "-c",
163
+ "push.recurseSubmodules=false",
164
+ "ls-files",
165
+ "-z",
166
+ "--stage",
167
+ "--",
168
+ "."
169
+ ],
170
+ {
171
+ cwd: root,
172
+ stdout: "pipe",
173
+ stderr: "pipe",
174
+ env: workerGitProcessEnvironment()
175
+ }
176
+ );
177
+ if (result.exitCode !== 0) {
178
+ throw new Error(`Inspect Git index gitlinks: ${result.stderr.toString().trim() || `git exited ${result.exitCode}`}`);
179
+ }
180
+ const gitlinks = /* @__PURE__ */ new Set();
181
+ for (const record of result.stdout.toString().split("\0")) {
182
+ const match = /^160000 [0-9a-f]+ [0-3]\t(.+)$/su.exec(record);
183
+ if (!match) continue;
184
+ const normalized = normalizeRelativePath(match[1]);
185
+ if (normalized) gitlinks.add(normalized);
186
+ }
187
+ return gitlinks;
188
+ }
189
+ function directoryIsEmpty(root, relativePath) {
190
+ const inspected = inspectRelativePathWithoutFollowingAncestors(root, relativePath);
191
+ return inspected.kind === "entry" && inspected.stat.isDirectory() && fs.readdirSync(inspected.absolutePath).length === 0;
192
+ }
193
+ function inspectWorkingTree(root, mode, options = {}) {
155
194
  const entries = /* @__PURE__ */ new Map();
156
195
  root = path.resolve(root);
157
196
  if (!inspectDirectoryRoot(root, { allowMissing: true, label: "Working-tree root" })) return entries;
158
197
  if (mode === "all") {
159
198
  for (const child of fs.readdirSync(root).sort()) addEntry(entries, root, child, true);
160
199
  } else {
200
+ const omittedGitlinks = options.omitUnpopulatedGitlinks ? gitlinkPaths(root) : null;
161
201
  for (const relativePath of gitEligibleRoots(root)) {
202
+ if (omittedGitlinks?.has(relativePath) && directoryIsEmpty(root, relativePath)) continue;
162
203
  addEntry(entries, root, relativePath, true);
163
204
  }
164
205
  }
@@ -332,7 +373,9 @@ function mirrorWorkingTree(input) {
332
373
  }
333
374
  inspectDirectoryRoot(targetRoot, { allowMissing: false, label: "Working-tree target root" });
334
375
  const desired = inspectWorkingTree(sourceRoot, input.sourceMode);
335
- const existing = inspectWorkingTree(targetRoot, input.deletionMode === "git" ? "git" : "all");
376
+ const existing = inspectWorkingTree(targetRoot, input.deletionMode === "git" ? "git" : "all", {
377
+ omitUnpopulatedGitlinks: input.deletionMode === "git"
378
+ });
336
379
  for (const relativePath of [...existing.keys()].sort((left, right) => right.split("/").length - left.split("/").length)) {
337
380
  if (!desired.has(relativePath)) removeEntry(targetRoot, relativePath);
338
381
  }
@@ -343,8 +386,212 @@ function mirrorWorkingTree(input) {
343
386
  }
344
387
  return { paths: [...desired.keys()].filter((relativePath) => desired.get(relativePath)?.kind !== "directory").sort() };
345
388
  }
389
+ function normalizeWorkingTreeRelativePath(value) {
390
+ return normalizeRelativePath(value);
391
+ }
392
+ const MIRROR_TEMPORARY_FILE_PATTERN = /^\.r5d-working-tree-mirror-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp$/u;
393
+ function relativeDepth(relativePath) {
394
+ return relativePath.split("/").length;
395
+ }
396
+ function shallowestFirst(left, right) {
397
+ return relativeDepth(left) - relativeDepth(right) || left.localeCompare(right);
398
+ }
399
+ function deepestFirst(left, right) {
400
+ return relativeDepth(right) - relativeDepth(left) || left.localeCompare(right);
401
+ }
402
+ function hasAncestorIn(relativePath, ancestors) {
403
+ let parent = path.posix.dirname(relativePath);
404
+ while (parent !== ".") {
405
+ if (ancestors.has(parent)) return true;
406
+ parent = path.posix.dirname(parent);
407
+ }
408
+ return false;
409
+ }
410
+ function treeEntryFromStat(stat, absolutePath) {
411
+ if (stat.isSymbolicLink()) return { kind: "symlink", mode: stat.mode, target: fs.readlinkSync(absolutePath) };
412
+ if (stat.isFile()) return { kind: "file", mode: stat.mode, size: stat.size };
413
+ if (stat.isDirectory()) return { kind: "directory", mode: stat.mode };
414
+ return null;
415
+ }
416
+ function fsyncPath(targetPath, flags) {
417
+ const descriptor = fs.openSync(targetPath, flags);
418
+ try {
419
+ fs.fsyncSync(descriptor);
420
+ } finally {
421
+ fs.closeSync(descriptor);
422
+ }
423
+ }
424
+ function planWorkingTreeMirror(input) {
425
+ const sourceRoot = path.resolve(input.sourceRoot);
426
+ const targetRoot = path.resolve(input.targetRoot);
427
+ const desired = inspectWorkingTree(sourceRoot, input.sourceMode);
428
+ const existing = inspectWorkingTree(targetRoot, input.deletionMode === "git" ? "git" : "all", {
429
+ omitUnpopulatedGitlinks: input.deletionMode === "git"
430
+ });
431
+ const replaced = /* @__PURE__ */ new Map();
432
+ const absent = [];
433
+ for (const [relativePath, entry] of existing) {
434
+ if (!desired.has(relativePath)) replaced.set(relativePath, entry);
435
+ }
436
+ for (const [relativePath, entry] of desired) {
437
+ const target = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
438
+ if (target.kind !== "entry") {
439
+ absent.push(relativePath);
440
+ continue;
441
+ }
442
+ const { absolutePath, stat } = target;
443
+ if (entry.kind === "directory") {
444
+ if (stat.isDirectory()) continue;
445
+ } else if (entry.kind === "symlink") {
446
+ if (stat.isSymbolicLink() && fs.readlinkSync(absolutePath) === entry.target) continue;
447
+ } else if (stat.isFile() && filesEqual(path.join(sourceRoot, ...relativePath.split("/")), absolutePath, entry)) {
448
+ continue;
449
+ }
450
+ const current = treeEntryFromStat(stat, absolutePath);
451
+ if (current) replaced.set(relativePath, current);
452
+ else absent.push(relativePath);
453
+ }
454
+ const replacedDirectories = new Set(
455
+ [...replaced].filter(([, entry]) => entry.kind === "directory").map(([relativePath]) => relativePath)
456
+ );
457
+ for (const relativePath of [...replaced.keys()]) {
458
+ if (hasAncestorIn(relativePath, replacedDirectories)) replaced.delete(relativePath);
459
+ }
460
+ return {
461
+ desired,
462
+ replaced,
463
+ absent: absent.filter((relativePath) => !hasAncestorIn(relativePath, replacedDirectories)).sort(shallowestFirst)
464
+ };
465
+ }
466
+ function captureWorkingTreeMirrorPlan(input) {
467
+ const targetRoot = path.resolve(input.targetRoot);
468
+ const snapshotRoot = path.resolve(input.snapshotRoot);
469
+ const durability = input.durability ?? "per_entry";
470
+ inspectDirectoryRoot(targetRoot, { allowMissing: false, label: "Working-tree target root" });
471
+ if (!inspectDirectoryRoot(snapshotRoot, { allowMissing: true, label: "Working-tree snapshot root" })) {
472
+ fs.mkdirSync(snapshotRoot, { recursive: true });
473
+ }
474
+ const trees = [];
475
+ for (const [relativePath, entry] of [...input.plan.replaced.entries()].sort(([left], [right]) => shallowestFirst(left, right))) {
476
+ if (entry.kind !== "directory") {
477
+ copyEntry(targetRoot, snapshotRoot, relativePath, entry, durability);
478
+ continue;
479
+ }
480
+ const source = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
481
+ if (source.kind !== "entry" || !source.stat.isDirectory()) {
482
+ throw new Error(`Working-tree source changed while it was being mirrored: ${path.join(targetRoot, ...relativePath.split("/"))}`);
483
+ }
484
+ ensureDirectory(snapshotRoot, relativePath, entry.mode);
485
+ mirrorWorkingTree({
486
+ sourceRoot: source.absolutePath,
487
+ targetRoot: path.join(snapshotRoot, ...relativePath.split("/")),
488
+ sourceMode: "all",
489
+ deletionMode: "all",
490
+ durability
491
+ });
492
+ trees.push(relativePath);
493
+ }
494
+ return { absent: [...input.plan.absent].sort(shallowestFirst), trees: trees.sort(shallowestFirst) };
495
+ }
496
+ function restoreWorkingTreeMirrorScope(input) {
497
+ const snapshotRoot = path.resolve(input.snapshotRoot);
498
+ const targetRoot = path.resolve(input.targetRoot);
499
+ inspectDirectoryRoot(snapshotRoot, { allowMissing: false, label: "Working-tree snapshot root" });
500
+ if (!inspectDirectoryRoot(targetRoot, { allowMissing: true, label: "Working-tree target root" })) {
501
+ fs.mkdirSync(targetRoot, { recursive: true });
502
+ }
503
+ const normalize = (relativePath) => {
504
+ const normalized = normalizeRelativePath(relativePath);
505
+ if (!normalized) throw new Error(`Invalid working-tree scope path: ${relativePath}`);
506
+ return normalized;
507
+ };
508
+ const absent = input.scope.absent.map(normalize);
509
+ const trees = new Set(input.scope.trees.map(normalize));
510
+ const captured = inspectWorkingTree(snapshotRoot, "all");
511
+ for (const tree of trees) {
512
+ if (captured.get(tree)?.kind !== "directory") {
513
+ throw new Error(`Working-tree snapshot does not hold the captured directory: ${tree}`);
514
+ }
515
+ }
516
+ const touched = /* @__PURE__ */ new Set();
517
+ for (const relativePath of [...absent].sort(deepestFirst)) {
518
+ const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
519
+ if (inspected.kind === "entry") fs.rmSync(inspected.absolutePath, { recursive: true, force: true });
520
+ touched.add(relativePath);
521
+ }
522
+ const writtenDirectories = /* @__PURE__ */ new Set();
523
+ for (const relativePath of absent) writtenDirectories.add(path.posix.dirname(relativePath));
524
+ for (const [relativePath, entry] of captured) {
525
+ if (entry.kind !== "directory" && !hasAncestorIn(relativePath, trees)) writtenDirectories.add(path.posix.dirname(relativePath));
526
+ }
527
+ for (const directory of writtenDirectories) {
528
+ let absoluteDirectory = targetRoot;
529
+ if (directory !== ".") {
530
+ const inspected = inspectRelativePathWithoutFollowingAncestors(targetRoot, directory);
531
+ if (inspected.kind !== "entry" || !inspected.stat.isDirectory()) continue;
532
+ absoluteDirectory = inspected.absolutePath;
533
+ }
534
+ for (const name of fs.readdirSync(absoluteDirectory)) {
535
+ if (!MIRROR_TEMPORARY_FILE_PATTERN.test(name)) continue;
536
+ const temporaryPath = path.join(absoluteDirectory, name);
537
+ if (!fs.lstatSync(temporaryPath).isFile()) continue;
538
+ fs.rmSync(temporaryPath, { force: true });
539
+ touched.add(directory === "." ? name : path.posix.join(directory, name));
540
+ }
541
+ }
542
+ for (const [relativePath, entry] of [...captured.entries()].sort(([left], [right]) => shallowestFirst(left, right))) {
543
+ touched.add(relativePath);
544
+ if (hasAncestorIn(relativePath, trees)) continue;
545
+ if (!trees.has(relativePath)) {
546
+ copyEntry(snapshotRoot, targetRoot, relativePath, entry, "per_entry");
547
+ continue;
548
+ }
549
+ const current = inspectRelativePathWithoutFollowingAncestors(targetRoot, relativePath);
550
+ if (current.kind === "blocked") {
551
+ throw new Error(`Refusing to restore a working-tree directory through a symlink or non-directory: ${current.blockingPath}`);
552
+ }
553
+ if (current.kind === "entry" && !current.stat.isDirectory()) {
554
+ fs.rmSync(current.absolutePath, { recursive: true, force: true });
555
+ }
556
+ ensureDirectory(targetRoot, relativePath, entry.mode);
557
+ mirrorWorkingTree({
558
+ sourceRoot: path.join(snapshotRoot, ...relativePath.split("/")),
559
+ targetRoot: path.join(targetRoot, ...relativePath.split("/")),
560
+ sourceMode: "all",
561
+ deletionMode: "all"
562
+ });
563
+ }
564
+ return { paths: [...touched].sort(shallowestFirst) };
565
+ }
566
+ function fsyncWorkingTreePaths(root, relativePaths) {
567
+ root = path.resolve(root);
568
+ inspectDirectoryRoot(root, { allowMissing: false, label: "Working-tree root" });
569
+ const directories = /* @__PURE__ */ new Set(["."]);
570
+ for (const relativePath of relativePaths) {
571
+ const normalized = normalizeRelativePath(relativePath);
572
+ if (!normalized) throw new Error(`Invalid working-tree path: ${relativePath}`);
573
+ directories.add(path.posix.dirname(normalized));
574
+ const inspected = inspectRelativePathWithoutFollowingAncestors(root, normalized);
575
+ if (inspected.kind !== "entry") continue;
576
+ if (inspected.stat.isDirectory()) directories.add(normalized);
577
+ else if (inspected.stat.isFile()) fsyncPath(inspected.absolutePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
578
+ }
579
+ for (const directory of directories) {
580
+ if (directory === ".") {
581
+ fsyncPath(root, fs.constants.O_RDONLY);
582
+ continue;
583
+ }
584
+ const inspected = inspectRelativePathWithoutFollowingAncestors(root, directory);
585
+ if (inspected.kind === "entry" && inspected.stat.isDirectory()) fsyncPath(inspected.absolutePath, fs.constants.O_RDONLY);
586
+ }
587
+ }
346
588
  export {
347
589
  assertWorkingTreeHasNoPortableGitMetadataAliases,
590
+ captureWorkingTreeMirrorPlan,
591
+ fsyncWorkingTreePaths,
348
592
  inspectWorkingTree,
349
- mirrorWorkingTree
593
+ mirrorWorkingTree,
594
+ normalizeWorkingTreeRelativePath,
595
+ planWorkingTreeMirror,
596
+ restoreWorkingTreeMirrorScope
350
597
  };