@vibe-agent-toolkit/utils 0.2.0-rc.11 → 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 +111 -9
- 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 +2 -0
- package/dist/env-flag.d.ts.map +1 -1
- package/dist/env-flag.js +2 -0
- 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 +4 -4
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +4 -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 +3 -1
- package/dist/path-containment.d.ts.map +1 -1
- package/dist/path-containment.js +13 -7
- 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-file.d.ts +14 -1
- package/dist/text-file.d.ts.map +1 -1
- package/dist/text-file.js +41 -8
- package/dist/text-file.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 +3 -16
- 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,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE module that asks the kernel to FOLLOW a link — `stat`, `realpath` — and the only place
|
|
3
|
+
* `local/no-link-following-fs` lets such a call stand (every other `packages/*\/src` file asks
|
|
4
|
+
* here; the allowlist of operator's-own-path files is the rule's option, asserted both ways).
|
|
5
|
+
*
|
|
6
|
+
* The order is the guarantee. A Windows kernel asked to follow a link to `\\host\share` opens an
|
|
7
|
+
* SMB session and offers the operator's NTLM credentials to that host before any answer comes
|
|
8
|
+
* back, so every following call here first reads the chain of links to its path hop by hop, by
|
|
9
|
+
* `lstat` and `readlink` alone ({@link assertLocalChain}), and asks the kernel only once the chain
|
|
10
|
+
* is read and local. On every host: the walk is the same rule everywhere, its flavor bound from
|
|
11
|
+
* `process.platform` here, at the edge.
|
|
12
|
+
*
|
|
13
|
+
* Each call names the tree its path is crawled in, `within` — the operator's own input (a project
|
|
14
|
+
* root, a directory the operator named, VAT's own scratch). That root is resolved by the kernel
|
|
15
|
+
* once, as the operator's input always was, and the walk reads only what lies beneath it: the
|
|
16
|
+
* root's ancestors are the operator's, never probed and never blamed for a fault. A path that IS
|
|
17
|
+
* the operator's own input passes itself as `within`, and the kernel answers it directly.
|
|
18
|
+
*
|
|
19
|
+
* A probe the OS refuses is never a pass: it is thrown, raw, for the caller's classifier — a read
|
|
20
|
+
* lane records it as that one path's fault, a write lane stops on its own side.
|
|
21
|
+
*/
|
|
22
|
+
import { type BigIntStats, type Stats } from 'node:fs';
|
|
23
|
+
import { type PathFlavor } from './tree-change/link-chain.js';
|
|
24
|
+
/**
|
|
25
|
+
* The kernel's realpath of the operator's OWN path (an edge: a root the operator named, VAT's own
|
|
26
|
+
* scratch), in the host's separators — the one place the kernel follows a link no walk read.
|
|
27
|
+
*
|
|
28
|
+
* @throws The resolution's own error: the caller classifies it
|
|
29
|
+
*/
|
|
30
|
+
export declare function edgeRealpathSync(path: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Throw unless every link on the way to `path` beneath `within` has been read and leads somewhere
|
|
33
|
+
* local — the check the kernel's following call is the SECOND lock behind. The user's own paths:
|
|
34
|
+
* an absolute local text is followed and walked by the same rules, so a later hop's network text
|
|
35
|
+
* is still caught. A chain that leads to nothing (a dangle, a cycle) is answered so — the kernel is
|
|
36
|
+
* not asked about it.
|
|
37
|
+
*
|
|
38
|
+
* @param path - Absolute, or relative to cwd; either separator
|
|
39
|
+
* @param within - The tree `path` is crawled in: the operator's own input (or `path` itself, for the operator's own path)
|
|
40
|
+
* @param flavor - Whose kernel is about to be asked: bound from `process.platform` at the edge
|
|
41
|
+
* @returns `'local'` when the kernel may now be asked; `'nothing'` when the chain leads to nothing
|
|
42
|
+
* @throws LinkChainRefusedError when a link on the way names a network or device path, or the chain
|
|
43
|
+
* never ends; the raw errno of an `lstat`/`readlink` (or the root's realpath) the OS refused
|
|
44
|
+
*/
|
|
45
|
+
export declare function assertLocalChain(path: string, within: string, flavor: PathFlavor): 'local' | 'nothing';
|
|
46
|
+
/**
|
|
47
|
+
* Throw what the kernel's own `syscall` would have thrown for a chain that leads to nothing — `ENOENT`
|
|
48
|
+
* for a dangle, `ELOOP` for a cycle — so the caller's classifier reads it as it always did; return
|
|
49
|
+
* when the chain is read, local, and the kernel may be asked.
|
|
50
|
+
*/
|
|
51
|
+
export declare function requireSomethingAt(path: string, within: string, syscall: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* The native realpath of a path crawled beneath `within` — the canonical ON-DISK spelling,
|
|
54
|
+
* forward-slashed — asked of the kernel only once the chain is read and local.
|
|
55
|
+
*
|
|
56
|
+
* @throws LinkChainRefusedError for a chain through a network path, or one that never ends; the
|
|
57
|
+
* `ENOENT` of a chain that leads to nothing; else the resolution's own error — the caller classifies it
|
|
58
|
+
*/
|
|
59
|
+
export declare function realpathNativeSync(path: string, within: string): string;
|
|
60
|
+
/** The asynchronous {@link realpathNativeSync}. */
|
|
61
|
+
export declare function realpathNative(path: string, within: string): Promise<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Node's JS realpath (`fs.realpath`, not `.native`: the spelling the caller wrote is kept, so it
|
|
64
|
+
* stays in the namespace of a lexical fallback) of a path crawled beneath `within`, asked only once
|
|
65
|
+
* the chain is read and local. Read off the `node:fs` default object per call, so a spy installed
|
|
66
|
+
* after import still sees it.
|
|
67
|
+
*
|
|
68
|
+
* @throws as {@link realpathNativeSync}
|
|
69
|
+
*/
|
|
70
|
+
export declare function followedRealpath(path: string, within: string): Promise<string>;
|
|
71
|
+
/** The synchronous {@link followedRealpath} (`fs.realpathSync`, the JS walk). */
|
|
72
|
+
export declare function followedRealpathSync(path: string, within: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* The kernel's `stat` of the operator's OWN path (an edge: the base a crawl was asked to start
|
|
75
|
+
* from), raw: the caller classifies its error.
|
|
76
|
+
*/
|
|
77
|
+
export declare function edgeStatSync(path: string): Stats;
|
|
78
|
+
/** {@link edgeStatSync} with `bigint` fields — the `dev`/`ino` an entry identity is built from. */
|
|
79
|
+
export declare function edgeBigintStatSync(path: string): BigIntStats;
|
|
80
|
+
/**
|
|
81
|
+
* What `path` (crawled beneath `within`) resolves to, its links followed — `undefined` when it
|
|
82
|
+
* resolves to nothing: absent, a dangling link, a cycle, or a chain the kernel itself calls a loop.
|
|
83
|
+
*
|
|
84
|
+
* @throws LinkChainRefusedError for a chain VAT will not hand the kernel; the raw errno of any probe
|
|
85
|
+
* the OS refused (never folded into "absent")
|
|
86
|
+
*/
|
|
87
|
+
export declare function followedStatSync(path: string, within: string): Stats | undefined;
|
|
88
|
+
/** The asynchronous {@link followedStatSync}. */
|
|
89
|
+
export declare function followedStat(path: string, within: string): Promise<Stats | undefined>;
|
|
90
|
+
/**
|
|
91
|
+
* {@link followedStatSync} for a caller that cannot go on without an answer: a path that resolves
|
|
92
|
+
* to nothing is the `ENOENT` (or, for a cycle, `ELOOP`) the kernel's own `stat` would have thrown,
|
|
93
|
+
* for the caller's classifier.
|
|
94
|
+
*/
|
|
95
|
+
export declare function requireFollowedStatSync(path: string, within: string): Stats;
|
|
96
|
+
/** The asynchronous {@link requireFollowedStatSync}. */
|
|
97
|
+
export declare function requireFollowedStat(path: string, within: string): Promise<Stats>;
|
|
98
|
+
/**
|
|
99
|
+
* The `type` a link made to copy the link at `path` (crawled beneath `within`) is created with —
|
|
100
|
+
* `'dir'` when its chain, read by text, ends at a directory; `'file'` otherwise, a dangle or a cycle
|
|
101
|
+
* included (what Node itself picks for a target it cannot `stat`). Read by `lstat` and `readlink`
|
|
102
|
+
* alone: a `symlink` with no type has Node on Windows ask the KERNEL to follow the target to pick
|
|
103
|
+
* one, and a link text no walk read must never reach the kernel.
|
|
104
|
+
*
|
|
105
|
+
* @throws LinkChainRefusedError for a chain through a network or device path, or one that never
|
|
106
|
+
* ends; the raw errno of an `lstat`/`readlink` the OS refused
|
|
107
|
+
*/
|
|
108
|
+
export declare function linkCopyType(path: string, within: string): 'dir' | 'file';
|
|
109
|
+
/** What a followed path resolves to. */
|
|
110
|
+
export type FollowedPathKind = 'directory' | 'file' | 'other' | 'absent';
|
|
111
|
+
/** What `path` beneath `within` resolves to ({@link followedStatSync}): `absent` for nothing. */
|
|
112
|
+
export declare function followedKindSync(path: string, within: string): FollowedPathKind;
|
|
113
|
+
/** The asynchronous {@link followedKindSync}. */
|
|
114
|
+
export declare function followedKind(path: string, within: string): Promise<FollowedPathKind>;
|
|
115
|
+
//# sourceMappingURL=link-following.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-following.d.ts","sourceRoot":"","sources":["../src/link-following.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAe,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAO/D,OAAO,EAAmD,KAAK,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAwB/G;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOrD;AAmDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,GAAG,SAAS,CAEtG;AASD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAKtF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGvE;AAED,mDAAmD;AACnD,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlF;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGpF;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGzE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAEhD;AAED,mGAAmG;AACnG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAE5D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAQhF;AAED,iDAAiD;AACjD,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAQ3F;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAG3E;AAED,wDAAwD;AACxD,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAGtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAMzE;AAED,wCAAwC;AACxC,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAQzE,iGAAiG;AACjG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAE/E;AAED,iDAAiD;AACjD,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAE1F"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE module that asks the kernel to FOLLOW a link — `stat`, `realpath` — and the only place
|
|
3
|
+
* `local/no-link-following-fs` lets such a call stand (every other `packages/*\/src` file asks
|
|
4
|
+
* here; the allowlist of operator's-own-path files is the rule's option, asserted both ways).
|
|
5
|
+
*
|
|
6
|
+
* The order is the guarantee. A Windows kernel asked to follow a link to `\\host\share` opens an
|
|
7
|
+
* SMB session and offers the operator's NTLM credentials to that host before any answer comes
|
|
8
|
+
* back, so every following call here first reads the chain of links to its path hop by hop, by
|
|
9
|
+
* `lstat` and `readlink` alone ({@link assertLocalChain}), and asks the kernel only once the chain
|
|
10
|
+
* is read and local. On every host: the walk is the same rule everywhere, its flavor bound from
|
|
11
|
+
* `process.platform` here, at the edge.
|
|
12
|
+
*
|
|
13
|
+
* Each call names the tree its path is crawled in, `within` — the operator's own input (a project
|
|
14
|
+
* root, a directory the operator named, VAT's own scratch). That root is resolved by the kernel
|
|
15
|
+
* once, as the operator's input always was, and the walk reads only what lies beneath it: the
|
|
16
|
+
* root's ancestors are the operator's, never probed and never blamed for a fault. A path that IS
|
|
17
|
+
* the operator's own input passes itself as `within`, and the kernel answers it directly.
|
|
18
|
+
*
|
|
19
|
+
* A probe the OS refuses is never a pass: it is thrown, raw, for the caller's classifier — a read
|
|
20
|
+
* lane records it as that one path's fault, a write lane stops on its own side.
|
|
21
|
+
*/
|
|
22
|
+
// Every kernel call is reached through the DEFAULT objects, never a named binding: Node snapshots a
|
|
23
|
+
// builtin's named ESM exports at import time, so a `vi.spyOn(fs, 'statSync')` cannot see a call made
|
|
24
|
+
// through one — it attaches, counts zero, and reads exactly like "no I/O here" (the guards on
|
|
25
|
+
// `pathSpellingFrom`/`realpathRowFrom` and the fault matrix are assertions of exactly that).
|
|
26
|
+
import nodeFs from 'node:fs';
|
|
27
|
+
import fsPromises from 'node:fs/promises';
|
|
28
|
+
import { promisify } from 'node:util';
|
|
29
|
+
import { isFinalPathUnanswerableError, isPathAbsentError, isUnresolvableLinkError } from './errors/errno-table.js';
|
|
30
|
+
import { relativeEscapesRoot, safePath, toForwardSlash } from './path-core.js';
|
|
31
|
+
import { LinkChainRefusedError, walkHostChain } from './tree-change/host-chain.js';
|
|
32
|
+
import { HOST_PATH_FLAVOR, namedPathRoot } from './tree-change/link-chain.js';
|
|
33
|
+
/**
|
|
34
|
+
* Whether a native realpath's failure must be asked again of Node's JS realpath before anyone
|
|
35
|
+
* believes it — the ONE fallback rule every native site shares.
|
|
36
|
+
*
|
|
37
|
+
* - A volume whose driver does not implement final-path queries (a RAM disk, some redirectors and
|
|
38
|
+
* virtual drives) answers EVERY native realpath with `ERROR_INVALID_FUNCTION`, which libuv
|
|
39
|
+
* reports as `EISDIR` ({@link isFinalPathUnanswerableError}). That says nothing about the path.
|
|
40
|
+
* - An absent answer ({@link isPathAbsentError}) is confirmed by the JS walk: on a musl libc
|
|
41
|
+
* without procfs the native `realpath(3)` reports `ENOENT` for a path that exists, and Windows
|
|
42
|
+
* answers `ENOENT` for a file on a volume mounted into a folder with no drive letter (pinned on a
|
|
43
|
+
* real host by `realpath-native-folder-mount.integration.test.ts`). A path the JS walk also finds
|
|
44
|
+
* absent throws the walk's own `ENOENT`, so nothing that is really missing reads as present.
|
|
45
|
+
*
|
|
46
|
+
* ⚠️ The JS answer is not the on-disk spelling: it keeps the casing the caller spelled (and, on
|
|
47
|
+
* Windows, an 8.3 short name), and it expands only what `lstat` calls a link — a `subst` drive and
|
|
48
|
+
* a volume mount point (which libuv reports as a plain directory) stay as spelled. A caller that
|
|
49
|
+
* needs the kernel's own identity compares `stat` identities, not these strings.
|
|
50
|
+
*/
|
|
51
|
+
function nativeAnswerNeedsJsWalk(error) {
|
|
52
|
+
return isFinalPathUnanswerableError(error) || isPathAbsentError(error);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The kernel's realpath of the operator's OWN path (an edge: a root the operator named, VAT's own
|
|
56
|
+
* scratch), in the host's separators — the one place the kernel follows a link no walk read.
|
|
57
|
+
*
|
|
58
|
+
* @throws The resolution's own error: the caller classifies it
|
|
59
|
+
*/
|
|
60
|
+
export function edgeRealpathSync(path) {
|
|
61
|
+
try {
|
|
62
|
+
return nodeFs.realpathSync.native(path);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (!nativeAnswerNeedsJsWalk(error))
|
|
66
|
+
throw error;
|
|
67
|
+
return nodeFs.realpathSync(path);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Read the chain to `path`, hop by hop, beneath the tree `within`.
|
|
72
|
+
*
|
|
73
|
+
* - `path` is `within` itself: the operator's own input, answered by the kernel (`edge`).
|
|
74
|
+
* - `path` lies beneath `within`: the walk starts at `within`'s real path and reads only the
|
|
75
|
+
* components below it — never the root's own ancestors.
|
|
76
|
+
* - `path` lies outside `within` (a link target named by content, a path a caller built): the whole
|
|
77
|
+
* of it is walked from its own root, ancestors included, as any link target is.
|
|
78
|
+
*/
|
|
79
|
+
function walkWithin(path, within, flavor) {
|
|
80
|
+
const start = toForwardSlash(safePath.resolve(path));
|
|
81
|
+
const root = toForwardSlash(safePath.resolve(within));
|
|
82
|
+
const below = safePath.relative(root, start);
|
|
83
|
+
if (below === '')
|
|
84
|
+
return { kind: 'edge' };
|
|
85
|
+
if (relativeEscapesRoot(below))
|
|
86
|
+
return walkWhole(start, flavor);
|
|
87
|
+
let real;
|
|
88
|
+
try {
|
|
89
|
+
real = realRootOf(root);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
// Nothing is beneath a root that is not there.
|
|
93
|
+
if (isPathAbsentError(error))
|
|
94
|
+
return { kind: 'nothing', at: root, cause: 'absent' };
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
return walkHostChain(safePath.join(real, below), { flavor, root: real, outside: 'follow' });
|
|
98
|
+
}
|
|
99
|
+
/** The whole chain to `start` (forward-slashed, absolute), ancestors included, from the root it hangs from. */
|
|
100
|
+
function walkWhole(start, flavor) {
|
|
101
|
+
return walkHostChain(start, { flavor, root: namedPathRoot(start, flavor), outside: 'follow' });
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Each tree root's real path, resolved by the kernel ONCE per process — at the edge, as the operator's
|
|
105
|
+
* input always was — not once per path crawled beneath it. Only an answer is kept: a root the OS
|
|
106
|
+
* refused, or one not there yet, is asked again next time.
|
|
107
|
+
*/
|
|
108
|
+
const REAL_ROOTS = new Map();
|
|
109
|
+
function realRootOf(root) {
|
|
110
|
+
const known = REAL_ROOTS.get(root);
|
|
111
|
+
if (known !== undefined)
|
|
112
|
+
return known;
|
|
113
|
+
const real = toForwardSlash(edgeRealpathSync(root));
|
|
114
|
+
REAL_ROOTS.set(root, real);
|
|
115
|
+
return real;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Throw unless every link on the way to `path` beneath `within` has been read and leads somewhere
|
|
119
|
+
* local — the check the kernel's following call is the SECOND lock behind. The user's own paths:
|
|
120
|
+
* an absolute local text is followed and walked by the same rules, so a later hop's network text
|
|
121
|
+
* is still caught. A chain that leads to nothing (a dangle, a cycle) is answered so — the kernel is
|
|
122
|
+
* not asked about it.
|
|
123
|
+
*
|
|
124
|
+
* @param path - Absolute, or relative to cwd; either separator
|
|
125
|
+
* @param within - The tree `path` is crawled in: the operator's own input (or `path` itself, for the operator's own path)
|
|
126
|
+
* @param flavor - Whose kernel is about to be asked: bound from `process.platform` at the edge
|
|
127
|
+
* @returns `'local'` when the kernel may now be asked; `'nothing'` when the chain leads to nothing
|
|
128
|
+
* @throws LinkChainRefusedError when a link on the way names a network or device path, or the chain
|
|
129
|
+
* never ends; the raw errno of an `lstat`/`readlink` (or the root's realpath) the OS refused
|
|
130
|
+
*/
|
|
131
|
+
export function assertLocalChain(path, within, flavor) {
|
|
132
|
+
return nothingOnTheWay(path, within, flavor) === undefined ? 'local' : 'nothing';
|
|
133
|
+
}
|
|
134
|
+
/** {@link assertLocalChain}, naming why a chain leads to nothing: `undefined` when the kernel may be asked. */
|
|
135
|
+
function nothingOnTheWay(path, within, flavor) {
|
|
136
|
+
const walked = walkWithin(path, within, flavor);
|
|
137
|
+
if (walked.kind === 'refused')
|
|
138
|
+
throw new LinkChainRefusedError(path, walked.refusal);
|
|
139
|
+
return walked.kind === 'nothing' ? walked.cause : undefined;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Throw what the kernel's own `syscall` would have thrown for a chain that leads to nothing — `ENOENT`
|
|
143
|
+
* for a dangle, `ELOOP` for a cycle — so the caller's classifier reads it as it always did; return
|
|
144
|
+
* when the chain is read, local, and the kernel may be asked.
|
|
145
|
+
*/
|
|
146
|
+
export function requireSomethingAt(path, within, syscall) {
|
|
147
|
+
const cause = nothingOnTheWay(path, within, HOST_PATH_FLAVOR);
|
|
148
|
+
if (cause === undefined)
|
|
149
|
+
return;
|
|
150
|
+
const [code, words] = cause === 'cycle' ? ['ELOOP', 'too many symbolic links encountered'] : ['ENOENT', 'no such file or directory'];
|
|
151
|
+
throw Object.assign(new Error(`${code}: ${words}, ${syscall} '${path}'`), { code, syscall, path });
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* The native realpath of a path crawled beneath `within` — the canonical ON-DISK spelling,
|
|
155
|
+
* forward-slashed — asked of the kernel only once the chain is read and local.
|
|
156
|
+
*
|
|
157
|
+
* @throws LinkChainRefusedError for a chain through a network path, or one that never ends; the
|
|
158
|
+
* `ENOENT` of a chain that leads to nothing; else the resolution's own error — the caller classifies it
|
|
159
|
+
*/
|
|
160
|
+
export function realpathNativeSync(path, within) {
|
|
161
|
+
requireSomethingAt(path, within, 'realpath');
|
|
162
|
+
return toForwardSlash(edgeRealpathSync(path));
|
|
163
|
+
}
|
|
164
|
+
/** The asynchronous {@link realpathNativeSync}. */
|
|
165
|
+
export async function realpathNative(path, within) {
|
|
166
|
+
requireSomethingAt(path, within, 'realpath');
|
|
167
|
+
try {
|
|
168
|
+
return toForwardSlash(await promisify(nodeFs.realpath.native)(path));
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (!nativeAnswerNeedsJsWalk(error))
|
|
172
|
+
throw error;
|
|
173
|
+
return toForwardSlash(await promisify(nodeFs.realpath)(path));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Node's JS realpath (`fs.realpath`, not `.native`: the spelling the caller wrote is kept, so it
|
|
178
|
+
* stays in the namespace of a lexical fallback) of a path crawled beneath `within`, asked only once
|
|
179
|
+
* the chain is read and local. Read off the `node:fs` default object per call, so a spy installed
|
|
180
|
+
* after import still sees it.
|
|
181
|
+
*
|
|
182
|
+
* @throws as {@link realpathNativeSync}
|
|
183
|
+
*/
|
|
184
|
+
export async function followedRealpath(path, within) {
|
|
185
|
+
requireSomethingAt(path, within, 'realpath');
|
|
186
|
+
return await promisify(nodeFs.realpath)(path);
|
|
187
|
+
}
|
|
188
|
+
/** The synchronous {@link followedRealpath} (`fs.realpathSync`, the JS walk). */
|
|
189
|
+
export function followedRealpathSync(path, within) {
|
|
190
|
+
requireSomethingAt(path, within, 'realpath');
|
|
191
|
+
return nodeFs.realpathSync(path);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* The kernel's `stat` of the operator's OWN path (an edge: the base a crawl was asked to start
|
|
195
|
+
* from), raw: the caller classifies its error.
|
|
196
|
+
*/
|
|
197
|
+
export function edgeStatSync(path) {
|
|
198
|
+
return nodeFs.statSync(path);
|
|
199
|
+
}
|
|
200
|
+
/** {@link edgeStatSync} with `bigint` fields — the `dev`/`ino` an entry identity is built from. */
|
|
201
|
+
export function edgeBigintStatSync(path) {
|
|
202
|
+
return nodeFs.statSync(path, { bigint: true });
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* What `path` (crawled beneath `within`) resolves to, its links followed — `undefined` when it
|
|
206
|
+
* resolves to nothing: absent, a dangling link, a cycle, or a chain the kernel itself calls a loop.
|
|
207
|
+
*
|
|
208
|
+
* @throws LinkChainRefusedError for a chain VAT will not hand the kernel; the raw errno of any probe
|
|
209
|
+
* the OS refused (never folded into "absent")
|
|
210
|
+
*/
|
|
211
|
+
export function followedStatSync(path, within) {
|
|
212
|
+
if (assertLocalChain(path, within, HOST_PATH_FLAVOR) === 'nothing')
|
|
213
|
+
return undefined;
|
|
214
|
+
try {
|
|
215
|
+
return nodeFs.statSync(path);
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
if (isUnresolvableLinkError(error))
|
|
219
|
+
return undefined;
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/** The asynchronous {@link followedStatSync}. */
|
|
224
|
+
export async function followedStat(path, within) {
|
|
225
|
+
if (assertLocalChain(path, within, HOST_PATH_FLAVOR) === 'nothing')
|
|
226
|
+
return undefined;
|
|
227
|
+
try {
|
|
228
|
+
return await fsPromises.stat(path);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
if (isUnresolvableLinkError(error))
|
|
232
|
+
return undefined;
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* {@link followedStatSync} for a caller that cannot go on without an answer: a path that resolves
|
|
238
|
+
* to nothing is the `ENOENT` (or, for a cycle, `ELOOP`) the kernel's own `stat` would have thrown,
|
|
239
|
+
* for the caller's classifier.
|
|
240
|
+
*/
|
|
241
|
+
export function requireFollowedStatSync(path, within) {
|
|
242
|
+
requireSomethingAt(path, within, 'stat');
|
|
243
|
+
return nodeFs.statSync(path);
|
|
244
|
+
}
|
|
245
|
+
/** The asynchronous {@link requireFollowedStatSync}. */
|
|
246
|
+
export async function requireFollowedStat(path, within) {
|
|
247
|
+
requireSomethingAt(path, within, 'stat');
|
|
248
|
+
return await fsPromises.stat(path);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The `type` a link made to copy the link at `path` (crawled beneath `within`) is created with —
|
|
252
|
+
* `'dir'` when its chain, read by text, ends at a directory; `'file'` otherwise, a dangle or a cycle
|
|
253
|
+
* included (what Node itself picks for a target it cannot `stat`). Read by `lstat` and `readlink`
|
|
254
|
+
* alone: a `symlink` with no type has Node on Windows ask the KERNEL to follow the target to pick
|
|
255
|
+
* one, and a link text no walk read must never reach the kernel.
|
|
256
|
+
*
|
|
257
|
+
* @throws LinkChainRefusedError for a chain through a network or device path, or one that never
|
|
258
|
+
* ends; the raw errno of an `lstat`/`readlink` the OS refused
|
|
259
|
+
*/
|
|
260
|
+
export function linkCopyType(path, within) {
|
|
261
|
+
const walked = walkWithin(path, within, HOST_PATH_FLAVOR);
|
|
262
|
+
// The link IS the tree's root: its chain is walked whole, from its own root, as any link target is.
|
|
263
|
+
const chain = walked.kind === 'edge' ? walkWhole(toForwardSlash(safePath.resolve(path)), HOST_PATH_FLAVOR) : walked;
|
|
264
|
+
if (chain.kind === 'refused')
|
|
265
|
+
throw new LinkChainRefusedError(path, chain.refusal);
|
|
266
|
+
return chain.kind === 'resolved' && chain.final === 'directory' ? 'dir' : 'file';
|
|
267
|
+
}
|
|
268
|
+
function kindOf(stats) {
|
|
269
|
+
if (stats === undefined)
|
|
270
|
+
return 'absent';
|
|
271
|
+
if (stats.isDirectory())
|
|
272
|
+
return 'directory';
|
|
273
|
+
return stats.isFile() ? 'file' : 'other';
|
|
274
|
+
}
|
|
275
|
+
/** What `path` beneath `within` resolves to ({@link followedStatSync}): `absent` for nothing. */
|
|
276
|
+
export function followedKindSync(path, within) {
|
|
277
|
+
return kindOf(followedStatSync(path, within));
|
|
278
|
+
}
|
|
279
|
+
/** The asynchronous {@link followedKindSync}. */
|
|
280
|
+
export async function followedKind(path, within) {
|
|
281
|
+
return kindOf(await followedStat(path, within));
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=link-following.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-following.js","sourceRoot":"","sources":["../src/link-following.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* its stderr without a word); and the miss surfaced one review round later as
|
|
18
18
|
* a HIGH. A required field is what makes `tsc` enumerate the callers.
|
|
19
19
|
*/
|
|
20
|
+
import { type FsSide } from './errors/fs-fault.js';
|
|
20
21
|
import { VatError } from './errors/vat-error.js';
|
|
21
22
|
import { type DirectoryRefusal } from './fs-utils.js';
|
|
22
23
|
/**
|
|
@@ -38,6 +39,15 @@ export interface RefuseListingContext {
|
|
|
38
39
|
* differs per caller and is why the crawler cannot supply it.
|
|
39
40
|
*/
|
|
40
41
|
remedy: string;
|
|
42
|
+
/**
|
|
43
|
+
* Which side of the calling verb the listed directories are on — an input it reads
|
|
44
|
+
* (`source`), an output it wrote (`destination`), or VAT's own scratch (`environment`).
|
|
45
|
+
* Required: the refusal table decides the refusal by it, and only the caller knows which
|
|
46
|
+
* tree it is listing. In a CRAWL, a directory that is (or lies in, or holds) one of the
|
|
47
|
+
* crawl's declared `outputs` is the destination's whatever this says: the crawl's
|
|
48
|
+
* `outputs` are the one declaration of what the verb writes ({@link settleCrawlRefusal}).
|
|
49
|
+
*/
|
|
50
|
+
side: FsSide;
|
|
41
51
|
}
|
|
42
52
|
/**
|
|
43
53
|
* The caller's decision about a directory the listing cannot open.
|
|
@@ -64,6 +74,13 @@ export type UnreadablePolicy = {
|
|
|
64
74
|
/**
|
|
65
75
|
* Thrown by a listing under `{ refuse }` when a directory refused to be
|
|
66
76
|
* listed. The message is the adopter's sentence — see {@link refusedListingMessage}.
|
|
77
|
+
*
|
|
78
|
+
* Coded `FS_FAULT` and carrying the classified fault as its `cause` (side from
|
|
79
|
+
* the caller's context, class from the errno), so the refusal is the schema
|
|
80
|
+
* table's for that side and class — never a code of its own. A refused listing
|
|
81
|
+
* is a filesystem call's refusal by construction, so every errno has a class:
|
|
82
|
+
* one the table does not list is the unlisted class, as {@link fsFaultOf}
|
|
83
|
+
* answers for the error it came from.
|
|
67
84
|
*/
|
|
68
85
|
export declare class DirectoryListingRefusedError extends VatError {
|
|
69
86
|
readonly refusal: DirectoryRefusal;
|
|
@@ -94,6 +111,33 @@ export declare function refusedListingMessage(refusal: DirectoryRefusal, context
|
|
|
94
111
|
* @param refusal - The directory that could not be listed
|
|
95
112
|
*/
|
|
96
113
|
export declare function settleRefusal(policy: UnreadablePolicy, refusal: DirectoryRefusal): void;
|
|
114
|
+
/**
|
|
115
|
+
* Whether `path` is on what a crawl's verb WRITES: one of its `outputs`, inside one, or holding
|
|
116
|
+
* one (the crawl base of a project a build writes into holds its output). THE one place a
|
|
117
|
+
* crawl derives a side from its `outputs` — the base `stat`, a refused listing at the base or
|
|
118
|
+
* beneath it, on every route. Paths are compared canonically (`canonicalPath`: real path, one
|
|
119
|
+
* spelling), so a directory git reports under its real path (`/private/var` for `/var`) or
|
|
120
|
+
* another drive-letter case is still the output.
|
|
121
|
+
*
|
|
122
|
+
* @param path - A directory the crawl met
|
|
123
|
+
* @param outputs - The trees the calling verb writes, `[]` for a verb that only reads
|
|
124
|
+
*/
|
|
125
|
+
export declare function onCrawlOutput(path: string, outputs: readonly string[]): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* The side of a path a crawl met: the destination's when it is on the verb's `outputs`
|
|
128
|
+
* ({@link onCrawlOutput}), else the side of the tree the verb reads — the policy's own
|
|
129
|
+
* (`source` when the policy degrades).
|
|
130
|
+
*/
|
|
131
|
+
export declare function crawlSideOf(path: string, outputs: readonly string[], policy: UnreadablePolicy): FsSide;
|
|
132
|
+
/**
|
|
133
|
+
* Settle one refusal a CRAWL met: a refused directory that is on the crawl's `outputs`
|
|
134
|
+
* ({@link onCrawlOutput}) is the destination's; any other keeps the policy's own side.
|
|
135
|
+
*
|
|
136
|
+
* @param policy - The caller's decision
|
|
137
|
+
* @param refusal - The directory that could not be listed
|
|
138
|
+
* @param outputs - The trees the calling verb writes (the crawl's one declaration of them)
|
|
139
|
+
*/
|
|
140
|
+
export declare function settleCrawlRefusal(policy: UnreadablePolicy, refusal: DirectoryRefusal, outputs: readonly string[]): void;
|
|
97
141
|
/**
|
|
98
142
|
* Refuse an omitted policy up front, by name, before any directory is listed.
|
|
99
143
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listing-refusal.d.ts","sourceRoot":"","sources":["../src/listing-refusal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"listing-refusal.d.ts","sourceRoot":"","sources":["../src/listing-refusal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAA+B,KAAK,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,KAAK,gBAAgB,EAA0B,MAAM,eAAe,CAAC;AAK9E;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GACzC;IAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;CAAE,CAAC;AAE9D;;;;;;;;;;GAUG;AACH,qBAAa,4BAA6B,SAAQ,QAAQ;IACxD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBAEvB,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,oBAAoB;CAIrE;AAgCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,oBAAoB,GAAG,MAAM,CAOtG;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAGvF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAO/E;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAGtG;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAGxH;AAaD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,gBAAgB,CAO7H"}
|
package/dist/listing-refusal.js
CHANGED
|
@@ -17,20 +17,43 @@
|
|
|
17
17
|
* its stderr without a word); and the miss surfaced one review round later as
|
|
18
18
|
* a HIGH. A required field is what makes `tsc` enumerate the callers.
|
|
19
19
|
*/
|
|
20
|
+
import { filesystemErrnoClass, fsFaultOf } from './errors/errno-table.js';
|
|
21
|
+
import { FS_FAULT_CODE, FsFaultError } from './errors/fs-fault.js';
|
|
20
22
|
import { VatError } from './errors/vat-error.js';
|
|
21
23
|
import { transientRefusalClause } from './fs-utils.js';
|
|
24
|
+
import { canonicalPath } from './path-containment.js';
|
|
25
|
+
import { relativeEscapesRoot } from './path-core.js';
|
|
22
26
|
import { safePath, toForwardSlash } from './path-utils.js';
|
|
23
27
|
/**
|
|
24
28
|
* Thrown by a listing under `{ refuse }` when a directory refused to be
|
|
25
29
|
* listed. The message is the adopter's sentence — see {@link refusedListingMessage}.
|
|
30
|
+
*
|
|
31
|
+
* Coded `FS_FAULT` and carrying the classified fault as its `cause` (side from
|
|
32
|
+
* the caller's context, class from the errno), so the refusal is the schema
|
|
33
|
+
* table's for that side and class — never a code of its own. A refused listing
|
|
34
|
+
* is a filesystem call's refusal by construction, so every errno has a class:
|
|
35
|
+
* one the table does not list is the unlisted class, as {@link fsFaultOf}
|
|
36
|
+
* answers for the error it came from.
|
|
26
37
|
*/
|
|
27
38
|
export class DirectoryListingRefusedError extends VatError {
|
|
28
39
|
refusal;
|
|
29
40
|
constructor(refusal, context) {
|
|
30
|
-
super(
|
|
41
|
+
super(FS_FAULT_CODE, refusedListingMessage(refusal, context), { cause: classifiedListingFault(refusal, context.side) });
|
|
31
42
|
this.refusal = refusal;
|
|
32
43
|
}
|
|
33
44
|
}
|
|
45
|
+
/** The classified fault a refused listing stands for. */
|
|
46
|
+
function classifiedListingFault(refusal, side) {
|
|
47
|
+
return new FsFaultError({
|
|
48
|
+
side,
|
|
49
|
+
faultClass: filesystemErrnoClass(refusal.code),
|
|
50
|
+
errno: refusal.code,
|
|
51
|
+
path: refusal.directory,
|
|
52
|
+
origin: 'content',
|
|
53
|
+
action: 'list a directory',
|
|
54
|
+
cause: undefined,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
34
57
|
/**
|
|
35
58
|
* What a refused listing costs, said so it is true on BOTH routes.
|
|
36
59
|
*
|
|
@@ -82,6 +105,60 @@ export function settleRefusal(policy, refusal) {
|
|
|
82
105
|
throw new DirectoryListingRefusedError(refusal, policy.refuse);
|
|
83
106
|
policy.degrade(refusal);
|
|
84
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Whether `path` is on what a crawl's verb WRITES: one of its `outputs`, inside one, or holding
|
|
110
|
+
* one (the crawl base of a project a build writes into holds its output). THE one place a
|
|
111
|
+
* crawl derives a side from its `outputs` — the base `stat`, a refused listing at the base or
|
|
112
|
+
* beneath it, on every route. Paths are compared canonically (`canonicalPath`: real path, one
|
|
113
|
+
* spelling), so a directory git reports under its real path (`/private/var` for `/var`) or
|
|
114
|
+
* another drive-letter case is still the output.
|
|
115
|
+
*
|
|
116
|
+
* @param path - A directory the crawl met
|
|
117
|
+
* @param outputs - The trees the calling verb writes, `[]` for a verb that only reads
|
|
118
|
+
*/
|
|
119
|
+
export function onCrawlOutput(path, outputs) {
|
|
120
|
+
if (outputs.length === 0)
|
|
121
|
+
return false;
|
|
122
|
+
const at = canonicalOrSpelled(path);
|
|
123
|
+
return outputs.some((output) => {
|
|
124
|
+
const out = canonicalOrSpelled(output);
|
|
125
|
+
return !relativeEscapesRoot(safePath.relative(out, at)) || !relativeEscapesRoot(safePath.relative(at, out));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The side of a path a crawl met: the destination's when it is on the verb's `outputs`
|
|
130
|
+
* ({@link onCrawlOutput}), else the side of the tree the verb reads — the policy's own
|
|
131
|
+
* (`source` when the policy degrades).
|
|
132
|
+
*/
|
|
133
|
+
export function crawlSideOf(path, outputs, policy) {
|
|
134
|
+
if (onCrawlOutput(path, outputs))
|
|
135
|
+
return 'destination';
|
|
136
|
+
return 'refuse' in policy ? policy.refuse.side : 'source';
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Settle one refusal a CRAWL met: a refused directory that is on the crawl's `outputs`
|
|
140
|
+
* ({@link onCrawlOutput}) is the destination's; any other keeps the policy's own side.
|
|
141
|
+
*
|
|
142
|
+
* @param policy - The caller's decision
|
|
143
|
+
* @param refusal - The directory that could not be listed
|
|
144
|
+
* @param outputs - The trees the calling verb writes (the crawl's one declaration of them)
|
|
145
|
+
*/
|
|
146
|
+
export function settleCrawlRefusal(policy, refusal, outputs) {
|
|
147
|
+
const onOutput = 'refuse' in policy && onCrawlOutput(refusal.directory, outputs);
|
|
148
|
+
settleRefusal(onOutput ? { refuse: { ...policy.refuse, side: 'destination' } } : policy, refusal);
|
|
149
|
+
}
|
|
150
|
+
function canonicalOrSpelled(path) {
|
|
151
|
+
try {
|
|
152
|
+
// The refused directory and the verb's outputs are spelled by the verb: each is its own edge.
|
|
153
|
+
return toForwardSlash(canonicalPath(path, path));
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
// A directory that refused its listing may refuse resolution too: its spelling is all there is.
|
|
157
|
+
if (fsFaultOf(error) === undefined)
|
|
158
|
+
throw error;
|
|
159
|
+
return toForwardSlash(safePath.resolve(path));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
85
162
|
/**
|
|
86
163
|
* Refuse an omitted policy up front, by name, before any directory is listed.
|
|
87
164
|
*
|
|
@@ -98,7 +175,7 @@ export function settleRefusal(policy, refusal) {
|
|
|
98
175
|
*/
|
|
99
176
|
export function requireUnreadablePolicy(policy, api) {
|
|
100
177
|
if (policy === undefined) {
|
|
101
|
-
throw new TypeError(`${api}: \`unreadable\` is required — pass { refuse: { root, remedy } } to stop on a directory the listing cannot open, ` +
|
|
178
|
+
throw new TypeError(`${api}: \`unreadable\` is required — pass { refuse: { root, remedy, side } } to stop on a directory the listing cannot open, ` +
|
|
102
179
|
'or { degrade: (refusal) => … } to keep going and report the gap yourself.');
|
|
103
180
|
}
|
|
104
181
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"listing-refusal.js","sourceRoot":"","sources":["../src/listing-refusal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAyB,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"listing-refusal.js","sourceRoot":"","sources":["../src/listing-refusal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One path segment as the strictest volume VAT writes onto compares it. Pure.
|
|
3
|
+
*
|
|
4
|
+
* Two segments with one fold are ONE entry on some volume, so a set of names holding both lands
|
|
5
|
+
* differently depending on where it is written — an archive's entry list and a plugin's `files:`
|
|
6
|
+
* destinations are both judged by it.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* A path segment as a filesystem that folds names compares it: the stream suffix and trailing dots
|
|
10
|
+
* and spaces cut (NTFS), decomposed, then case-folded UP and then down — NTFS compares upper-cased
|
|
11
|
+
* names, so `ß` and `SS` are one name, and a dotless `ı` (U+0131) is `I` there, which lower-casing
|
|
12
|
+
* alone leaves apart from `i`. The cut is made on every host, so a POSIX directory really named
|
|
13
|
+
* `skills:notes` compares as `skills` too.
|
|
14
|
+
*
|
|
15
|
+
* STRICTER than any one volume, on purpose. The decomposition is by compatibility (NFKD), where
|
|
16
|
+
* the volumes that normalise (APFS, HFS+) do so canonically: `x².md` and `x2.md` fold together
|
|
17
|
+
* here and are two names everywhere. And the stream cut makes `10:00.log` and `10:30.log` one
|
|
18
|
+
* name, which they are only on NTFS. A caller that refuses on equal folds over-refuses those, and
|
|
19
|
+
* says which rule matched.
|
|
20
|
+
*
|
|
21
|
+
* @param segment - One path segment, never a path
|
|
22
|
+
* @returns The segment's fold: equal for two names that are one entry on some volume
|
|
23
|
+
*/
|
|
24
|
+
export declare function foldedSegment(segment: string): string;
|
|
25
|
+
//# sourceMappingURL=name-fold.d.ts.map
|