@vibe-agent-toolkit/utils 0.2.0-rc.10 → 0.2.0-rc.12
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.
- package/README.md +112 -10
- package/dist/archive/archive.d.ts +129 -0
- package/dist/archive/archive.d.ts.map +1 -0
- package/dist/archive/archive.js +223 -0
- package/dist/archive/archive.js.map +1 -0
- package/dist/archive/entries.d.ts +132 -0
- package/dist/archive/entries.d.ts.map +1 -0
- package/dist/archive/entries.js +223 -0
- package/dist/archive/entries.js.map +1 -0
- package/dist/archive/limits.d.ts +39 -0
- package/dist/archive/limits.d.ts.map +1 -0
- package/dist/archive/limits.js +59 -0
- package/dist/archive/limits.js.map +1 -0
- package/dist/archive/records.d.ts +14 -0
- package/dist/archive/records.d.ts.map +1 -0
- package/dist/archive/records.js +3 -0
- package/dist/archive/records.js.map +1 -0
- package/dist/archive/tar-reader.d.ts +18 -0
- package/dist/archive/tar-reader.d.ts.map +1 -0
- package/dist/archive/tar-reader.js +173 -0
- package/dist/archive/tar-reader.js.map +1 -0
- package/dist/archive/zip-reader.d.ts +76 -0
- package/dist/archive/zip-reader.d.ts.map +1 -0
- package/dist/archive/zip-reader.js +376 -0
- package/dist/archive/zip-reader.js.map +1 -0
- package/dist/archive.d.ts +14 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +12 -0
- package/dist/archive.js.map +1 -0
- package/dist/asset-reference.d.ts +17 -1
- package/dist/asset-reference.d.ts.map +1 -1
- package/dist/asset-reference.js +31 -7
- package/dist/asset-reference.js.map +1 -1
- package/dist/dirent-kind.d.ts +13 -40
- package/dist/dirent-kind.d.ts.map +1 -1
- package/dist/dirent-kind.js +21 -69
- package/dist/dirent-kind.js.map +1 -1
- package/dist/entrypoint.d.ts +2 -2
- package/dist/entrypoint.d.ts.map +1 -1
- package/dist/entrypoint.js +4 -3
- package/dist/entrypoint.js.map +1 -1
- package/dist/env-flag.d.ts +13 -5
- package/dist/env-flag.d.ts.map +1 -1
- package/dist/env-flag.js +13 -5
- package/dist/env-flag.js.map +1 -1
- package/dist/errors/confirmed-absent.d.ts +41 -0
- package/dist/errors/confirmed-absent.d.ts.map +1 -0
- package/dist/errors/confirmed-absent.js +43 -0
- package/dist/errors/confirmed-absent.js.map +1 -0
- package/dist/errors/errno-table.d.ts +177 -0
- package/dist/errors/errno-table.d.ts.map +1 -0
- package/dist/errors/errno-table.js +343 -0
- package/dist/errors/errno-table.js.map +1 -0
- package/dist/errors/fs-boundary.d.ts +36 -0
- package/dist/errors/fs-boundary.d.ts.map +1 -0
- package/dist/errors/fs-boundary.js +121 -0
- package/dist/errors/fs-boundary.js.map +1 -0
- package/dist/errors/fs-fault.d.ts +176 -0
- package/dist/errors/fs-fault.d.ts.map +1 -0
- package/dist/errors/fs-fault.js +215 -0
- package/dist/errors/fs-fault.js.map +1 -0
- package/dist/errors/git-errno.d.ts +13 -0
- package/dist/errors/git-errno.d.ts.map +1 -0
- package/dist/errors/git-errno.js +45 -0
- package/dist/errors/git-errno.js.map +1 -0
- package/dist/errors/path-present.d.ts +72 -0
- package/dist/errors/path-present.d.ts.map +1 -0
- package/dist/errors/path-present.js +110 -0
- package/dist/errors/path-present.js.map +1 -0
- package/dist/errors/rag-index-empty.d.ts +16 -0
- package/dist/errors/rag-index-empty.d.ts.map +1 -0
- package/dist/errors/rag-index-empty.js +16 -0
- package/dist/errors/rag-index-empty.js.map +1 -0
- package/dist/errors/suppressed-faults.d.ts +34 -0
- package/dist/errors/suppressed-faults.d.ts.map +1 -0
- package/dist/errors/suppressed-faults.js +66 -0
- package/dist/errors/suppressed-faults.js.map +1 -0
- package/dist/file-crawler.d.ts +13 -18
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +140 -24
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs-utils.d.ts +75 -50
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +148 -158
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +5 -4
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +5 -4
- package/dist/fs.js.map +1 -1
- package/dist/git-snapshot.d.ts +25 -17
- package/dist/git-snapshot.d.ts.map +1 -1
- package/dist/git-snapshot.js +65 -11
- package/dist/git-snapshot.js.map +1 -1
- package/dist/git-tracker.d.ts +11 -3
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +29 -6
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +7 -35
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +1 -1
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +1 -1
- package/dist/git.js.map +1 -1
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +5 -4
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/in-order.d.ts +93 -0
- package/dist/in-order.d.ts.map +1 -0
- package/dist/in-order.js +165 -0
- package/dist/in-order.js.map +1 -0
- package/dist/index.d.ts +20 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -4
- package/dist/index.js.map +1 -1
- package/dist/kill-process-tree.d.ts +43 -0
- package/dist/kill-process-tree.d.ts.map +1 -0
- package/dist/kill-process-tree.js +48 -0
- package/dist/kill-process-tree.js.map +1 -0
- package/dist/link-following.d.ts +115 -0
- package/dist/link-following.d.ts.map +1 -0
- package/dist/link-following.js +283 -0
- package/dist/link-following.js.map +1 -0
- package/dist/listing-refusal.d.ts +44 -0
- package/dist/listing-refusal.d.ts.map +1 -1
- package/dist/listing-refusal.js +79 -2
- package/dist/listing-refusal.js.map +1 -1
- package/dist/name-fold.d.ts +25 -0
- package/dist/name-fold.d.ts.map +1 -0
- package/dist/name-fold.js +40 -0
- package/dist/name-fold.js.map +1 -0
- package/dist/path-containment.d.ts +17 -0
- package/dist/path-containment.d.ts.map +1 -1
- package/dist/path-containment.js +28 -5
- package/dist/path-containment.js.map +1 -1
- package/dist/path-utils.d.ts +14 -17
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +37 -55
- package/dist/path-utils.js.map +1 -1
- package/dist/process.d.ts +2 -0
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +2 -0
- package/dist/process.js.map +1 -1
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +8 -9
- package/dist/project-utils.js.map +1 -1
- package/dist/safe-exec.d.ts +6 -2
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +1 -1
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-targets.d.ts.map +1 -1
- package/dist/skill-targets.js +3 -1
- package/dist/skill-targets.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +0 -15
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +11 -50
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/special-file.d.ts +18 -0
- package/dist/special-file.d.ts.map +1 -0
- package/dist/special-file.js +21 -0
- package/dist/special-file.js.map +1 -0
- package/dist/terminating-signals.d.ts +129 -0
- package/dist/terminating-signals.d.ts.map +1 -0
- package/dist/terminating-signals.js +210 -0
- package/dist/terminating-signals.js.map +1 -0
- package/dist/test-helpers.d.ts +9 -5
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +18 -21
- package/dist/test-helpers.js.map +1 -1
- package/dist/test-scratch-guard.d.ts +34 -0
- package/dist/test-scratch-guard.d.ts.map +1 -0
- package/dist/test-scratch-guard.js +45 -0
- package/dist/test-scratch-guard.js.map +1 -0
- package/dist/testing/archive-fixtures.d.ts +147 -0
- package/dist/testing/archive-fixtures.d.ts.map +1 -0
- package/dist/testing/archive-fixtures.js +449 -0
- package/dist/testing/archive-fixtures.js.map +1 -0
- package/dist/testing/executables.d.ts +6 -0
- package/dist/testing/executables.d.ts.map +1 -1
- package/dist/testing/executables.js +20 -9
- package/dist/testing/executables.js.map +1 -1
- package/dist/testing/fault-fs-preload.d.ts +16 -0
- package/dist/testing/fault-fs-preload.d.ts.map +1 -0
- package/dist/testing/fault-fs-preload.js +22 -0
- package/dist/testing/fault-fs-preload.js.map +1 -0
- package/dist/testing/fault-fs.d.ts +115 -0
- package/dist/testing/fault-fs.d.ts.map +1 -0
- package/dist/testing/fault-fs.js +411 -0
- package/dist/testing/fault-fs.js.map +1 -0
- package/dist/testing/fault-spec.d.ts +83 -0
- package/dist/testing/fault-spec.d.ts.map +1 -0
- package/dist/testing/fault-spec.js +132 -0
- package/dist/testing/fault-spec.js.map +1 -0
- package/dist/testing/hostile-tree.d.ts.map +1 -1
- package/dist/testing/hostile-tree.js +3 -4
- package/dist/testing/hostile-tree.js.map +1 -1
- package/dist/testing/platform-gates.d.ts +8 -0
- package/dist/testing/platform-gates.d.ts.map +1 -1
- package/dist/testing/platform-gates.js +24 -0
- package/dist/testing/platform-gates.js.map +1 -1
- package/dist/testing/temp-dir.d.ts +31 -0
- package/dist/testing/temp-dir.d.ts.map +1 -1
- package/dist/testing/temp-dir.js +63 -0
- package/dist/testing/temp-dir.js.map +1 -1
- package/dist/testing/tier-timeout.d.ts +34 -0
- package/dist/testing/tier-timeout.d.ts.map +1 -0
- package/dist/testing/tier-timeout.js +45 -0
- package/dist/testing/tier-timeout.js.map +1 -0
- package/dist/testing/tree-snapshot.d.ts +71 -0
- package/dist/testing/tree-snapshot.d.ts.map +1 -0
- package/dist/testing/tree-snapshot.js +163 -0
- package/dist/testing/tree-snapshot.js.map +1 -0
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +8 -1
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts +29 -0
- package/dist/text-content.d.ts.map +1 -1
- package/dist/text-content.js +64 -0
- package/dist/text-content.js.map +1 -1
- package/dist/text-file.d.ts +58 -6
- package/dist/text-file.d.ts.map +1 -1
- package/dist/text-file.js +111 -11
- package/dist/text-file.js.map +1 -1
- package/dist/text.d.ts +1 -1
- package/dist/text.d.ts.map +1 -1
- package/dist/text.js +1 -1
- package/dist/text.js.map +1 -1
- package/dist/timing-dump.d.ts.map +1 -1
- package/dist/timing-dump.js +2 -10
- package/dist/timing-dump.js.map +1 -1
- package/dist/tree-change/apply-decisions.d.ts +101 -0
- package/dist/tree-change/apply-decisions.d.ts.map +1 -0
- package/dist/tree-change/apply-decisions.js +139 -0
- package/dist/tree-change/apply-decisions.js.map +1 -0
- package/dist/tree-change/apply.d.ts +98 -0
- package/dist/tree-change/apply.d.ts.map +1 -0
- package/dist/tree-change/apply.js +364 -0
- package/dist/tree-change/apply.js.map +1 -0
- package/dist/tree-change/copy-decisions.d.ts +58 -0
- package/dist/tree-change/copy-decisions.d.ts.map +1 -0
- package/dist/tree-change/copy-decisions.js +66 -0
- package/dist/tree-change/copy-decisions.js.map +1 -0
- package/dist/tree-change/copy-tree.d.ts +125 -0
- package/dist/tree-change/copy-tree.d.ts.map +1 -0
- package/dist/tree-change/copy-tree.js +279 -0
- package/dist/tree-change/copy-tree.js.map +1 -0
- package/dist/tree-change/files.d.ts +167 -0
- package/dist/tree-change/files.d.ts.map +1 -0
- package/dist/tree-change/files.js +423 -0
- package/dist/tree-change/files.js.map +1 -0
- package/dist/tree-change/followed-walk.d.ts +54 -0
- package/dist/tree-change/followed-walk.d.ts.map +1 -0
- package/dist/tree-change/followed-walk.js +81 -0
- package/dist/tree-change/followed-walk.js.map +1 -0
- package/dist/tree-change/host-chain.d.ts +48 -0
- package/dist/tree-change/host-chain.d.ts.map +1 -0
- package/dist/tree-change/host-chain.js +85 -0
- package/dist/tree-change/host-chain.js.map +1 -0
- package/dist/tree-change/identity-compare.d.ts +61 -0
- package/dist/tree-change/identity-compare.d.ts.map +1 -0
- package/dist/tree-change/identity-compare.js +115 -0
- package/dist/tree-change/identity-compare.js.map +1 -0
- package/dist/tree-change/identity.d.ts +62 -0
- package/dist/tree-change/identity.d.ts.map +1 -0
- package/dist/tree-change/identity.js +152 -0
- package/dist/tree-change/identity.js.map +1 -0
- package/dist/tree-change/index.d.ts +24 -0
- package/dist/tree-change/index.d.ts.map +1 -0
- package/dist/tree-change/index.js +14 -0
- package/dist/tree-change/index.js.map +1 -0
- package/dist/tree-change/link-chain.d.ts +180 -0
- package/dist/tree-change/link-chain.d.ts.map +1 -0
- package/dist/tree-change/link-chain.js +377 -0
- package/dist/tree-change/link-chain.js.map +1 -0
- package/dist/tree-change/link-containment.d.ts +105 -0
- package/dist/tree-change/link-containment.d.ts.map +1 -0
- package/dist/tree-change/link-containment.js +196 -0
- package/dist/tree-change/link-containment.js.map +1 -0
- package/dist/tree-change/link-walk.d.ts +49 -0
- package/dist/tree-change/link-walk.d.ts.map +1 -0
- package/dist/tree-change/link-walk.js +69 -0
- package/dist/tree-change/link-walk.js.map +1 -0
- package/dist/tree-change/plan.d.ts +206 -0
- package/dist/tree-change/plan.d.ts.map +1 -0
- package/dist/tree-change/plan.js +320 -0
- package/dist/tree-change/plan.js.map +1 -0
- package/dist/tree-change/readable-tree.d.ts +44 -0
- package/dist/tree-change/readable-tree.d.ts.map +1 -0
- package/dist/tree-change/readable-tree.js +52 -0
- package/dist/tree-change/readable-tree.js.map +1 -0
- package/dist/tree-change/rollback-error.d.ts +29 -0
- package/dist/tree-change/rollback-error.d.ts.map +1 -0
- package/dist/tree-change/rollback-error.js +31 -0
- package/dist/tree-change/rollback-error.js.map +1 -0
- package/dist/tree-change/staging-names.d.ts +27 -0
- package/dist/tree-change/staging-names.d.ts.map +1 -0
- package/dist/tree-change/staging-names.js +40 -0
- package/dist/tree-change/staging-names.js.map +1 -0
- package/dist/tree-change/tree-walk.d.ts +81 -0
- package/dist/tree-change/tree-walk.d.ts.map +1 -0
- package/dist/tree-change/tree-walk.js +212 -0
- package/dist/tree-change/tree-walk.js.map +1 -0
- package/dist/win32-namespace.d.ts +24 -0
- package/dist/win32-namespace.d.ts.map +1 -0
- package/dist/win32-namespace.js +31 -0
- package/dist/win32-namespace.js.map +1 -0
- package/dist/yaml/surgical-yaml.d.ts +12 -3
- package/dist/yaml/surgical-yaml.d.ts.map +1 -1
- package/dist/yaml/surgical-yaml.js +16 -6
- package/dist/yaml/surgical-yaml.js.map +1 -1
- package/eslint/README.md +12 -4
- package/eslint/rules/fs-binding-scan.cjs +278 -0
- package/eslint/rules/no-adhoc-errno.cjs +364 -0
- package/eslint/rules/no-bare-symlink-in-tests.cjs +2 -3
- package/eslint/rules/no-blind-catch.cjs +1 -1
- package/eslint/rules/no-destructive-fs.cjs +359 -0
- package/eslint/rules/no-existssync.cjs +70 -0
- package/eslint/rules/no-fragile-entrypoint-guard.cjs +3 -2
- package/eslint/rules/no-link-following-fs.cjs +68 -0
- package/eslint/rules/no-literal-process-exit.cjs +96 -8
- package/eslint/rules/no-literal-test-timeout.cjs +214 -0
- package/eslint/rules/no-raw-signal-listener.cjs +224 -0
- package/eslint/rules/no-stdout-outside-writer.cjs +135 -0
- package/package.json +10 -2
- package/dist/errors/errno.d.ts +0 -51
- package/dist/errors/errno.d.ts.map +0 -1
- package/dist/errors/errno.js +0 -112
- package/dist/errors/errno.js.map +0 -1
- package/eslint/rules/no-fs-promises-cp.cjs +0 -43
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file-level half of the tree-change primitive: the staging names every
|
|
3
|
+
* change sits beside its destination under, the one rename (retried where Windows
|
|
4
|
+
* makes a rename race a scanner), the one removal (a read-only tree included), a
|
|
5
|
+
* whole-file replace that never truncates what it replaces, and a temp directory
|
|
6
|
+
* that is always disposed of.
|
|
7
|
+
*/
|
|
8
|
+
import { rmSync } from 'node:fs';
|
|
9
|
+
import fs from 'node:fs/promises';
|
|
10
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
11
|
+
import { requireConfirmedAbsent } from '../errors/confirmed-absent.js';
|
|
12
|
+
import { isAccessRefusedError, isAlreadyExistsError, isPathAbsentError, isRenameContentionError } from '../errors/errno-table.js';
|
|
13
|
+
import { classifyFsFault, everywhere, isFsFaultError, withFsFault } from '../errors/fs-fault.js';
|
|
14
|
+
import { recordSuppressedFault } from '../errors/suppressed-faults.js';
|
|
15
|
+
import { mapWithConcurrency } from '../in-order.js';
|
|
16
|
+
import { realpathNative, requireFollowedStat } from '../link-following.js';
|
|
17
|
+
import { isUnderRoot } from '../path-containment.js';
|
|
18
|
+
import { safePath, toForwardSlash } from '../path-core.js';
|
|
19
|
+
import { normalizedTmpdir } from '../path-utils.js';
|
|
20
|
+
import { entryInTheWayFault, renameRetryDelay, tempDirRefusal } from './apply-decisions.js';
|
|
21
|
+
import { stagingName } from './staging-names.js';
|
|
22
|
+
/** The owner's read, write and search bits: what a directory needs for its entries to be removed. */
|
|
23
|
+
const OWNER_RWX = 0o700;
|
|
24
|
+
export { TEMP_DIR_OUTSIDE_TMPDIR_CODE } from './apply-decisions.js';
|
|
25
|
+
function renameTrying(from, to, attempt) {
|
|
26
|
+
return fs.rename(from, to).catch(async (error) => {
|
|
27
|
+
const wait = renameRetryDelay(process.platform, attempt, isRenameContentionError(error));
|
|
28
|
+
if (wait === undefined)
|
|
29
|
+
throw error;
|
|
30
|
+
await delay(wait);
|
|
31
|
+
return renameTrying(from, to, attempt + 1);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Rename `from` to `to` — a file or a whole tree — in one step. Under win32 a
|
|
36
|
+
* rename the OS refuses with contention (`EPERM`, `EBUSY`, `EACCES`: a scanner,
|
|
37
|
+
* an indexer, a handle still closing) is retried, up to 6 tries with a backoff of
|
|
38
|
+
* 50·2ⁿ ms (1.55 s in all); any other errno, and any errno on another platform,
|
|
39
|
+
* is final.
|
|
40
|
+
*
|
|
41
|
+
* @param from - The entry to move
|
|
42
|
+
* @param to - Its new name, on the same filesystem
|
|
43
|
+
* @throws the rename's raw errno, for the caller to classify on the side it knows
|
|
44
|
+
*/
|
|
45
|
+
export function renameFileAtomic(from, to) {
|
|
46
|
+
return renameTrying(from, to, 0);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Give `dir` the owner's rwx, then list the directories under it (links not followed). A
|
|
50
|
+
* directory already gone is nothing to grant: `rm`'s recursion removes siblings concurrently and
|
|
51
|
+
* goes on after it has rejected, so the walk can meet one it has just taken.
|
|
52
|
+
*/
|
|
53
|
+
async function grantOwner(dir) {
|
|
54
|
+
try {
|
|
55
|
+
await fs.chmod(dir, OWNER_RWX);
|
|
56
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
57
|
+
return entries.filter((entry) => !entry.isSymbolicLink() && entry.isDirectory()).map((entry) => safePath.join(dir, entry.name));
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (isPathAbsentError(error))
|
|
61
|
+
return [];
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Give every directory of `level` and below the owner's rwx, each before it is listed: one
|
|
67
|
+
* level at a time, at most `FS_CONCURRENCY` in flight however wide or deep the tree is.
|
|
68
|
+
*/
|
|
69
|
+
async function grantOwnerWalk(level) {
|
|
70
|
+
if (level.length === 0)
|
|
71
|
+
return;
|
|
72
|
+
const next = await mapWithConcurrency(level, grantOwner);
|
|
73
|
+
await grantOwnerWalk(next.flat());
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Remove `entry` — a tree, a file or a link (never what it points at) — whatever
|
|
77
|
+
* the modes under it: the root gets the owner's rwx first, and a removal still
|
|
78
|
+
* refused (`EACCES` / `EPERM`: a read-only directory deeper down refuses the
|
|
79
|
+
* removal of its entries) is retried once every directory under it has been given
|
|
80
|
+
* them too. Nothing there is not a failure. A root the OS will not chmod is still
|
|
81
|
+
* removed when the removal itself is allowed. Done means not there: a removal that
|
|
82
|
+
* resolved with the entry still present (`rm`'s `force` reading its own listing's
|
|
83
|
+
* ENOENT as "gone") is run once more, and a plain `rmdir` then throws why it stays.
|
|
84
|
+
*
|
|
85
|
+
* @throws the raw errno of the chmod, listing or removal that failed
|
|
86
|
+
*/
|
|
87
|
+
export async function removeEntry(entry) {
|
|
88
|
+
if (!(await removeOnce(entry)))
|
|
89
|
+
return;
|
|
90
|
+
// `rm` with `force` takes an ENOENT from its own listing for "already gone" and resolves with the entry
|
|
91
|
+
// still there: done is what is not there. Once more; then the non-recursive removal names why it stays.
|
|
92
|
+
if (!(await present(entry)))
|
|
93
|
+
return;
|
|
94
|
+
await removeOnce(entry);
|
|
95
|
+
if (await present(entry))
|
|
96
|
+
await fs.rmdir(entry);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The `lstat` of `entry`, or `undefined` when nothing — a dangling link included — is there. "Nothing
|
|
100
|
+
* there" is believed only when the parent's listing agrees: an `lstat` answering ENOTDIR for a parked
|
|
101
|
+
* tree that WAS there read as "already gone", and the run exited 0 with the tree left behind.
|
|
102
|
+
*/
|
|
103
|
+
async function examined(entry) {
|
|
104
|
+
try {
|
|
105
|
+
return await fs.lstat(entry);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (!isPathAbsentError(error))
|
|
109
|
+
throw error;
|
|
110
|
+
requireConfirmedAbsent(entry, error, { action: 'remove the entry', path: entry }, { follows: false, sideOf: everywhere('destination') });
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Whether anything is at `entry` (see {@link examined}); a refusal is thrown. */
|
|
115
|
+
async function present(entry) {
|
|
116
|
+
return (await examined(entry)) !== undefined;
|
|
117
|
+
}
|
|
118
|
+
/** One removal pass (see {@link removeEntry}); `false` when nothing was there to remove. */
|
|
119
|
+
async function removeOnce(entry) {
|
|
120
|
+
const stats = await examined(entry);
|
|
121
|
+
if (stats === undefined)
|
|
122
|
+
return false;
|
|
123
|
+
const directory = stats.isDirectory();
|
|
124
|
+
// Best effort: a root the OS will not chmod may still be removable, and the walk below asks again.
|
|
125
|
+
if (directory)
|
|
126
|
+
await fs.chmod(entry, OWNER_RWX).catch((error_) => error_);
|
|
127
|
+
try {
|
|
128
|
+
await fs.rm(entry, { recursive: true, force: true });
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (!directory || !isAccessRefusedError(error))
|
|
132
|
+
throw error;
|
|
133
|
+
await grantOwnerWalk([entry]);
|
|
134
|
+
await fs.rm(entry, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
/** Where a write to `dest` really lands: a link's target (so a dotfiles link stays a link), or `dest` itself. */
|
|
139
|
+
async function writeTarget(dest) {
|
|
140
|
+
try {
|
|
141
|
+
// The one native realpath rule (`realpathNative`): an unanswerable volume is asked again by the JS walk.
|
|
142
|
+
// `dest` is the path the verb itself names — the operator's own, so its own link is the edge.
|
|
143
|
+
const real = await realpathNative(dest, dest);
|
|
144
|
+
return { path: real, mode: (await requireFollowedStat(real, real)).mode & 0o7777 };
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
if (isPathAbsentError(error))
|
|
148
|
+
return { path: dest, mode: undefined };
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Prove an existing file writable as it is, by opening it for writing — no truncation, no
|
|
154
|
+
* creation — and closing it. A rename over a file needs only its directory's permission, so
|
|
155
|
+
* without this a file its owner made read-only (or Windows marked read-only) would be replaced
|
|
156
|
+
* where a write in place is refused. An open, not `access`: on Windows `access` does not consult ACLs.
|
|
157
|
+
*/
|
|
158
|
+
async function proveWritable(file) {
|
|
159
|
+
const handle = await fs.open(file, 'r+');
|
|
160
|
+
await handle.close();
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Replace the file `dest` with `contents` in one step: the bytes go to a temp
|
|
164
|
+
* name beside it, which is then renamed over it ({@link renameFileAtomic}). A
|
|
165
|
+
* write that fails — a full disk mid-write — leaves `dest` byte-equal and the
|
|
166
|
+
* temp removed (a temp that will not go is recorded beside the thrown error:
|
|
167
|
+
* `suppressedFaultsOf`). An existing file keeps its mode, and must be writable as it is: one
|
|
168
|
+
* a write in place would be refused (a read-only file) is refused before anything is written,
|
|
169
|
+
* never replaced by the rename. A link is written through, so it stays a link to the replaced file.
|
|
170
|
+
*
|
|
171
|
+
* @param dest - The file to replace or create
|
|
172
|
+
* @param contents - Its new bytes
|
|
173
|
+
* @throws the raw errno, for the caller to classify on the side it knows
|
|
174
|
+
*/
|
|
175
|
+
export async function replaceFile(dest, contents) {
|
|
176
|
+
const target = await writeTarget(dest);
|
|
177
|
+
if (target.mode !== undefined)
|
|
178
|
+
await proveWritable(target.path);
|
|
179
|
+
const temp = stagingName(target.path);
|
|
180
|
+
try {
|
|
181
|
+
const handle = await fs.open(temp, 'wx');
|
|
182
|
+
try {
|
|
183
|
+
await handle.writeFile(contents);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
await handle.close();
|
|
187
|
+
}
|
|
188
|
+
if (target.mode !== undefined)
|
|
189
|
+
await fs.chmod(temp, target.mode);
|
|
190
|
+
await renameFileAtomic(temp, target.path);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
await fs.rm(temp, { force: true }).catch((error_) => {
|
|
194
|
+
recordSuppressedFault(error, classifyFsFault(error_, { side: 'destination', action: 'remove the temporary file', path: temp }));
|
|
195
|
+
});
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** What is at `path`, never following a link; `undefined` when nothing is. A refusal is thrown raw. */
|
|
200
|
+
async function entryAt(path) {
|
|
201
|
+
try {
|
|
202
|
+
return await fs.lstat(path);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
if (isPathAbsentError(error))
|
|
206
|
+
return undefined;
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The directory `segments` name under `dir`, each component a REAL directory: made (a plain `mkdir`)
|
|
212
|
+
* when absent, refused when a link or a file stands there. One component at a time — each must be a
|
|
213
|
+
* directory before the next is looked at.
|
|
214
|
+
*/
|
|
215
|
+
async function plainDirectoryUnder(dir, segments, writing) {
|
|
216
|
+
const [segment, ...rest] = segments;
|
|
217
|
+
if (segment === undefined)
|
|
218
|
+
return dir;
|
|
219
|
+
const next = safePath.join(dir, segment);
|
|
220
|
+
const there = await entryAt(next);
|
|
221
|
+
if (there === undefined)
|
|
222
|
+
await fs.mkdir(next);
|
|
223
|
+
else if (!there.isDirectory())
|
|
224
|
+
throw entryInTheWayFault(next, writing, undefined);
|
|
225
|
+
return plainDirectoryUnder(next, rest, writing);
|
|
226
|
+
}
|
|
227
|
+
/** The components of `relative`, a path under a tree's root. A `..` would leave the tree: a defect in the caller. */
|
|
228
|
+
function segmentsUnderRoot(relative) {
|
|
229
|
+
const segments = toForwardSlash(relative).split('/').filter((segment) => segment.length > 0 && segment !== '.');
|
|
230
|
+
if (segments.includes('..'))
|
|
231
|
+
throw new TypeError(`"${relative}" is not a path under the tree's root`);
|
|
232
|
+
return segments;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Make the directory `relative` under `root` — a tree VAT is building, which may hold whatever its
|
|
236
|
+
* source shipped, links included — without ever going through a link: every component must be a real
|
|
237
|
+
* directory (adopted when there, made by a plain `mkdir` when absent). What a recursive `mkdir`
|
|
238
|
+
* would do instead is follow a link standing where a component goes, out of the tree.
|
|
239
|
+
*
|
|
240
|
+
* @param root - The tree's root, which the caller made
|
|
241
|
+
* @param relative - The directory, relative to `root`, forward slashes
|
|
242
|
+
* @param writing - What the directory is for, for a refusal's message
|
|
243
|
+
* @returns The directory's path
|
|
244
|
+
* @throws FsFaultError side `source`, origin `content`, class `occupied`, naming the link or file
|
|
245
|
+
* standing where a directory goes; any other failure is the raw errno
|
|
246
|
+
*/
|
|
247
|
+
export async function makeDirectoryUnder(root, relative, writing) {
|
|
248
|
+
return await plainDirectoryUnder(root, segmentsUnderRoot(relative), writing);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The ONE way VAT makes a file in a tree it is building — a staged tree that may hold whatever its
|
|
252
|
+
* source shipped, links included — without ever writing over or through what is already there.
|
|
253
|
+
*
|
|
254
|
+
* Every directory of `relative` under `root` must be a real directory (a link standing where one goes
|
|
255
|
+
* would carry the write outside the tree); a missing one is made by a plain `mkdir`. The file itself
|
|
256
|
+
* is made by `create`, which must be an EXCLUSIVE create (`wx`, which no link satisfies).
|
|
257
|
+
*
|
|
258
|
+
* @throws FsFaultError side `source`, origin `content`, class `occupied`, naming the entry in the
|
|
259
|
+
* way; any other failure is the raw errno, for the caller's boundary to classify
|
|
260
|
+
*/
|
|
261
|
+
async function createUnder(root, relative, options, create) {
|
|
262
|
+
const segments = segmentsUnderRoot(relative);
|
|
263
|
+
const name = segments.at(-1);
|
|
264
|
+
if (name === undefined)
|
|
265
|
+
throw new TypeError(`"${relative}" names no file under the tree's root`);
|
|
266
|
+
const target = safePath.join(await plainDirectoryUnder(root, segments.slice(0, -1), options.writing), name);
|
|
267
|
+
if (options.existing === 'replace') {
|
|
268
|
+
const there = await entryAt(target);
|
|
269
|
+
if (there?.isFile() === true)
|
|
270
|
+
await fs.unlink(target);
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
return await create(target);
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
throw isAlreadyExistsError(error) ? entryInTheWayFault(target, options.writing, error) : error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Write a file VAT makes into a tree it is building, never over or through what is already there
|
|
281
|
+
* (see {@link createUnder}: real directories only, an exclusive create, `existing` deciding the
|
|
282
|
+
* entry at the file's own name).
|
|
283
|
+
*
|
|
284
|
+
* @param root - The tree's root, which the caller made
|
|
285
|
+
* @param relative - The file, relative to `root`, forward slashes
|
|
286
|
+
* @param contents - Its bytes
|
|
287
|
+
* @param options - `existing`, and `writing`: what the file is, for a refusal's message
|
|
288
|
+
* @throws FsFaultError side `source`, origin `content`, class `occupied`, naming the entry in the
|
|
289
|
+
* way; any other failure is the raw errno, for the caller's boundary to classify
|
|
290
|
+
*/
|
|
291
|
+
export async function writeFileUnder(root, relative, contents, options) {
|
|
292
|
+
await createUnder(root, relative, options, (target) => fs.writeFile(target, contents, { flag: 'wx' }));
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* {@link writeFileUnder} for bytes that are streamed: the file is made the same way and handed back
|
|
296
|
+
* OPEN, for the caller to fill and close. What the copy of one file into such a tree is built on.
|
|
297
|
+
*
|
|
298
|
+
* @returns The new file's handle, open for writing; the caller closes it
|
|
299
|
+
*/
|
|
300
|
+
export function openFileUnder(root, relative, options) {
|
|
301
|
+
return createUnder(root, relative, options, (target) => fs.open(target, 'wx'));
|
|
302
|
+
}
|
|
303
|
+
/** The classified fault disposing of `dir` raised, or `undefined` once it is gone. A non-filesystem error is a defect and propagates. */
|
|
304
|
+
async function disposalFault(dir) {
|
|
305
|
+
try {
|
|
306
|
+
await removeEntry(dir);
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
return classifiedDisposalFault(dir, error);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/** What a failed disposal of `dir` threw, classified; a non-filesystem error is a defect and is rethrown. */
|
|
314
|
+
function classifiedDisposalFault(dir, error) {
|
|
315
|
+
// The action names the directory: what is left is all of it, while the errno may name an entry deep inside.
|
|
316
|
+
const fault = classifyFsFault(error, { side: 'environment', action: `remove the temporary directory ${dir}`, path: dir });
|
|
317
|
+
if (!isFsFaultError(fault))
|
|
318
|
+
throw error;
|
|
319
|
+
return fault;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Remove a temporary directory, its modes notwithstanding — only one strictly
|
|
323
|
+
* under the temp directory (`normalizedTmpdir()`): this removal makes a read-only
|
|
324
|
+
* tree writable before it deletes it, so a wrong argument (an empty string, a
|
|
325
|
+
* project directory) must be refused before anything is touched. A directory
|
|
326
|
+
* elsewhere that VAT owns goes through a `remove` plan.
|
|
327
|
+
*
|
|
328
|
+
* @param dir - The directory
|
|
329
|
+
* @returns `undefined` when it is gone (or was never there); otherwise the leftover: the
|
|
330
|
+
* classified fault (an `FsFaultError`, side `environment`) whose message names the directory,
|
|
331
|
+
* for the caller to report as the one `TREE_CLEANUP_INCOMPLETE` warning beside its work
|
|
332
|
+
* @throws VatError {@link TEMP_DIR_OUTSIDE_TMPDIR_CODE} — a defect in the caller — for a
|
|
333
|
+
* directory not strictly under the temp directory, with nothing removed
|
|
334
|
+
*/
|
|
335
|
+
export async function disposeTempDir(dir) {
|
|
336
|
+
refuseOutsideTmpdir(dir);
|
|
337
|
+
return await disposalFault(dir);
|
|
338
|
+
}
|
|
339
|
+
/** How many times {@link disposeTempDirSync} retries a transient refusal (`EBUSY`, `EPERM`, `ENOTEMPTY`, …). */
|
|
340
|
+
const SYNC_DISPOSAL_RETRIES = 4;
|
|
341
|
+
/** The first retry's delay; each further one waits this much longer (Node's linear backoff). */
|
|
342
|
+
const SYNC_DISPOSAL_RETRY_DELAY_MS = 40;
|
|
343
|
+
/**
|
|
344
|
+
* {@link disposeTempDir}, synchronously — for a process on its way OUT, where no promise will ever
|
|
345
|
+
* settle: a signal handler about to re-raise, an `exit` listener. Same refusal of any directory not
|
|
346
|
+
* strictly under the temp directory, same answer.
|
|
347
|
+
*
|
|
348
|
+
* ⚠️ No mode repair: it is for a directory the process filled with its own ordinary files. A
|
|
349
|
+
* SHORT bounded retry ({@link SYNC_DISPOSAL_RETRIES} × {@link SYNC_DISPOSAL_RETRY_DELAY_MS}, linear
|
|
350
|
+
* backoff, at most ~0.4 s) covers the one transient refusal a dying process meets: on Windows a
|
|
351
|
+
* child it has just terminated still holds a handle in the directory for a moment, and removal
|
|
352
|
+
* answers `EBUSY`/`EPERM` until the OS lets go. Anything still there after it is answered as the
|
|
353
|
+
* leftover fault.
|
|
354
|
+
*
|
|
355
|
+
* @param dir - The directory
|
|
356
|
+
* @returns `undefined` when it is gone (or was never there); otherwise the leftover fault
|
|
357
|
+
* @throws VatError {@link TEMP_DIR_OUTSIDE_TMPDIR_CODE} — a defect in the caller — for a
|
|
358
|
+
* directory not strictly under the temp directory, with nothing removed
|
|
359
|
+
*/
|
|
360
|
+
export function disposeTempDirSync(dir) {
|
|
361
|
+
refuseOutsideTmpdir(dir);
|
|
362
|
+
try {
|
|
363
|
+
rmSync(dir, { recursive: true, force: true, maxRetries: SYNC_DISPOSAL_RETRIES, retryDelay: SYNC_DISPOSAL_RETRY_DELAY_MS });
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
return classifiedDisposalFault(dir, error);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/** Refuse (a defect in the caller) a directory to dispose of that is not strictly under the temp directory. */
|
|
371
|
+
function refuseOutsideTmpdir(dir) {
|
|
372
|
+
const tmp = normalizedTmpdir();
|
|
373
|
+
const refusal = tempDirRefusal(dir, toForwardSlash(tmp), isUnderRoot(tmp, dir));
|
|
374
|
+
if (refusal !== undefined)
|
|
375
|
+
throw refusal;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Dispose of a temporary directory because the work that owned it failed — for a directory
|
|
379
|
+
* whose lifetime is not one call (one handed back to the caller on success, removed only on
|
|
380
|
+
* failure), where {@link withTempDir} cannot hold it. The failure stays the answer: a
|
|
381
|
+
* disposal fault is recorded beside it (`suppressedFaultsOf`), never thrown in its place.
|
|
382
|
+
*
|
|
383
|
+
* @param dir - The directory, strictly under the temp directory
|
|
384
|
+
* @param failure - What the work threw; the caller rethrows it
|
|
385
|
+
* @returns `undefined` when the directory went; otherwise the leftover fault, already recorded
|
|
386
|
+
* beside `failure` — for a caller that must also say so where no report will carry the failure
|
|
387
|
+
* @throws VatError {@link TEMP_DIR_OUTSIDE_TMPDIR_CODE} — a defect in the caller — for a
|
|
388
|
+
* directory not strictly under the temp directory, with nothing removed
|
|
389
|
+
*/
|
|
390
|
+
export async function disposeTempDirAfterFailure(dir, failure) {
|
|
391
|
+
refuseOutsideTmpdir(dir);
|
|
392
|
+
const fault = await disposalFault(dir);
|
|
393
|
+
if (fault !== undefined)
|
|
394
|
+
recordSuppressedFault(failure, fault);
|
|
395
|
+
return fault;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Run `work` in a fresh directory under the temp dir (`normalizedTmpdir()`), and
|
|
399
|
+
* dispose of it after, whatever happened.
|
|
400
|
+
*
|
|
401
|
+
* The work's error always wins: rethrown unchanged — never wrapped, never given a
|
|
402
|
+
* cause, never mutated — with a disposal failure recorded beside it
|
|
403
|
+
* (`suppressedFaultsOf`), so a report still names the directory left behind.
|
|
404
|
+
* When the work succeeded, nothing is thrown: the value comes back with the
|
|
405
|
+
* disposal's `leftover` (see {@link TempDirOutcome}), for the caller to report.
|
|
406
|
+
*
|
|
407
|
+
* @param prefix - `mkdtemp` prefix, so a leaked directory names its owner
|
|
408
|
+
* @param work - Given the directory's forward-slash path
|
|
409
|
+
*/
|
|
410
|
+
export async function withTempDir(prefix, work) {
|
|
411
|
+
const tmp = normalizedTmpdir();
|
|
412
|
+
const dir = toForwardSlash(await withFsFault({ side: 'environment', action: 'create a temporary directory', path: tmp }, () => fs.mkdtemp(safePath.join(tmp, prefix))));
|
|
413
|
+
let value;
|
|
414
|
+
try {
|
|
415
|
+
value = await work(dir);
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
await disposeTempDirAfterFailure(dir, error);
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
return { value, leftover: await disposalFault(dir) };
|
|
422
|
+
}
|
|
423
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/tree-change/files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two refusals that bound a walk which FOLLOWS links: a link back into the
|
|
3
|
+
* walk ({@link FollowedWalk}, {@link DirectoryWalkRevisitedError}) and, for a walk
|
|
4
|
+
* contained to a root, a link out of it ({@link CopyLinkEscapesSourceError}).
|
|
5
|
+
* `proveTreeReadable` and `copyTree` under `links: 'follow-contained'` hold both;
|
|
6
|
+
* every other following walk holds a {@link FollowedWalk}.
|
|
7
|
+
*/
|
|
8
|
+
import { type FsSide } from '../errors/fs-fault.js';
|
|
9
|
+
import { VatError } from '../errors/vat-error.js';
|
|
10
|
+
/** The `VatError` code of a {@link DirectoryWalkRevisitedError}. */
|
|
11
|
+
export declare const DIRECTORY_WALK_REVISITED_CODE = "DIRECTORY_WALK_REVISITED";
|
|
12
|
+
/** Thrown when a following walk is led back into a directory it has already entered. */
|
|
13
|
+
export declare class DirectoryWalkRevisitedError extends VatError {
|
|
14
|
+
constructor(dir: string, enteredAs: string);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The directories a FOLLOWING walk has entered, by realpath, so a link that
|
|
18
|
+
* leads back into the walk is refused instead of recursed until
|
|
19
|
+
* `ENAMETOOLONG`.
|
|
20
|
+
*
|
|
21
|
+
* A walk that follows links (`direntKindFollowing`) has no cycle guard by
|
|
22
|
+
* construction: `scripts/loop -> .` makes a copy create `dest/loop/loop/…`,
|
|
23
|
+
* writing every file at every level first, and a hashing walk do the same in
|
|
24
|
+
* memory. Every following walk holds one of these and calls {@link enter}
|
|
25
|
+
* on every directory it recurses into — the root included. A REVISIT is
|
|
26
|
+
* refused, not just a cycle: two links to one directory make the walk's
|
|
27
|
+
* output ambiguous (which spelling is the file's path?), and a refusal that
|
|
28
|
+
* names both spellings is the answer the author can act on.
|
|
29
|
+
*/
|
|
30
|
+
export declare class FollowedWalk {
|
|
31
|
+
#private;
|
|
32
|
+
/**
|
|
33
|
+
* @param side - The side of the verb the walked tree is on: a refused realpath in {@link enter} is a fault there
|
|
34
|
+
* @param within - The tree the walk is in (the operator's own input): every link beneath it is read before the kernel resolves it
|
|
35
|
+
*/
|
|
36
|
+
constructor(side: FsSide, within: string);
|
|
37
|
+
/**
|
|
38
|
+
* Record that the walk is entering `dir`. Synchronous on purpose — one
|
|
39
|
+
* realpath per directory entered is nothing beside the listing itself, and
|
|
40
|
+
* the sync and async walkers then share one guard.
|
|
41
|
+
*
|
|
42
|
+
* @param dir - A directory the walk is about to list, in any spelling
|
|
43
|
+
* @throws {DirectoryWalkRevisitedError} When its realpath was entered before
|
|
44
|
+
* @throws FsFaultError on the walk's side (origin `content`) when the OS refuses to resolve it
|
|
45
|
+
*/
|
|
46
|
+
enter(dir: string): void;
|
|
47
|
+
}
|
|
48
|
+
/** The `VatError` code of a {@link CopyLinkEscapesSourceError}. */
|
|
49
|
+
export declare const COPY_LINK_ESCAPES_SOURCE_CODE = "COPY_LINK_ESCAPES_SOURCE";
|
|
50
|
+
/** Thrown when a link inside a tree walked with `links: 'follow-contained'` points outside it. */
|
|
51
|
+
export declare class CopyLinkEscapesSourceError extends VatError {
|
|
52
|
+
constructor(link: string, src: string);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=followed-walk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"followed-walk.d.ts","sourceRoot":"","sources":["../../src/tree-change/followed-walk.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAmB,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAIlD,oEAAoE;AACpE,eAAO,MAAM,6BAA6B,6BAA6B,CAAC;AAExE,wFAAwF;AACxF,qBAAa,2BAA4B,SAAQ,QAAQ;gBAC3C,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;CAM3C;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,YAAY;;IAKvB;;;OAGG;gBACS,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAKxC;;;;;;;;OAQG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAczB;AAED,mEAAmE;AACnE,eAAO,MAAM,6BAA6B,6BAA6B,CAAC;AAExE,kGAAkG;AAClG,qBAAa,0BAA2B,SAAQ,QAAQ;gBAC1C,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;CAQtC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two refusals that bound a walk which FOLLOWS links: a link back into the
|
|
3
|
+
* walk ({@link FollowedWalk}, {@link DirectoryWalkRevisitedError}) and, for a walk
|
|
4
|
+
* contained to a root, a link out of it ({@link CopyLinkEscapesSourceError}).
|
|
5
|
+
* `proveTreeReadable` and `copyTree` under `links: 'follow-contained'` hold both;
|
|
6
|
+
* every other following walk holds a {@link FollowedWalk}.
|
|
7
|
+
*/
|
|
8
|
+
import { classifyFsFault } from '../errors/fs-fault.js';
|
|
9
|
+
import { VatError } from '../errors/vat-error.js';
|
|
10
|
+
import { toForwardSlash } from '../path-core.js';
|
|
11
|
+
import { normalizePath } from '../path-utils.js';
|
|
12
|
+
/** The `VatError` code of a {@link DirectoryWalkRevisitedError}. */
|
|
13
|
+
export const DIRECTORY_WALK_REVISITED_CODE = 'DIRECTORY_WALK_REVISITED';
|
|
14
|
+
/** Thrown when a following walk is led back into a directory it has already entered. */
|
|
15
|
+
export class DirectoryWalkRevisitedError extends VatError {
|
|
16
|
+
constructor(dir, enteredAs) {
|
|
17
|
+
super(DIRECTORY_WALK_REVISITED_CODE, `Refusing to enter ${dir}: it is the directory already walked as ${enteredAs} — a symlink leads the walk back into itself.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The directories a FOLLOWING walk has entered, by realpath, so a link that
|
|
22
|
+
* leads back into the walk is refused instead of recursed until
|
|
23
|
+
* `ENAMETOOLONG`.
|
|
24
|
+
*
|
|
25
|
+
* A walk that follows links (`direntKindFollowing`) has no cycle guard by
|
|
26
|
+
* construction: `scripts/loop -> .` makes a copy create `dest/loop/loop/…`,
|
|
27
|
+
* writing every file at every level first, and a hashing walk do the same in
|
|
28
|
+
* memory. Every following walk holds one of these and calls {@link enter}
|
|
29
|
+
* on every directory it recurses into — the root included. A REVISIT is
|
|
30
|
+
* refused, not just a cycle: two links to one directory make the walk's
|
|
31
|
+
* output ambiguous (which spelling is the file's path?), and a refusal that
|
|
32
|
+
* names both spellings is the answer the author can act on.
|
|
33
|
+
*/
|
|
34
|
+
export class FollowedWalk {
|
|
35
|
+
#entered = new Map();
|
|
36
|
+
#side;
|
|
37
|
+
#within;
|
|
38
|
+
/**
|
|
39
|
+
* @param side - The side of the verb the walked tree is on: a refused realpath in {@link enter} is a fault there
|
|
40
|
+
* @param within - The tree the walk is in (the operator's own input): every link beneath it is read before the kernel resolves it
|
|
41
|
+
*/
|
|
42
|
+
constructor(side, within) {
|
|
43
|
+
this.#side = side;
|
|
44
|
+
this.#within = within;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Record that the walk is entering `dir`. Synchronous on purpose — one
|
|
48
|
+
* realpath per directory entered is nothing beside the listing itself, and
|
|
49
|
+
* the sync and async walkers then share one guard.
|
|
50
|
+
*
|
|
51
|
+
* @param dir - A directory the walk is about to list, in any spelling
|
|
52
|
+
* @throws {DirectoryWalkRevisitedError} When its realpath was entered before
|
|
53
|
+
* @throws FsFaultError on the walk's side (origin `content`) when the OS refuses to resolve it
|
|
54
|
+
*/
|
|
55
|
+
enter(dir) {
|
|
56
|
+
const real = toForwardSlash(this.#realpath(dir));
|
|
57
|
+
const before = this.#entered.get(real);
|
|
58
|
+
if (before !== undefined)
|
|
59
|
+
throw new DirectoryWalkRevisitedError(dir, before);
|
|
60
|
+
this.#entered.set(real, dir);
|
|
61
|
+
}
|
|
62
|
+
#realpath(dir) {
|
|
63
|
+
try {
|
|
64
|
+
return normalizePath(dir, this.#within);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw classifyFsFault(error, { side: this.#side, origin: 'content', action: `resolve ${dir}`, path: dir });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** The `VatError` code of a {@link CopyLinkEscapesSourceError}. */
|
|
72
|
+
export const COPY_LINK_ESCAPES_SOURCE_CODE = 'COPY_LINK_ESCAPES_SOURCE';
|
|
73
|
+
/** Thrown when a link inside a tree walked with `links: 'follow-contained'` points outside it. */
|
|
74
|
+
export class CopyLinkEscapesSourceError extends VatError {
|
|
75
|
+
constructor(link, src) {
|
|
76
|
+
super(COPY_LINK_ESCAPES_SOURCE_CODE, `Refusing to copy ${link}: it is a symlink to a path outside ${src}. ` +
|
|
77
|
+
'A copy follows links, so this would ship content the source tree does not own — ' +
|
|
78
|
+
'replace the link with the files, or point it inside the tree.');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=followed-walk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"followed-walk.js","sourceRoot":"","sources":["../../src/tree-change/followed-walk.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAe,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,oEAAoE;AACpE,MAAM,CAAC,MAAM,6BAA6B,GAAG,0BAA0B,CAAC;AAExE,wFAAwF;AACxF,MAAM,OAAO,2BAA4B,SAAQ,QAAQ;IACvD,YAAY,GAAW,EAAE,SAAiB;QACxC,KAAK,CACH,6BAA6B,EAC7B,qBAAqB,GAAG,2CAA2C,SAAS,+CAA+C,CAC5H,CAAC;IACJ,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,YAAY;IACd,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IACrC,KAAK,CAAS;IACd,OAAO,CAAS;IAEzB;;;OAGG;IACH,YAAY,IAAY,EAAE,MAAc;QACtC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,GAAW;QACf,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM,IAAI,2BAA2B,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC7E,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,SAAS,CAAC,GAAW;QACnB,IAAI,CAAC;YACH,OAAO,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,MAAM,eAAe,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7G,CAAC;IACH,CAAC;CACF;AAED,mEAAmE;AACnE,MAAM,CAAC,MAAM,6BAA6B,GAAG,0BAA0B,CAAC;AAExE,kGAAkG;AAClG,MAAM,OAAO,0BAA2B,SAAQ,QAAQ;IACtD,YAAY,IAAY,EAAE,GAAW;QACnC,KAAK,CACH,6BAA6B,EAC7B,oBAAoB,IAAI,uCAAuC,GAAG,IAAI;YACpE,kFAAkF;YAClF,+DAA+D,CAClE,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The link-chain resolver ({@link walkLinkChain}) bound to this host's filesystem: `lstat` and
|
|
3
|
+
* `readlink`, and nothing that follows a link. Every call VAT makes that FOLLOWS a link — a
|
|
4
|
+
* `stat`, a `realpath` — reads the chain here first (`link-following.ts`, `assertLocalChain`), so
|
|
5
|
+
* no lane — a write lane, a read lane, an untrusted tree or the user's own — hands the kernel a
|
|
6
|
+
* chain whose every text has not been read.
|
|
7
|
+
*/
|
|
8
|
+
import { VatError } from '../errors/vat-error.js';
|
|
9
|
+
import { type ChainRefusal, type ChainRule, type LinkChain, type PlaceKind } from './link-chain.js';
|
|
10
|
+
/**
|
|
11
|
+
* What stands at `path`, a link there not followed. A probe the OS refuses is thrown raw, for the
|
|
12
|
+
* caller to classify.
|
|
13
|
+
*
|
|
14
|
+
* ⚠️ "Not followed" is `lstat`'s word, and on Windows libuv keeps it only for what it calls a
|
|
15
|
+
* symbolic link: a junction to a volume (`\??\Volume{…}\`) and a reparse point of another tag
|
|
16
|
+
* (cloud files, dedup, WCI) are retried as a link-FOLLOWING `stat` and reported as the directory
|
|
17
|
+
* or file they lead to. Their targets are local — the kernel refuses a remote mount point — so no
|
|
18
|
+
* credential leaves the host, but the walk reads such a place as a plain directory where the
|
|
19
|
+
* kernel reparses it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function hostPlaceKind(path: string): PlaceKind;
|
|
22
|
+
/**
|
|
23
|
+
* Where `path` leads on this host's filesystem, under `rule` ({@link walkLinkChain}).
|
|
24
|
+
*
|
|
25
|
+
* @param path - Absolute, or relative to cwd; either separator
|
|
26
|
+
* @param rule - The flavor (bound from the host at the edge), the tree, and what the chain may do outside it
|
|
27
|
+
* @throws The raw errno of an `lstat` or `readlink` the OS refused, for the caller to classify
|
|
28
|
+
*/
|
|
29
|
+
export declare function walkHostChain(path: string, rule: ChainRule): LinkChain;
|
|
30
|
+
/**
|
|
31
|
+
* A path VAT would not resolve: a link on the way to it leads to a network path, or the chain is
|
|
32
|
+
* longer than any kernel follows. A read lane records it as that one target's fault
|
|
33
|
+
* (`linkResolutionFault`); a lane that stops classifies it on its own side, like any refusal
|
|
34
|
+
* (`classifyFsFault`), so a destination's refused chain reads as the destination's.
|
|
35
|
+
*/
|
|
36
|
+
export declare class LinkChainRefusedError extends VatError {
|
|
37
|
+
/** The path whose resolution was asked. */
|
|
38
|
+
readonly path: string;
|
|
39
|
+
/** Why, with the link whose text decided it. */
|
|
40
|
+
readonly refusal: ChainRefusal;
|
|
41
|
+
constructor(path: string, refusal: ChainRefusal);
|
|
42
|
+
}
|
|
43
|
+
/** Whether `error` is a {@link LinkChainRefusedError}, by its code (a `dist` copy never `instanceof`-matches a `src` one). */
|
|
44
|
+
export declare function isLinkChainRefusedError(error: unknown): error is VatError & {
|
|
45
|
+
readonly path: string;
|
|
46
|
+
readonly refusal: ChainRefusal;
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=host-chain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-chain.d.ts","sourceRoot":"","sources":["../../src/tree-change/host-chain.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAAc,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,EAAmB,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAiB,KAAK,SAAS,EAAiB,MAAM,iBAAiB,CAAC;AAEnJ;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CASrD;AAKD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,SAAS,CAEtE;AAkBD;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,QAAQ;IACjD,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;gBACnB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY;CAKhD;AAED,8HAA8H;AAC9H,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;CAAE,CAErI"}
|