@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,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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { lstatSync, readlinkSync } from 'node:fs';
|
|
9
|
+
import { isPathAbsentError, LINK_CHAIN_REFUSED_CODE } from '../errors/errno-table.js';
|
|
10
|
+
import { isVatError, VatError } from '../errors/vat-error.js';
|
|
11
|
+
import { safePath, toForwardSlash } from '../path-core.js';
|
|
12
|
+
import { MAX_LINK_HOPS, walkLinkChain } from './link-chain.js';
|
|
13
|
+
/**
|
|
14
|
+
* What stands at `path`, a link there not followed. A probe the OS refuses is thrown raw, for the
|
|
15
|
+
* caller to classify.
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ "Not followed" is `lstat`'s word, and on Windows libuv keeps it only for what it calls a
|
|
18
|
+
* symbolic link: a junction to a volume (`\??\Volume{…}\`) and a reparse point of another tag
|
|
19
|
+
* (cloud files, dedup, WCI) are retried as a link-FOLLOWING `stat` and reported as the directory
|
|
20
|
+
* or file they lead to. Their targets are local — the kernel refuses a remote mount point — so no
|
|
21
|
+
* credential leaves the host, but the walk reads such a place as a plain directory where the
|
|
22
|
+
* kernel reparses it.
|
|
23
|
+
*/
|
|
24
|
+
export function hostPlaceKind(path) {
|
|
25
|
+
try {
|
|
26
|
+
const stats = lstatSync(path);
|
|
27
|
+
if (stats.isSymbolicLink())
|
|
28
|
+
return 'link';
|
|
29
|
+
return stats.isDirectory() ? 'directory' : 'file';
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (isPathAbsentError(error))
|
|
33
|
+
return 'absent';
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** The probe of this host's filesystem: `lstat` and `readlink` alone. */
|
|
38
|
+
const HOST_PROBE = { kindAt: hostPlaceKind, textAt: (path) => readlinkSync(path) };
|
|
39
|
+
/**
|
|
40
|
+
* Where `path` leads on this host's filesystem, under `rule` ({@link walkLinkChain}).
|
|
41
|
+
*
|
|
42
|
+
* @param path - Absolute, or relative to cwd; either separator
|
|
43
|
+
* @param rule - The flavor (bound from the host at the edge), the tree, and what the chain may do outside it
|
|
44
|
+
* @throws The raw errno of an `lstat` or `readlink` the OS refused, for the caller to classify
|
|
45
|
+
*/
|
|
46
|
+
export function walkHostChain(path, rule) {
|
|
47
|
+
return walkLinkChain(toForwardSlash(safePath.resolve(path)), rule, HOST_PROBE);
|
|
48
|
+
}
|
|
49
|
+
/** How each refusal reads, after "the symbolic link <link> ". */
|
|
50
|
+
function refusalClause(refusal) {
|
|
51
|
+
switch (refusal.why) {
|
|
52
|
+
case 'network':
|
|
53
|
+
return `leads to ${refusal.text}, a network or device path, which VAT never asks the OS to follow (a Windows host would offer the operator's credentials to it)`;
|
|
54
|
+
case 'hop-limit':
|
|
55
|
+
return `starts a chain of more than ${MAX_LINK_HOPS.toString()} symbolic links that is not a cycle, so where it ends was never read`;
|
|
56
|
+
case 'unportable':
|
|
57
|
+
return `has a text no kernel resolves as written (${JSON.stringify(refusal.text)}: a NUL, or a name ending in a dot or a space)`;
|
|
58
|
+
case 'absolute':
|
|
59
|
+
case 'drive-relative':
|
|
60
|
+
case 'climbs-out':
|
|
61
|
+
return `leads outside the tree it is in (${refusal.text})`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A path VAT would not resolve: a link on the way to it leads to a network path, or the chain is
|
|
66
|
+
* longer than any kernel follows. A read lane records it as that one target's fault
|
|
67
|
+
* (`linkResolutionFault`); a lane that stops classifies it on its own side, like any refusal
|
|
68
|
+
* (`classifyFsFault`), so a destination's refused chain reads as the destination's.
|
|
69
|
+
*/
|
|
70
|
+
export class LinkChainRefusedError extends VatError {
|
|
71
|
+
/** The path whose resolution was asked. */
|
|
72
|
+
path;
|
|
73
|
+
/** Why, with the link whose text decided it. */
|
|
74
|
+
refusal;
|
|
75
|
+
constructor(path, refusal) {
|
|
76
|
+
super(LINK_CHAIN_REFUSED_CODE, `Refusing to resolve ${path}: the symbolic link ${refusal.link} ${refusalClause(refusal)} — replace the link with the files it names.`);
|
|
77
|
+
this.path = path;
|
|
78
|
+
this.refusal = refusal;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Whether `error` is a {@link LinkChainRefusedError}, by its code (a `dist` copy never `instanceof`-matches a `src` one). */
|
|
82
|
+
export function isLinkChainRefusedError(error) {
|
|
83
|
+
return isVatError(error, LINK_CHAIN_REFUSED_CODE) && 'refusal' in error;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=host-chain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-chain.js","sourceRoot":"","sources":["../../src/tree-change/host-chain.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAElD,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACtF,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE3D,OAAO,EAAsE,aAAa,EAAkB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEnJ;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,KAAK,CAAC,cAAc,EAAE;YAAE,OAAO,MAAM,CAAC;QAC1C,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC;IACpD,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,iBAAiB,CAAC,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC;QAC9C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,GAAe,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;AAE/F;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,IAAe;IACzD,OAAO,aAAa,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;AACjF,CAAC;AAED,iEAAiE;AACjE,SAAS,aAAa,CAAC,OAAqB;IAC1C,QAAQ,OAAO,CAAC,GAAG,EAAE,CAAC;QACpB,KAAK,SAAS;YACZ,OAAO,YAAY,OAAO,CAAC,IAAI,iIAAiI,CAAC;QACnK,KAAK,WAAW;YACd,OAAO,+BAA+B,aAAa,CAAC,QAAQ,EAAE,sEAAsE,CAAC;QACvI,KAAK,YAAY;YACf,OAAO,6CAA6C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,gDAAgD,CAAC;QACnI,KAAK,UAAU,CAAC;QAChB,KAAK,gBAAgB,CAAC;QACtB,KAAK,YAAY;YACf,OAAO,oCAAoC,OAAO,CAAC,IAAI,GAAG,CAAC;IAC/D,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,qBAAsB,SAAQ,QAAQ;IACjD,2CAA2C;IAClC,IAAI,CAAS;IACtB,gDAAgD;IACvC,OAAO,CAAe;IAC/B,YAAY,IAAY,EAAE,OAAqB;QAC7C,KAAK,CAAC,uBAAuB,EAAE,uBAAuB,IAAI,uBAAuB,OAAO,CAAC,IAAI,IAAI,aAAa,CAAC,OAAO,CAAC,8CAA8C,CAAC,CAAC;QACvK,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED,8HAA8H;AAC9H,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,OAAO,UAAU,CAAC,KAAK,EAAE,uBAAuB,CAAC,IAAI,SAAS,IAAI,KAAK,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deciding sameness and containment from entry identities already read off the filesystem.
|
|
3
|
+
* Pure: the examination is handed in, so the decision is the same whoever asked the OS.
|
|
4
|
+
*/
|
|
5
|
+
/** One way an entry answers to being identified: a filesystem id, or a folded real path when the filesystem reports none. */
|
|
6
|
+
export type Identity = {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
} | {
|
|
9
|
+
readonly foldedRealPath: string;
|
|
10
|
+
};
|
|
11
|
+
/** Whether two entries are one. `unknown` means it could not be decided: treat it as `same` when deciding whether to delete. */
|
|
12
|
+
export type EntrySameness = 'same' | 'different' | 'unknown';
|
|
13
|
+
/** Whether an entry lies strictly under another. `unknown` means it could not be decided. */
|
|
14
|
+
export type EntryContainment = 'inside' | 'outside' | 'unknown';
|
|
15
|
+
/** What one round of identity questions needs: each entry's identities, or `undefined` when the OS refuses to examine it. */
|
|
16
|
+
export type Examiner = (entry: string) => readonly Identity[] | undefined;
|
|
17
|
+
export declare function sameBy(examine: Examiner, a: string, b: string): EntrySameness;
|
|
18
|
+
/**
|
|
19
|
+
* Where an entry physically is: every spelling of it with no link left in its directories (its own
|
|
20
|
+
* place, and — for a link — its target's), or `undefined` when the OS refuses to resolve it.
|
|
21
|
+
*/
|
|
22
|
+
export type PhysicalSpellings = (entry: string) => readonly string[] | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Whether `child` lies strictly under `ancestor`: some directory above it IS the ancestor. Walked
|
|
25
|
+
* along the spelling given AND along every physical spelling of the child — a directory of the given
|
|
26
|
+
* spelling that is a link to somewhere BELOW the ancestor is neither the ancestor nor a link to it,
|
|
27
|
+
* and the directories between its target and the ancestor are only on the physical way up.
|
|
28
|
+
*/
|
|
29
|
+
export declare function insideBy(examine: Examiner, child: string, ancestor: string, physical: PhysicalSpellings): EntryContainment;
|
|
30
|
+
/**
|
|
31
|
+
* The identity a `stat` answers with: the filesystem's own id when it reports both a device and an
|
|
32
|
+
* inode, else the fold — which is only computed then, since it costs a `realpath`.
|
|
33
|
+
*
|
|
34
|
+
* @param stats - The `dev` and `ino` of a `bigint` stat
|
|
35
|
+
* @param fold - The folded real path, for a filesystem that reports no inode
|
|
36
|
+
*/
|
|
37
|
+
export declare function identityOf(stats: {
|
|
38
|
+
readonly dev: bigint;
|
|
39
|
+
readonly ino: bigint;
|
|
40
|
+
}, fold: () => Identity): Identity;
|
|
41
|
+
/** Sameness and containment over one memo, and the one throwing examination of an entry they share. */
|
|
42
|
+
export interface IdentityOracle {
|
|
43
|
+
/** The entry's identities, read once: a refusal is remembered and thrown again. */
|
|
44
|
+
readonly identities: (entry: string) => readonly Identity[];
|
|
45
|
+
/** Whether the OS refuses to examine `entry` (the remembered answer of {@link IdentityOracle.identities}). */
|
|
46
|
+
readonly refuses: (entry: string) => boolean;
|
|
47
|
+
readonly sameEntry: (a: string, b: string) => EntrySameness;
|
|
48
|
+
readonly isInside: (child: string, ancestor: string) => EntryContainment;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Identity questions that examine each entry ONCE: every answer is built from the same
|
|
52
|
+
* observation of each entry. One plan's decisions asked the filesystem again and again,
|
|
53
|
+
* so a refusal seen by one (subsumption: `unknown`) and not by the next (the overlap
|
|
54
|
+
* check: `inside`) made the planner refuse its own plan.
|
|
55
|
+
*
|
|
56
|
+
* @param read - The examination: an entry's identities, or a thrown classified fault when the OS
|
|
57
|
+
* refuses. Anything else it throws is a defect and is never remembered as a refusal.
|
|
58
|
+
* @param physical - Where an entry physically is, for containment (see {@link insideBy})
|
|
59
|
+
*/
|
|
60
|
+
export declare function identityOracleOver(read: (entry: string) => readonly Identity[], physical: PhysicalSpellings): IdentityOracle;
|
|
61
|
+
//# sourceMappingURL=identity-compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-compare.d.ts","sourceRoot":"","sources":["../../src/tree-change/identity-compare.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,6HAA6H;AAC7H,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AAErF,gIAAgI;AAChI,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7D,6FAA6F;AAC7F,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAkBhE,6HAA6H;AAC7H,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,SAAS,QAAQ,EAAE,GAAG,SAAS,CAAC;AAE1E,wBAAgB,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,aAAa,CAI7E;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;AAgBjF;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,gBAAgB,CAO1H;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,EAAE,MAAM,QAAQ,GAAG,QAAQ,CAEhH;AAED,uGAAuG;AACvG,MAAM,WAAW,cAAc;IAC7B,mFAAmF;IACnF,QAAQ,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,SAAS,QAAQ,EAAE,CAAC;IAC5D,8GAA8G;IAC9G,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,aAAa,CAAC;IAC5D,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,gBAAgB,CAAC;CAC1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,SAAS,QAAQ,EAAE,EAAE,QAAQ,EAAE,iBAAiB,GAAG,cAAc,CA8B5H"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deciding sameness and containment from entry identities already read off the filesystem.
|
|
3
|
+
* Pure: the examination is handed in, so the decision is the same whoever asked the OS.
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { isFsFaultError } from '../errors/fs-fault.js';
|
|
7
|
+
function hasFold(identities) {
|
|
8
|
+
return identities.some((identity) => 'foldedRealPath' in identity);
|
|
9
|
+
}
|
|
10
|
+
/** One entry when both are filesystem ids and equal, or both are folds and equal; an id never equals a fold. */
|
|
11
|
+
function sameIdentity(left, right) {
|
|
12
|
+
if ('id' in left && 'id' in right)
|
|
13
|
+
return left.id === right.id;
|
|
14
|
+
return 'foldedRealPath' in left && 'foldedRealPath' in right && left.foldedRealPath === right.foldedRealPath;
|
|
15
|
+
}
|
|
16
|
+
function compare(left, right) {
|
|
17
|
+
if (left.length === 0 || right.length === 0)
|
|
18
|
+
return 'different';
|
|
19
|
+
if (left.some((l) => right.some((r) => sameIdentity(l, r))))
|
|
20
|
+
return 'same';
|
|
21
|
+
return hasFold(left) || hasFold(right) ? 'unknown' : 'different';
|
|
22
|
+
}
|
|
23
|
+
export function sameBy(examine, a, b) {
|
|
24
|
+
const left = examine(a);
|
|
25
|
+
const right = examine(b);
|
|
26
|
+
return left === undefined || right === undefined ? 'unknown' : compare(left, right);
|
|
27
|
+
}
|
|
28
|
+
/** Whether some directory above `start`, spelled as given, is the wanted entry. */
|
|
29
|
+
function ancestorAbove(examine, start, wanted) {
|
|
30
|
+
let undecided = false;
|
|
31
|
+
let current = start;
|
|
32
|
+
for (let parent = path.dirname(current); parent !== current; parent = path.dirname(current)) {
|
|
33
|
+
const found = examine(parent);
|
|
34
|
+
const verdict = found === undefined ? 'unknown' : compare(found, wanted);
|
|
35
|
+
if (verdict === 'same')
|
|
36
|
+
return 'inside';
|
|
37
|
+
if (verdict === 'unknown')
|
|
38
|
+
undecided = true;
|
|
39
|
+
current = parent;
|
|
40
|
+
}
|
|
41
|
+
return undecided ? 'unknown' : 'outside';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Whether `child` lies strictly under `ancestor`: some directory above it IS the ancestor. Walked
|
|
45
|
+
* along the spelling given AND along every physical spelling of the child — a directory of the given
|
|
46
|
+
* spelling that is a link to somewhere BELOW the ancestor is neither the ancestor nor a link to it,
|
|
47
|
+
* and the directories between its target and the ancestor are only on the physical way up.
|
|
48
|
+
*/
|
|
49
|
+
export function insideBy(examine, child, ancestor, physical) {
|
|
50
|
+
const wanted = examine(ancestor);
|
|
51
|
+
if (wanted === undefined)
|
|
52
|
+
return 'unknown';
|
|
53
|
+
const resolved = physical(child);
|
|
54
|
+
const verdicts = new Set([...new Set([child, ...(resolved ?? [])])].map((start) => ancestorAbove(examine, start, wanted)));
|
|
55
|
+
if (verdicts.has('inside'))
|
|
56
|
+
return 'inside';
|
|
57
|
+
return resolved === undefined || verdicts.has('unknown') ? 'unknown' : 'outside';
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The identity a `stat` answers with: the filesystem's own id when it reports both a device and an
|
|
61
|
+
* inode, else the fold — which is only computed then, since it costs a `realpath`.
|
|
62
|
+
*
|
|
63
|
+
* @param stats - The `dev` and `ino` of a `bigint` stat
|
|
64
|
+
* @param fold - The folded real path, for a filesystem that reports no inode
|
|
65
|
+
*/
|
|
66
|
+
export function identityOf(stats, fold) {
|
|
67
|
+
return stats.dev !== 0n && stats.ino !== 0n ? { id: `${stats.dev}:${stats.ino}` } : fold();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Identity questions that examine each entry ONCE: every answer is built from the same
|
|
71
|
+
* observation of each entry. One plan's decisions asked the filesystem again and again,
|
|
72
|
+
* so a refusal seen by one (subsumption: `unknown`) and not by the next (the overlap
|
|
73
|
+
* check: `inside`) made the planner refuse its own plan.
|
|
74
|
+
*
|
|
75
|
+
* @param read - The examination: an entry's identities, or a thrown classified fault when the OS
|
|
76
|
+
* refuses. Anything else it throws is a defect and is never remembered as a refusal.
|
|
77
|
+
* @param physical - Where an entry physically is, for containment (see {@link insideBy})
|
|
78
|
+
*/
|
|
79
|
+
export function identityOracleOver(read, physical) {
|
|
80
|
+
const memo = new Map();
|
|
81
|
+
const identities = (entry) => {
|
|
82
|
+
let seen = memo.get(entry);
|
|
83
|
+
if (seen === undefined) {
|
|
84
|
+
try {
|
|
85
|
+
seen = { ok: read(entry) };
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (!isFsFaultError(error))
|
|
89
|
+
throw error;
|
|
90
|
+
seen = { refused: error };
|
|
91
|
+
}
|
|
92
|
+
memo.set(entry, seen);
|
|
93
|
+
}
|
|
94
|
+
if ('refused' in seen)
|
|
95
|
+
throw seen.refused;
|
|
96
|
+
return seen.ok;
|
|
97
|
+
};
|
|
98
|
+
const undecided = (entry) => {
|
|
99
|
+
try {
|
|
100
|
+
return identities(entry);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (isFsFaultError(error))
|
|
104
|
+
return undefined;
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
identities,
|
|
110
|
+
refuses: (entry) => undecided(entry) === undefined,
|
|
111
|
+
sameEntry: (a, b) => sameBy(undecided, a, b),
|
|
112
|
+
isInside: (child, ancestor) => insideBy(undecided, child, ancestor, physical),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=identity-compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-compare.js","sourceRoot":"","sources":["../../src/tree-change/identity-compare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry identity: are two names ONE entry on disk?
|
|
3
|
+
*
|
|
4
|
+
* Two changes to a tree can alias without sharing a spelling — `plugins/Old` and
|
|
5
|
+
* `plugins/old` on APFS or NTFS, a link and its target, two links to one target.
|
|
6
|
+
* Comparing names cannot see it, and acting on a wrong "different" deletes what the
|
|
7
|
+
* user just installed. So identity is asked of the filesystem:
|
|
8
|
+
*
|
|
9
|
+
* - `dev` and `ino` both nonzero → the id `${dev}:${ino}`, from a `bigint` `lstat` of
|
|
10
|
+
* the entry, plus a `stat` of a link's target. A link answers to BOTH: removing it
|
|
11
|
+
* removes only the link, but it aliases its target.
|
|
12
|
+
* - Otherwise (a filesystem that reports `ino` 0) → a folded real path: the real
|
|
13
|
+
* parent, a `/`, the base name, NFC then lower-cased. The fold can only err toward
|
|
14
|
+
* "same", which keeps what it is asked to remove.
|
|
15
|
+
*
|
|
16
|
+
* An entry the OS refuses to examine is `unknown`, never `different`: the caller
|
|
17
|
+
* keeps. {@link entryIdentities} throws the refusal as an `FsFaultError` on the side
|
|
18
|
+
* the CALLER names, so whatever surfaces it reports a classified fault, not a raw errno.
|
|
19
|
+
*/
|
|
20
|
+
import { type FsSide } from '../errors/fs-fault.js';
|
|
21
|
+
import { type EntryContainment, type EntrySameness, type Identity, type IdentityOracle } from './identity-compare.js';
|
|
22
|
+
/**
|
|
23
|
+
* Every identity `entry` answers to: `[]` when nothing is there; its own (`lstat`) for
|
|
24
|
+
* anything; plus its target's (`stat`) when it is a link that resolves.
|
|
25
|
+
*
|
|
26
|
+
* @param entry - Absolute path to identify
|
|
27
|
+
* @param side - The side of the verb that named `entry`; every fault (the `lstat`, the `stat`, the real path of a fold) is raised on it
|
|
28
|
+
* @throws FsFaultError on `side` when the OS refuses to examine `entry`
|
|
29
|
+
*/
|
|
30
|
+
export declare function entryIdentities(entry: string, side: FsSide): readonly Identity[];
|
|
31
|
+
/**
|
|
32
|
+
* Whether `a` and `b` are one entry. Any shared identity is `same`; identities on
|
|
33
|
+
* both sides with a filesystem id each and none shared is `different`; a folded path
|
|
34
|
+
* on either side that does not match is `unknown`; an entry the OS refuses to
|
|
35
|
+
* examine is `unknown`; an absent entry is `different` (nothing there can alias).
|
|
36
|
+
*
|
|
37
|
+
* @param a - Absolute path
|
|
38
|
+
* @param b - Absolute path
|
|
39
|
+
*/
|
|
40
|
+
export declare function sameEntry(a: string, b: string): EntrySameness;
|
|
41
|
+
/**
|
|
42
|
+
* Whether `child` lies strictly under `ancestor`, judged by identity: some directory
|
|
43
|
+
* on the way up from `child` — along its spelling as given, and along where it
|
|
44
|
+
* physically is — is the same entry as `ancestor`. So a linked or case-aliased
|
|
45
|
+
* spelling of the ancestor is found, and so is a directory of `child`'s spelling that
|
|
46
|
+
* is a link to somewhere BELOW the ancestor. `child` need not exist.
|
|
47
|
+
* `unknown` when no ancestor proved `same` and one could not be examined.
|
|
48
|
+
*
|
|
49
|
+
* ⚠️ On a filesystem that reports `ino` 0 the answer for an existing `ancestor` is
|
|
50
|
+
* `unknown`, never `outside`: a fold cannot prove two names apart. A caller that
|
|
51
|
+
* refuses on `inside` must not refuse on `unknown`, or every copy there is refused.
|
|
52
|
+
*
|
|
53
|
+
* @param child - Absolute path
|
|
54
|
+
* @param ancestor - Absolute path
|
|
55
|
+
*/
|
|
56
|
+
export declare function isInsideByIdentity(child: string, ancestor: string): EntryContainment;
|
|
57
|
+
/**
|
|
58
|
+
* {@link sameEntry} and {@link isInsideByIdentity} over one memo: each entry is examined once
|
|
59
|
+
* (`entryIdentities(entry, 'destination')`), so every answer of a plan is built from one observation.
|
|
60
|
+
*/
|
|
61
|
+
export declare function identityOracle(): IdentityOracle;
|
|
62
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/tree-change/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAOH,OAAO,EAA+C,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAMjG,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAE,KAAK,QAAQ,EAAc,KAAK,cAAc,EAAwC,MAAM,uBAAuB,CAAC;AAkCxK;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,QAAQ,EAAE,CAYhF;AA8BD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,aAAa,CAE7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAEpF;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,cAAc,CAE/C"}
|