@vibe-agent-toolkit/utils 0.2.0-rc.1 → 0.2.0-rc.10
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 +69 -23
- package/dist/asset-reference.js +3 -11
- package/dist/asset-reference.js.map +1 -1
- package/dist/compare-code-units.d.ts +28 -0
- package/dist/compare-code-units.d.ts.map +1 -0
- package/dist/compare-code-units.js +33 -0
- package/dist/compare-code-units.js.map +1 -0
- package/dist/crawl-timing.d.ts +657 -0
- package/dist/crawl-timing.d.ts.map +1 -0
- package/dist/crawl-timing.js +765 -0
- package/dist/crawl-timing.js.map +1 -0
- package/dist/crawl.d.ts +2 -4
- package/dist/crawl.d.ts.map +1 -1
- package/dist/crawl.js +2 -4
- package/dist/crawl.js.map +1 -1
- package/dist/dirent-kind.d.ts +83 -0
- package/dist/dirent-kind.d.ts.map +1 -0
- package/dist/dirent-kind.js +129 -0
- package/dist/dirent-kind.js.map +1 -0
- package/dist/entrypoint.d.ts +54 -0
- package/dist/entrypoint.d.ts.map +1 -0
- package/dist/entrypoint.js +71 -0
- package/dist/entrypoint.js.map +1 -0
- package/dist/env-flag.d.ts +51 -0
- package/dist/env-flag.d.ts.map +1 -0
- package/dist/env-flag.js +62 -0
- package/dist/env-flag.js.map +1 -0
- package/dist/errors/errno.d.ts +51 -0
- package/dist/errors/errno.d.ts.map +1 -0
- package/dist/errors/errno.js +112 -0
- package/dist/errors/errno.js.map +1 -0
- package/dist/errors/vat-error.d.ts +60 -0
- package/dist/errors/vat-error.d.ts.map +1 -0
- package/dist/errors/vat-error.js +86 -0
- package/dist/errors/vat-error.js.map +1 -0
- package/dist/file-crawler.d.ts +60 -0
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +133 -29
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs/file-hash.d.ts.map +1 -1
- package/dist/fs/file-hash.js +0 -1
- package/dist/fs/file-hash.js.map +1 -1
- package/dist/fs-utils.d.ts +422 -191
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +534 -204
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +5 -2
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +9 -7
- package/dist/fs.js.map +1 -1
- package/dist/git-run.d.ts +167 -0
- package/dist/git-run.d.ts.map +1 -0
- package/dist/git-run.js +154 -0
- package/dist/git-run.js.map +1 -0
- package/dist/git-snapshot.d.ts +199 -0
- package/dist/git-snapshot.d.ts.map +1 -0
- package/dist/git-snapshot.js +250 -0
- package/dist/git-snapshot.js.map +1 -0
- package/dist/git-tracker.d.ts +166 -3
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +250 -22
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts +84 -1
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +208 -82
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +13 -4
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +12 -4
- package/dist/git.js.map +1 -1
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +15 -6
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/glob/glob-pattern.js +5 -5
- package/dist/glob/glob-pattern.js.map +1 -1
- package/dist/index.d.ts +32 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -50
- package/dist/index.js.map +1 -1
- package/dist/listing-refusal.d.ts +112 -0
- package/dist/listing-refusal.d.ts.map +1 -0
- package/dist/listing-refusal.js +105 -0
- package/dist/listing-refusal.js.map +1 -0
- package/dist/path-containment.d.ts +64 -0
- package/dist/path-containment.d.ts.map +1 -0
- package/dist/path-containment.js +111 -0
- package/dist/path-containment.js.map +1 -0
- package/dist/path-core.d.ts +113 -18
- package/dist/path-core.d.ts.map +1 -1
- package/dist/path-core.js +139 -23
- package/dist/path-core.js.map +1 -1
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +42 -47
- package/dist/path-utils.js.map +1 -1
- package/dist/path.d.ts +1 -1
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +1 -1
- package/dist/path.js.map +1 -1
- package/dist/process.d.ts +7 -0
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +7 -0
- package/dist/process.js.map +1 -1
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +15 -9
- package/dist/project-utils.js.map +1 -1
- package/dist/project.d.ts +7 -5
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +7 -5
- package/dist/project.js.map +1 -1
- package/dist/safe-exec.d.ts +40 -11
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +111 -25
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-targets.d.ts +2 -2
- package/dist/skill-targets.js +2 -2
- package/dist/skill-test/auth-resolver.d.ts +10 -2
- package/dist/skill-test/auth-resolver.d.ts.map +1 -1
- package/dist/skill-test/auth-resolver.js +26 -11
- package/dist/skill-test/auth-resolver.js.map +1 -1
- package/dist/skill-test/index.d.ts +1 -1
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +1 -1
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +30 -1
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +71 -5
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/skill-test/transcript.d.ts +41 -2
- package/dist/skill-test/transcript.d.ts.map +1 -1
- package/dist/skill-test/transcript.js +55 -10
- package/dist/skill-test/transcript.js.map +1 -1
- package/dist/spawn-hardened.js +0 -1
- package/dist/spawn-hardened.js.map +1 -1
- package/dist/stdio-blocking.js +13 -14
- package/dist/stdio-blocking.js.map +1 -1
- package/dist/test-helpers.d.ts +135 -100
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +224 -146
- package/dist/test-helpers.js.map +1 -1
- package/dist/testing/executables.d.ts +32 -0
- package/dist/testing/executables.d.ts.map +1 -0
- package/dist/testing/executables.js +70 -0
- package/dist/testing/executables.js.map +1 -0
- package/dist/testing/hostile-tree.d.ts +79 -0
- package/dist/testing/hostile-tree.d.ts.map +1 -0
- package/dist/testing/hostile-tree.js +164 -0
- package/dist/testing/hostile-tree.js.map +1 -0
- package/dist/testing/platform-gates.d.ts +31 -0
- package/dist/testing/platform-gates.d.ts.map +1 -0
- package/dist/testing/platform-gates.js +31 -0
- package/dist/testing/platform-gates.js.map +1 -0
- package/dist/testing/temp-dir.d.ts +255 -0
- package/dist/testing/temp-dir.d.ts.map +1 -0
- package/dist/testing/temp-dir.js +363 -0
- package/dist/testing/temp-dir.js.map +1 -0
- package/dist/testing.d.ts +94 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +119 -3
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts +197 -0
- package/dist/text-content.d.ts.map +1 -0
- package/dist/text-content.js +354 -0
- package/dist/text-content.js.map +1 -0
- package/dist/text-file.d.ts +45 -0
- package/dist/text-file.d.ts.map +1 -0
- package/dist/text-file.js +51 -0
- package/dist/text-file.js.map +1 -0
- package/dist/text.d.ts +16 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +16 -0
- package/dist/text.js.map +1 -0
- package/dist/timing-dump.d.ts +136 -0
- package/dist/timing-dump.d.ts.map +1 -0
- package/dist/timing-dump.js +229 -0
- package/dist/timing-dump.js.map +1 -0
- package/dist/windows-shell.d.ts +0 -1
- package/dist/windows-shell.d.ts.map +1 -1
- package/dist/windows-shell.js +0 -1
- package/dist/windows-shell.js.map +1 -1
- package/dist/zod-introspection.js +2 -2
- package/dist/zod-introspection.js.map +1 -1
- package/eslint/README.md +223 -49
- package/eslint/index.cjs +111 -97
- package/eslint/index.d.cts +16 -6
- package/eslint/rules/callee-name.cjs +23 -0
- package/eslint/rules/commands-import-boundary.cjs +148 -0
- package/eslint/rules/dead-import.cjs +1 -1
- package/eslint/rules/dirent-type-needs-symlink-check.cjs +282 -0
- package/eslint/rules/eslint-rule-factory.cjs +5 -2
- package/eslint/rules/exempt-path-matcher.cjs +2 -1
- package/eslint/rules/explicit-zod-strictness.cjs +120 -0
- package/eslint/rules/no-bare-dynamic-import-path.cjs +5 -1
- package/eslint/rules/no-bare-executable-spawn.cjs +70 -0
- package/eslint/rules/no-bare-symlink-in-tests.cjs +213 -0
- package/eslint/rules/no-blind-catch.cjs +171 -0
- package/eslint/rules/no-child-process-execSync.cjs +8 -0
- package/eslint/rules/no-decaying-referent.cjs +217 -0
- package/eslint/rules/no-dotdot-containment.cjs +160 -0
- package/eslint/rules/no-file-url-string-concat.cjs +4 -1
- package/eslint/rules/no-fragile-entrypoint-guard.cjs +228 -0
- package/eslint/rules/no-fs-mkdirSync.cjs +8 -0
- package/eslint/rules/no-fs-promises-cp.cjs +7 -0
- package/eslint/rules/no-fs-realpathSync.cjs +8 -0
- package/eslint/rules/no-hardcoded-path-split.cjs +29 -18
- package/eslint/rules/no-io-in-unit-tier.cjs +129 -0
- package/eslint/rules/no-literal-process-exit.cjs +129 -0
- package/eslint/rules/no-manual-path-normalize.cjs +136 -107
- package/eslint/rules/no-os-tmpdir.cjs +8 -0
- package/eslint/rules/no-path-operations-in-comparisons.cjs +7 -2
- package/eslint/rules/no-path-sep-in-strings.cjs +7 -2
- package/eslint/rules/no-path-startswith.cjs +7 -2
- package/eslint/rules/no-process-exit-in-phase.cjs +127 -0
- package/eslint/rules/no-raw-node-path.cjs +563 -0
- package/eslint/rules/no-raw-text-decode.cjs +305 -0
- package/eslint/rules/no-registry-count-pin.cjs +255 -0
- package/eslint/rules/no-self-package-import.cjs +168 -0
- package/eslint/rules/no-test-scoped-functions.cjs +10 -3
- package/eslint/rules/no-unix-shell-commands.cjs +6 -2
- package/eslint/rules/no-unsafe-root-join.cjs +23 -2
- package/eslint/rules/no-url-pathname-for-fs.cjs +5 -1
- package/eslint/rules/no-version-literal.cjs +151 -0
- package/eslint/rules/prefer-startswith-over-regex.cjs +9 -0
- package/eslint/rules/require-justified-skip.cjs +8 -2
- package/eslint/rules/safe-import.cjs +1 -1
- package/package.json +16 -16
- package/dist/link-auth/build-headers.d.ts +0 -34
- package/dist/link-auth/build-headers.d.ts.map +0 -1
- package/dist/link-auth/build-headers.js +0 -58
- package/dist/link-auth/build-headers.js.map +0 -1
- package/dist/link-auth/expand-macro.d.ts +0 -38
- package/dist/link-auth/expand-macro.d.ts.map +0 -1
- package/dist/link-auth/expand-macro.js +0 -133
- package/dist/link-auth/expand-macro.js.map +0 -1
- package/dist/link-auth/macros.yaml +0 -50
- package/dist/link-auth/resolve-token.d.ts +0 -83
- package/dist/link-auth/resolve-token.d.ts.map +0 -1
- package/dist/link-auth/resolve-token.js +0 -101
- package/dist/link-auth/resolve-token.js.map +0 -1
- package/dist/link-auth/resolve.d.ts +0 -102
- package/dist/link-auth/resolve.d.ts.map +0 -1
- package/dist/link-auth/resolve.js +0 -66
- package/dist/link-auth/resolve.js.map +0 -1
- package/dist/link-auth/rewrite.d.ts +0 -52
- package/dist/link-auth/rewrite.d.ts.map +0 -1
- package/dist/link-auth/rewrite.js +0 -102
- package/dist/link-auth/rewrite.js.map +0 -1
- package/dist/link-auth/select-provider.d.ts +0 -30
- package/dist/link-auth/select-provider.d.ts.map +0 -1
- package/dist/link-auth/select-provider.js +0 -55
- package/dist/link-auth/select-provider.js.map +0 -1
- package/dist/link-auth/template.d.ts +0 -40
- package/dist/link-auth/template.d.ts.map +0 -1
- package/dist/link-auth/template.js +0 -89
- package/dist/link-auth/template.js.map +0 -1
- package/dist/link-auth/transforms.d.ts +0 -46
- package/dist/link-auth/transforms.d.ts.map +0 -1
- package/dist/link-auth/transforms.js +0 -52
- package/dist/link-auth/transforms.js.map +0 -1
- package/dist/template-entry.d.ts +0 -10
- package/dist/template-entry.d.ts.map +0 -1
- package/dist/template-entry.js +0 -10
- package/dist/template-entry.js.map +0 -1
- package/dist/template.d.ts +0 -7
- package/dist/template.d.ts.map +0 -1
- package/dist/template.js +0 -18
- package/dist/template.js.map +0 -1
- package/eslint/rules/no-path-join.cjs +0 -14
- package/eslint/rules/no-path-relative.cjs +0 -14
- package/eslint/rules/no-path-resolve.cjs +0 -14
- package/eslint/rules/path-function-rule-factory.cjs +0 -496
package/dist/index.js
CHANGED
|
@@ -3,88 +3,133 @@
|
|
|
3
3
|
* Core shared utilities with no dependencies on other packages
|
|
4
4
|
*
|
|
5
5
|
* Utilities are added as needed by other packages, not speculatively.
|
|
6
|
+
*
|
|
7
|
+
* **A module that brings a third-party dependency belongs on a subpath, not
|
|
8
|
+
* here.** This entry has ~245 in-repo importers plus published adopters, and
|
|
9
|
+
* every one of them pays for the whole graph reachable from it — a bundler that
|
|
10
|
+
* cannot tree-shake a dependency (a CJS package, or one reached through
|
|
11
|
+
* `new URL(..., import.meta.url)`) inlines it into every consumer that touched
|
|
12
|
+
* a path helper. The domains that carry weight each have their own entry:
|
|
13
|
+
* `./crawl`, `./git`, `./process`, `./skill-test`, `./template`, `./yaml`.
|
|
14
|
+
* `./skill-test` is there for a *transitive* reason rather than a direct one —
|
|
15
|
+
* it declares no dependency itself, but spawning a headless agent goes through
|
|
16
|
+
* `./process`, which reaches `which` and (because `safeExecSync` refuses `git`
|
|
17
|
+
* and delegates) `@vibe-validate/git`. Reachability is the criterion, not the
|
|
18
|
+
* import a module happens to write.
|
|
19
|
+
*
|
|
20
|
+
* The rule is enforced, not merely stated: `test/subpath-purity.test.ts` asserts
|
|
21
|
+
* this entry's reachable third-party set by equality, so a dependency arriving
|
|
22
|
+
* here — directly or transitively, through any module below — turns that row red
|
|
23
|
+
* and has to be an argued edit rather than an unnoticed one.
|
|
6
24
|
*/
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export * from './windows-shell.js';
|
|
11
|
-
// Hardened async spawn (streaming stdio + correct Windows .cmd/.bat launching)
|
|
12
|
-
export * from './spawn-hardened.js';
|
|
25
|
+
// The base of every error VAT throws on purpose; `code` is what a catch
|
|
26
|
+
// block dispatches on, never `message`.
|
|
27
|
+
export * from './errors/vat-error.js';
|
|
13
28
|
// Cross-platform path utilities
|
|
14
29
|
export * from './path-utils.js';
|
|
15
|
-
//
|
|
16
|
-
export * from './
|
|
30
|
+
// The filesystem-backed containment verdict every delete/copy/uninstall sink asks.
|
|
31
|
+
export * from './path-containment.js';
|
|
32
|
+
// The Dirent classifiers a walk uses instead of a bare isFile()/isDirectory().
|
|
33
|
+
export * from './dirent-kind.js';
|
|
34
|
+
// THE content-decoding seam: bytes to text, in one place. `decodeTextContent` is
|
|
35
|
+
// pure and also reachable from the dependency-free `./text` entry; the two
|
|
36
|
+
// file-reading wrappers need `node:fs` and are also on `./fs`. Other routes from
|
|
37
|
+
// bytes to text — `buf.toString('utf-8')`, `new TextDecoder()`,
|
|
38
|
+
// `readFile(p, 'utf-8')` — are a lint error under `local/no-raw-text-decode`
|
|
39
|
+
// WHERE IT IS REGISTERED (this package's `src` and `resources`' `src`); the rest
|
|
40
|
+
// of the repo is a migration ledger in `eslint.config.js`, not a covered claim.
|
|
41
|
+
export * from './text-content.js';
|
|
42
|
+
export * from './text-file.js';
|
|
17
43
|
// Asset reference resolution (paths + npm bare specifiers)
|
|
18
44
|
export * from './asset-reference.js';
|
|
19
45
|
// Filesystem utilities.
|
|
20
46
|
//
|
|
21
|
-
// Named rather than `export *` on purpose: `
|
|
22
|
-
// judge
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
47
|
+
// Named rather than `export *` on purpose: `fs-utils.ts` holds internal members
|
|
48
|
+
// of the fill+judge pairs below — the per-directory index build, the table-key
|
|
49
|
+
// derivation — and a star re-export would publish each of them on this barrel
|
|
50
|
+
// the moment it was written. (The `./fs` subpath was already an explicit list
|
|
51
|
+
// and was never at risk.) The public surface is a decision, not a side effect of
|
|
52
|
+
// module layout.
|
|
27
53
|
//
|
|
28
54
|
// TWO materialized columns are published, both shaped fill-first-then-judge:
|
|
29
55
|
//
|
|
30
|
-
// -
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
56
|
+
// - path spellings — `fillPathSpellings` plus `pathSpellingFrom`. It judges a
|
|
57
|
+
// WHOLE path, which is what a caller validating references needs: judging
|
|
58
|
+
// only the basename hands every directory component back to the host
|
|
59
|
+
// filesystem's own folding, so a link that resolves on macOS 404s on a
|
|
60
|
+
// case-sensitive filesystem. The `DirectorySpellingIndex` underneath is
|
|
61
|
+
// published too, for the lazy caller that cannot enumerate its targets up
|
|
62
|
+
// front.
|
|
34
63
|
// - realpaths — `fillRealpaths` (every canonicalization, once, up front) plus
|
|
35
|
-
// `realpathFrom`.
|
|
36
|
-
//
|
|
64
|
+
// `realpathFrom`.
|
|
65
|
+
//
|
|
66
|
+
// In both, the row lookup IS the judge: the row is the answer, so there is
|
|
67
|
+
// nothing left to keep internal.
|
|
37
68
|
//
|
|
38
69
|
// There is deliberately no one-call wrapper composing either pair: handed one, a
|
|
39
70
|
// caller with many paths loops over it, which reinstates the per-path `await` the
|
|
40
71
|
// pairs exist to remove.
|
|
41
72
|
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
// `FilenameCaseVerdict`/`FilenameMatch`, which a consumer does not hand in but
|
|
47
|
-
// does receive and branch on.
|
|
73
|
+
// `PathSpellingTable` and `RealpathTable` — what the fills return and the judges
|
|
74
|
+
// consume — are the types a caller can name, as are `PathSpelling`,
|
|
75
|
+
// `PathSpellingRequest`, `ComponentMatch` and `FilenameMatch`, which a consumer
|
|
76
|
+
// receives and branches on.
|
|
48
77
|
//
|
|
49
78
|
// The cost of that decision, stated so it is not a surprise: a new *type* added
|
|
50
79
|
// to `fs-utils.ts` no longer reaches consumers automatically, and
|
|
51
80
|
// `barrel-exports.test.ts` pins runtime names only, so nothing will fail. The
|
|
52
81
|
// symptom is a type that cannot be imported, which surfaces the first time
|
|
53
82
|
// someone tries — not a silent break in existing code.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
export
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export * from './git-url.js';
|
|
61
|
-
// Git utilities (using git commands directly)
|
|
62
|
-
export * from './git-utils.js';
|
|
83
|
+
// The lookups themselves, plus the memo every fill shares.
|
|
84
|
+
export { isFilesystemAccessError, isPathAbsentError } from './errors/errno.js';
|
|
85
|
+
export { copyDirectory, CopyLinkEscapesSourceError, FsLookupCache, transientRefusalClause, } from './fs-utils.js';
|
|
86
|
+
// The two fill+judge pairs, in the order the note above lists them, plus the
|
|
87
|
+
// lazy index a caller that cannot enumerate its targets up front reaches for.
|
|
88
|
+
export { DirectorySpellingIndex, fillPathSpellings, fillRealpaths, pathSpellingFrom, realpathFrom, spellingWalkRoot, } from './fs-utils.js';
|
|
63
89
|
// Project root discovery (canonical: config → git → null).
|
|
64
90
|
// CLI-boundary use only — see docs/concepts/roots-and-config.md.
|
|
65
91
|
export * from './project-utils.js';
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
|
|
92
|
+
// The runtime half of `test-helpers.ts`: symlink creation with its host
|
|
93
|
+
// capability probe, and the git-environment detachment every child process
|
|
94
|
+
// needs. Named rather than `export *` because the SAME module also holds the
|
|
95
|
+
// fs-refusal fakes (`refuseSyncFs` and siblings), which are test scaffolding
|
|
96
|
+
// and reach adopters on the `./testing` subpath only. A published runtime
|
|
97
|
+
// barrel that carried them — eight such names, once — was the audit's finding,
|
|
98
|
+
// and an `export *` here is how they got in.
|
|
99
|
+
export { createSymlink, createSymlinkAsync, detachGitEnv, errnoError, INHERITED_GIT_ENV, symlinkCapability, } from './test-helpers.js';
|
|
70
100
|
// Zod type introspection (version-agnostic)
|
|
71
101
|
export * from './zod-introspection.js';
|
|
72
|
-
// Handlebars template rendering (cached, no HTML escaping)
|
|
73
|
-
export * from './template.js';
|
|
74
102
|
// Skill target resolution (cross-platform flat skill install paths)
|
|
75
103
|
export * from './skill-targets.js';
|
|
76
|
-
// linkAuth pure engine — public API only (issue #113).
|
|
77
|
-
// Internal helpers (rewrite, build-headers, etc.) stay module-private.
|
|
78
|
-
export { resolveAuthenticatedUrl, } from './link-auth/resolve.js';
|
|
79
|
-
export { defaultRunCommand } from './link-auth/resolve-token.js';
|
|
80
|
-
export { expandMacro, UnknownMacroError } from './link-auth/expand-macro.js';
|
|
81
|
-
// Skill testing utilities (environment management for headless agent runs)
|
|
82
|
-
export * from './skill-test/index.js';
|
|
83
104
|
// Glob pattern helpers (isGlob, staticGlobBase, globMagicRemainder)
|
|
84
105
|
export * from './glob/glob-pattern.js';
|
|
85
106
|
// Filesystem hashing (sha256 of raw file bytes)
|
|
86
107
|
export * from './fs/file-hash.js';
|
|
87
|
-
// Byte-surgical YAML value updater (replace/insert without reflowing the doc)
|
|
88
|
-
export * from './yaml/surgical-yaml.js';
|
|
89
108
|
export { parseWholeNumberAtLeast } from './numeric-args.js';
|
|
109
|
+
// THE reading of an environment variable as a boolean. One implementation
|
|
110
|
+
// because three switches each had their own `!== '0'` comparison and none of
|
|
111
|
+
// them turned off for `=false`.
|
|
112
|
+
export { parseEnvBoolean } from './env-flag.js';
|
|
113
|
+
// Machine-independent string ordering for hashed/serialized output — never `localeCompare`.
|
|
114
|
+
export { compareCodeUnits } from './compare-code-units.js';
|
|
115
|
+
// The crawl-timing seam: which contributor, stratum and fixpoint pass owns the
|
|
116
|
+
// time it takes to FIND documents.
|
|
117
|
+
//
|
|
118
|
+
// It lives HERE, at the bottom of the graph, because the work it measures is
|
|
119
|
+
// spread across four packages above this one — `resources` builds the registry,
|
|
120
|
+
// `agent-skills` walks the link graph, `claude-marketplace` enumerates an
|
|
121
|
+
// inventory, and `utils` itself initializes the `GitTracker` both crawlers
|
|
122
|
+
// consume. Both arms must record through ONE recorder or the two are not
|
|
123
|
+
// comparable, and only `utils` is beneath all of them. See the module header.
|
|
124
|
+
//
|
|
125
|
+
// Named rather than `export *`: the accumulator internals (`addEntry`,
|
|
126
|
+
// `keyOf`, `recordInheritedPass`) are not surface, and `timing-dump.ts` is
|
|
127
|
+
// plumbing shared with `resources`' package-internal `parse-timing.ts` — a star
|
|
128
|
+
// re-export would publish both the moment either grew a symbol.
|
|
129
|
+
export { CRAWL_BLOB_POPULATE_ID, CRAWL_CLOSURE_CONTRIBUTE_ID, CRAWL_CLOSURE_RESOLVE_ID, CRAWL_PASS_INSIDE, CRAWL_REGISTRY_ADMIT_ID, CRAWL_REGISTRY_ENUMERATE_ID, CRAWL_REGISTRY_ID_PREFIX, CRAWL_REGISTRY_RESOLVE_LINKS_ID, CRAWL_SHARED_GIT_TRACKER_ID, CRAWL_STORE_READ_ID, CRAWL_STORE_WRITE_ID, CRAWL_STRATA, CRAWL_WALKER_GITIGNORE_ID, CRAWL_WALKER_ID, crawlTimingStart, recordContributorInvocation, recordCrawlPass, recordRegistryPass, recordSharedPass, withContributorStratum, withOuterBracket, } from './crawl-timing.js';
|
|
130
|
+
// The three `__*ForTest` seams of the same module are on `./testing`, not here.
|
|
131
|
+
// The on-disk plumbing both timing seams share. Exported because
|
|
132
|
+
// `resources`' `parse-timing.ts` is the other consumer and now sits a package
|
|
133
|
+
// away; nothing else should reach for it.
|
|
134
|
+
export { ensureTimingDirectory, normalizeTimingDirectory, readTimingProcess, writeTimingDump, } from './timing-dump.js';
|
|
90
135
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a listing does with a directory it was refused — the vocabulary shared
|
|
3
|
+
* by the filesystem walk (`file-crawler.ts`) and the git listings
|
|
4
|
+
* (`git-utils.ts`), so a caller decides ONCE for both routes.
|
|
5
|
+
*
|
|
6
|
+
* 🚨 A refused listing is a gap in the POPULATION: every file beneath that
|
|
7
|
+
* directory the listing would have found was never seen and is absent from
|
|
8
|
+
* every count downstream. There are exactly two honest answers — stop, with a
|
|
9
|
+
* sentence the adopter can act on, or keep going and REPORT the gap — and
|
|
10
|
+
* which one is right is the caller's knowledge, not the crawler's. So the
|
|
11
|
+
* policy is {@link UnreadablePolicy}, it is REQUIRED wherever a listing is
|
|
12
|
+
* asked for, and it has no default.
|
|
13
|
+
*
|
|
14
|
+
* 🪤 It used to be `onUnreadable?`, an optional callback. Every caller that
|
|
15
|
+
* ignored it compiled; the two routes then defaulted differently (the walk
|
|
16
|
+
* threw a programmer-facing sentence, the git route dropped the refusal off
|
|
17
|
+
* its stderr without a word); and the miss surfaced one review round later as
|
|
18
|
+
* a HIGH. A required field is what makes `tsc` enumerate the callers.
|
|
19
|
+
*/
|
|
20
|
+
import { VatError } from './errors/vat-error.js';
|
|
21
|
+
import { type DirectoryRefusal } from './fs-utils.js';
|
|
22
|
+
/**
|
|
23
|
+
* What a caller that STOPS on a refusal tells the person reading the error.
|
|
24
|
+
*
|
|
25
|
+
* A caller that has decided to stop — a build that must not ship an incomplete
|
|
26
|
+
* bundle, a discovery that must not silently find fewer skills — owes the
|
|
27
|
+
* adopter the directory against the root they know, and the knob THEY have.
|
|
28
|
+
*/
|
|
29
|
+
export interface RefuseListingContext {
|
|
30
|
+
/**
|
|
31
|
+
* The root the refused directory is expressed against. An absolute path in
|
|
32
|
+
* an error is the developer's `$HOME` in every CI log.
|
|
33
|
+
*/
|
|
34
|
+
root: string;
|
|
35
|
+
/**
|
|
36
|
+
* What the adopter can do about it — naming this lane's deliberate-drop
|
|
37
|
+
* mechanism (`resources.exclude`, a plugin `exclude:`, `--exclude`), which
|
|
38
|
+
* differs per caller and is why the crawler cannot supply it.
|
|
39
|
+
*/
|
|
40
|
+
remedy: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The caller's decision about a directory the listing cannot open.
|
|
44
|
+
*
|
|
45
|
+
* - `{ refuse }` — STOP: the listing throws {@link DirectoryListingRefusedError}
|
|
46
|
+
* with the adopter-facing sentence built from the context. Right for every
|
|
47
|
+
* lane whose output is acted on as a complete population (a build, a
|
|
48
|
+
* discovery, a fingerprint, a copy).
|
|
49
|
+
* - `{ degrade }` — KEEP GOING and hand the refusal over. The caller is
|
|
50
|
+
* promising to REPORT the gap — as its own finding, with the directory and
|
|
51
|
+
* errno the refusal carries — not to drop it. Right for a backstop appended
|
|
52
|
+
* to a larger report, where one unlistable directory must not destroy every
|
|
53
|
+
* finding beside it.
|
|
54
|
+
*
|
|
55
|
+
* A directory that VANISHED between being enumerated and being listed
|
|
56
|
+
* (`ENOENT` / `ENOTDIR`) is not a refusal under either arm: it is no longer in
|
|
57
|
+
* the population and is skipped without a call.
|
|
58
|
+
*/
|
|
59
|
+
export type UnreadablePolicy = {
|
|
60
|
+
readonly refuse: RefuseListingContext;
|
|
61
|
+
} | {
|
|
62
|
+
readonly degrade: (refusal: DirectoryRefusal) => void;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Thrown by a listing under `{ refuse }` when a directory refused to be
|
|
66
|
+
* listed. The message is the adopter's sentence — see {@link refusedListingMessage}.
|
|
67
|
+
*/
|
|
68
|
+
export declare class DirectoryListingRefusedError extends VatError {
|
|
69
|
+
readonly refusal: DirectoryRefusal;
|
|
70
|
+
constructor(refusal: DirectoryRefusal, context: RefuseListingContext);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The adopter-facing sentence: root-relative directory, errno, the gap, and
|
|
74
|
+
* the caller's remedy — **the one owner of that sentence.**
|
|
75
|
+
*
|
|
76
|
+
* Exported so every lane that refuses says the same thing. Three lanes used to
|
|
77
|
+
* compose this by hand and had drifted by a clause each ("enumerated" /
|
|
78
|
+
* "scanned" / "the population could not be enumerated"); the transient clause
|
|
79
|
+
* in particular was written once in `fs-utils.ts` precisely so nobody would.
|
|
80
|
+
* See {@link REFUSAL_GAP_CLAUSE} for why it asserts nothing about what WAS
|
|
81
|
+
* enumerated beneath the directory.
|
|
82
|
+
*
|
|
83
|
+
* @param refusal - What was refused
|
|
84
|
+
* @param context - The root to express it against, and what the adopter can do
|
|
85
|
+
* @returns One sentence, root-relative, never containing the absolute path
|
|
86
|
+
*/
|
|
87
|
+
export declare function refusedListingMessage(refusal: DirectoryRefusal, context: RefuseListingContext): string;
|
|
88
|
+
/**
|
|
89
|
+
* Apply the caller's policy to one refusal: throw under `{ refuse }`, hand it
|
|
90
|
+
* over under `{ degrade }`. The single place either route settles a refusal,
|
|
91
|
+
* so the two cannot drift.
|
|
92
|
+
*
|
|
93
|
+
* @param policy - The caller's decision
|
|
94
|
+
* @param refusal - The directory that could not be listed
|
|
95
|
+
*/
|
|
96
|
+
export declare function settleRefusal(policy: UnreadablePolicy, refusal: DirectoryRefusal): void;
|
|
97
|
+
/**
|
|
98
|
+
* Refuse an omitted policy up front, by name, before any directory is listed.
|
|
99
|
+
*
|
|
100
|
+
* The type already makes `unreadable` required, and that is what enumerates
|
|
101
|
+
* the TypeScript callers. This is the runtime half, for the callers the type
|
|
102
|
+
* cannot reach — test files are not typechecked by the build, and a JavaScript
|
|
103
|
+
* adopter has no compiler at all. Without it an omitted policy would surface
|
|
104
|
+
* only on the first refusal, as a `TypeError` from the `in` operator, and a
|
|
105
|
+
* tree with nothing unreadable would keep returning complete lists — the
|
|
106
|
+
* optional seam back again, one layer down.
|
|
107
|
+
*
|
|
108
|
+
* @param policy - What the caller passed as `unreadable`
|
|
109
|
+
* @param api - The entry point, for the sentence
|
|
110
|
+
*/
|
|
111
|
+
export declare function requireUnreadablePolicy(policy: UnreadablePolicy | undefined, api: string): asserts policy is UnreadablePolicy;
|
|
112
|
+
//# sourceMappingURL=listing-refusal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listing-refusal.d.ts","sourceRoot":"","sources":["../src/listing-refusal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,KAAK,gBAAgB,EAA0B,MAAM,eAAe,CAAC;AAG9E;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB;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;;;GAGG;AACH,qBAAa,4BAA6B,SAAQ,QAAQ;IACxD,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;gBAEvB,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,oBAAoB;CAIrE;AAmBD;;;;;;;;;;;;;;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;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,gBAAgB,CAO7H"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a listing does with a directory it was refused — the vocabulary shared
|
|
3
|
+
* by the filesystem walk (`file-crawler.ts`) and the git listings
|
|
4
|
+
* (`git-utils.ts`), so a caller decides ONCE for both routes.
|
|
5
|
+
*
|
|
6
|
+
* 🚨 A refused listing is a gap in the POPULATION: every file beneath that
|
|
7
|
+
* directory the listing would have found was never seen and is absent from
|
|
8
|
+
* every count downstream. There are exactly two honest answers — stop, with a
|
|
9
|
+
* sentence the adopter can act on, or keep going and REPORT the gap — and
|
|
10
|
+
* which one is right is the caller's knowledge, not the crawler's. So the
|
|
11
|
+
* policy is {@link UnreadablePolicy}, it is REQUIRED wherever a listing is
|
|
12
|
+
* asked for, and it has no default.
|
|
13
|
+
*
|
|
14
|
+
* 🪤 It used to be `onUnreadable?`, an optional callback. Every caller that
|
|
15
|
+
* ignored it compiled; the two routes then defaulted differently (the walk
|
|
16
|
+
* threw a programmer-facing sentence, the git route dropped the refusal off
|
|
17
|
+
* its stderr without a word); and the miss surfaced one review round later as
|
|
18
|
+
* a HIGH. A required field is what makes `tsc` enumerate the callers.
|
|
19
|
+
*/
|
|
20
|
+
import { VatError } from './errors/vat-error.js';
|
|
21
|
+
import { transientRefusalClause } from './fs-utils.js';
|
|
22
|
+
import { safePath, toForwardSlash } from './path-utils.js';
|
|
23
|
+
/**
|
|
24
|
+
* Thrown by a listing under `{ refuse }` when a directory refused to be
|
|
25
|
+
* listed. The message is the adopter's sentence — see {@link refusedListingMessage}.
|
|
26
|
+
*/
|
|
27
|
+
export class DirectoryListingRefusedError extends VatError {
|
|
28
|
+
refusal;
|
|
29
|
+
constructor(refusal, context) {
|
|
30
|
+
super('DIRECTORY_LISTING_REFUSED', refusedListingMessage(refusal, context));
|
|
31
|
+
this.refusal = refusal;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* What a refused listing costs, said so it is true on BOTH routes.
|
|
36
|
+
*
|
|
37
|
+
* 🪤 It used to read "so nothing beneath it was enumerated: every file there
|
|
38
|
+
* is in the declared scan". Both halves were the readdir walk's truth and the
|
|
39
|
+
* git route's lie: `git ls-files --others` reports the refusal while the index
|
|
40
|
+
* has already named every TRACKED file beneath the directory — the same run
|
|
41
|
+
* that printed the sentence had scanned one of them — and the projection's
|
|
42
|
+
* population is the whole non-ignored tree, which no `include`/`exclude`
|
|
43
|
+
* "declared". What holds on every route is the hazard itself: whatever the
|
|
44
|
+
* listing would have found and nothing else named is missing, and the shorter
|
|
45
|
+
* result is indistinguishable from a complete one.
|
|
46
|
+
*/
|
|
47
|
+
const REFUSAL_GAP_CLAUSE = 'any file beneath it that no other listing named is absent from every count, ' +
|
|
48
|
+
'and the result cannot be told from a complete one';
|
|
49
|
+
/**
|
|
50
|
+
* The adopter-facing sentence: root-relative directory, errno, the gap, and
|
|
51
|
+
* the caller's remedy — **the one owner of that sentence.**
|
|
52
|
+
*
|
|
53
|
+
* Exported so every lane that refuses says the same thing. Three lanes used to
|
|
54
|
+
* compose this by hand and had drifted by a clause each ("enumerated" /
|
|
55
|
+
* "scanned" / "the population could not be enumerated"); the transient clause
|
|
56
|
+
* in particular was written once in `fs-utils.ts` precisely so nobody would.
|
|
57
|
+
* See {@link REFUSAL_GAP_CLAUSE} for why it asserts nothing about what WAS
|
|
58
|
+
* enumerated beneath the directory.
|
|
59
|
+
*
|
|
60
|
+
* @param refusal - What was refused
|
|
61
|
+
* @param context - The root to express it against, and what the adopter can do
|
|
62
|
+
* @returns One sentence, root-relative, never containing the absolute path
|
|
63
|
+
*/
|
|
64
|
+
export function refusedListingMessage(refusal, context) {
|
|
65
|
+
const relative = toForwardSlash(safePath.relative(context.root, refusal.directory));
|
|
66
|
+
const where = relative === '' ? 'the scan root itself' : `the directory '${relative}'`;
|
|
67
|
+
const remedy = refusal.transient
|
|
68
|
+
? `${transientRefusalClause(refusal.code)}, so nothing is wrong with the tree — re-run before investigating anything.`
|
|
69
|
+
: context.remedy;
|
|
70
|
+
return `Listing ${where} was refused (${refusal.code}): ${REFUSAL_GAP_CLAUSE}. ${remedy}`;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Apply the caller's policy to one refusal: throw under `{ refuse }`, hand it
|
|
74
|
+
* over under `{ degrade }`. The single place either route settles a refusal,
|
|
75
|
+
* so the two cannot drift.
|
|
76
|
+
*
|
|
77
|
+
* @param policy - The caller's decision
|
|
78
|
+
* @param refusal - The directory that could not be listed
|
|
79
|
+
*/
|
|
80
|
+
export function settleRefusal(policy, refusal) {
|
|
81
|
+
if ('refuse' in policy)
|
|
82
|
+
throw new DirectoryListingRefusedError(refusal, policy.refuse);
|
|
83
|
+
policy.degrade(refusal);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Refuse an omitted policy up front, by name, before any directory is listed.
|
|
87
|
+
*
|
|
88
|
+
* The type already makes `unreadable` required, and that is what enumerates
|
|
89
|
+
* the TypeScript callers. This is the runtime half, for the callers the type
|
|
90
|
+
* cannot reach — test files are not typechecked by the build, and a JavaScript
|
|
91
|
+
* adopter has no compiler at all. Without it an omitted policy would surface
|
|
92
|
+
* only on the first refusal, as a `TypeError` from the `in` operator, and a
|
|
93
|
+
* tree with nothing unreadable would keep returning complete lists — the
|
|
94
|
+
* optional seam back again, one layer down.
|
|
95
|
+
*
|
|
96
|
+
* @param policy - What the caller passed as `unreadable`
|
|
97
|
+
* @param api - The entry point, for the sentence
|
|
98
|
+
*/
|
|
99
|
+
export function requireUnreadablePolicy(policy, api) {
|
|
100
|
+
if (policy === undefined) {
|
|
101
|
+
throw new TypeError(`${api}: \`unreadable\` is required — pass { refuse: { root, remedy } } to stop on a directory the listing cannot open, ` +
|
|
102
|
+
'or { degrade: (refusal) => … } to keep going and report the gap yourself.');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=listing-refusal.js.map
|
|
@@ -0,0 +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;AA4C3D;;;GAGG;AACH,MAAM,OAAO,4BAA6B,SAAQ,QAAQ;IAC/C,OAAO,CAAmB;IAEnC,YAAY,OAAyB,EAAE,OAA6B;QAClE,KAAK,CAAC,2BAA2B,EAAE,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAC5E,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,kBAAkB,GACtB,8EAA8E;IAC9E,mDAAmD,CAAC;AAEtD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAyB,EAAE,OAA6B;IAC5F,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IACpF,MAAM,KAAK,GAAG,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,kBAAkB,QAAQ,GAAG,CAAC;IACvF,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS;QAC9B,CAAC,CAAC,GAAG,sBAAsB,CAAC,OAAO,CAAC,IAAI,CAAC,6EAA6E;QACtH,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACnB,OAAO,WAAW,KAAK,iBAAiB,OAAO,CAAC,IAAI,MAAM,kBAAkB,KAAK,MAAM,EAAE,CAAC;AAC5F,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,MAAwB,EAAE,OAAyB;IAC/E,IAAI,QAAQ,IAAI,MAAM;QAAE,MAAM,IAAI,4BAA4B,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACvF,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAoC,EAAE,GAAW;IACvF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,SAAS,CACjB,GAAG,GAAG,mHAAmH;YACvH,2EAA2E,CAC9E,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `isUnderRoot` — the containment question a SINK asks.
|
|
3
|
+
*
|
|
4
|
+
* A delete, a copy, an uninstall: each takes a path that somebody else spelled
|
|
5
|
+
* (a manifest's `name:`, a `flatSkills` entry, a positional the user typed) and
|
|
6
|
+
* is about to act on `join(root, that)`. The question is whether the action
|
|
7
|
+
* lands inside the root, and the only honest answer comes from the
|
|
8
|
+
* filesystem: `startsWith('..')` on a lexical `relative()` misses a symlink
|
|
9
|
+
* inside the root that points out, refuses a member named `..cache`, and is
|
|
10
|
+
* blind to a drive letter — the sweep behind this module watched all three
|
|
11
|
+
* happen at exit 0.
|
|
12
|
+
*
|
|
13
|
+
* Both sides are canonicalized the same way, through the deepest existing
|
|
14
|
+
* ancestor, so a root reached through a symlink (macOS `/tmp`, a linked
|
|
15
|
+
* `~/.claude`) still contains its members, and a candidate that does not exist
|
|
16
|
+
* yet is judged by where creating it would land. Absence is reported as its
|
|
17
|
+
* own answer rather than folded into either side, because a delete sink and a
|
|
18
|
+
* create sink want opposite things from it.
|
|
19
|
+
*
|
|
20
|
+
* Lexical classification of an already-relative path — a projection identity,
|
|
21
|
+
* a report relativizer — is a different question with a different helper:
|
|
22
|
+
* `relativeEscapesRoot` in `path-core.ts`, which touches no filesystem.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Where a candidate stands relative to a root, as the filesystem sees it.
|
|
26
|
+
*
|
|
27
|
+
* - `inside` — the candidate exists and its realpath is a STRICT descendant of
|
|
28
|
+
* the root's realpath. A delete or copy may proceed.
|
|
29
|
+
* - `outside` — its realpath (or, when it does not exist, the realpath of its
|
|
30
|
+
* deepest existing ancestor with the missing remainder re-appended) is not
|
|
31
|
+
* under the root. The root itself is `outside` too: nothing is under itself,
|
|
32
|
+
* and a sink that could delete its own root has no business here.
|
|
33
|
+
* - `absent` — nothing exists at the candidate, and creating it would land
|
|
34
|
+
* inside the root. A create sink proceeds; a delete sink has nothing to do.
|
|
35
|
+
*/
|
|
36
|
+
export type Containment = 'inside' | 'outside' | 'absent';
|
|
37
|
+
/**
|
|
38
|
+
* Is `candidate` strictly under `root`, as the filesystem judges it?
|
|
39
|
+
*
|
|
40
|
+
* Both paths may be relative (resolved from cwd) or absolute, in either
|
|
41
|
+
* separator; neither has to exist. See {@link Containment} for the three
|
|
42
|
+
* answers and which sinks accept which.
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ Costs one `lstat` per missing component plus two `realpath`s. That is
|
|
45
|
+
* nothing for a sink — a delete or a copy dwarfs it — and too much for a loop
|
|
46
|
+
* over every link in a corpus, which is why the projection and link lanes
|
|
47
|
+
* classify lexically with `relativeEscapesRoot` and never call this.
|
|
48
|
+
*
|
|
49
|
+
* @param root - The directory the action must stay inside
|
|
50
|
+
* @param candidate - The path the action is about to touch
|
|
51
|
+
* @returns `'inside'`, `'outside'`, or `'absent'`
|
|
52
|
+
* @throws When the OS refuses to examine either path for any reason but
|
|
53
|
+
* absence — a refusal is reported, never read as "not there"
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* // A delete sink: only an existing, contained target may go.
|
|
57
|
+
* if (isUnderRoot(skillsDir, target) !== 'inside') throw new Error(`refusing to remove ${target}: not inside ${skillsDir}`);
|
|
58
|
+
* await rm(target, { recursive: true, force: true });
|
|
59
|
+
*
|
|
60
|
+
* // A create sink: absent-but-contained is exactly the happy path.
|
|
61
|
+
* if (isUnderRoot(skillsDir, dest) === 'outside') throw new Error(`refusing to install to ${dest}: not inside ${skillsDir}`);
|
|
62
|
+
*/
|
|
63
|
+
export declare function isUnderRoot(root: string, candidate: string): Containment;
|
|
64
|
+
//# sourceMappingURL=path-containment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-containment.d.ts","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAQH;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAkD1D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAQxE"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `isUnderRoot` — the containment question a SINK asks.
|
|
3
|
+
*
|
|
4
|
+
* A delete, a copy, an uninstall: each takes a path that somebody else spelled
|
|
5
|
+
* (a manifest's `name:`, a `flatSkills` entry, a positional the user typed) and
|
|
6
|
+
* is about to act on `join(root, that)`. The question is whether the action
|
|
7
|
+
* lands inside the root, and the only honest answer comes from the
|
|
8
|
+
* filesystem: `startsWith('..')` on a lexical `relative()` misses a symlink
|
|
9
|
+
* inside the root that points out, refuses a member named `..cache`, and is
|
|
10
|
+
* blind to a drive letter — the sweep behind this module watched all three
|
|
11
|
+
* happen at exit 0.
|
|
12
|
+
*
|
|
13
|
+
* Both sides are canonicalized the same way, through the deepest existing
|
|
14
|
+
* ancestor, so a root reached through a symlink (macOS `/tmp`, a linked
|
|
15
|
+
* `~/.claude`) still contains its members, and a candidate that does not exist
|
|
16
|
+
* yet is judged by where creating it would land. Absence is reported as its
|
|
17
|
+
* own answer rather than folded into either side, because a delete sink and a
|
|
18
|
+
* create sink want opposite things from it.
|
|
19
|
+
*
|
|
20
|
+
* Lexical classification of an already-relative path — a projection identity,
|
|
21
|
+
* a report relativizer — is a different question with a different helper:
|
|
22
|
+
* `relativeEscapesRoot` in `path-core.ts`, which touches no filesystem.
|
|
23
|
+
*/
|
|
24
|
+
import { lstatSync } from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { isPathAbsentError } from './errors/errno.js';
|
|
27
|
+
import { normalizePath, safePath, toForwardSlash } from './path-utils.js';
|
|
28
|
+
/**
|
|
29
|
+
* The canonical spelling of `target`, from its deepest existing ancestor.
|
|
30
|
+
*
|
|
31
|
+
* Walks up until `lstat` answers, canonicalizes THAT with the same realpath
|
|
32
|
+
* every path helper uses, and re-appends the missing tail. A refusal is not
|
|
33
|
+
* an absence: `EACCES` on an ancestor, `ELOOP` on a cycle, an invalid name —
|
|
34
|
+
* the OS is saying it cannot examine the path, and a containment verdict
|
|
35
|
+
* built on the spelling it refused is the bug this module exists to remove.
|
|
36
|
+
* Those stay loud.
|
|
37
|
+
*
|
|
38
|
+
* `lstat` rather than `stat`, so a dangling symlink counts as existing: it is
|
|
39
|
+
* an entry the sink can act on (a delete removes the link), and its realpath
|
|
40
|
+
* failing is answered by {@link normalizePath} with the lexical spelling, which
|
|
41
|
+
* is where the entry is.
|
|
42
|
+
*/
|
|
43
|
+
function canonicalFromAncestor(target) {
|
|
44
|
+
const absolute = safePath.resolve(target);
|
|
45
|
+
const missing = [];
|
|
46
|
+
let candidate = absolute;
|
|
47
|
+
for (;;) {
|
|
48
|
+
if (entryExists(candidate)) {
|
|
49
|
+
return {
|
|
50
|
+
canonical: safePath.join(toForwardSlash(normalizePath(candidate)), ...missing),
|
|
51
|
+
exists: missing.length === 0,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const parent = toForwardSlash(path.dirname(candidate));
|
|
55
|
+
// Fixpoint at a filesystem root: `dirname` returns its own input, nothing
|
|
56
|
+
// on the path resolved, and the lexical form is the only answer there is.
|
|
57
|
+
if (parent === candidate) {
|
|
58
|
+
return { canonical: absolute, exists: false };
|
|
59
|
+
}
|
|
60
|
+
missing.unshift(path.basename(candidate));
|
|
61
|
+
candidate = parent;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Whether an entry (a file, a directory, or a link — dangling or not) is at `p`. */
|
|
65
|
+
function entryExists(p) {
|
|
66
|
+
try {
|
|
67
|
+
lstatSync(p);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (isPathAbsentError(error))
|
|
72
|
+
return false;
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Is `candidate` strictly under `root`, as the filesystem judges it?
|
|
78
|
+
*
|
|
79
|
+
* Both paths may be relative (resolved from cwd) or absolute, in either
|
|
80
|
+
* separator; neither has to exist. See {@link Containment} for the three
|
|
81
|
+
* answers and which sinks accept which.
|
|
82
|
+
*
|
|
83
|
+
* ⚠️ Costs one `lstat` per missing component plus two `realpath`s. That is
|
|
84
|
+
* nothing for a sink — a delete or a copy dwarfs it — and too much for a loop
|
|
85
|
+
* over every link in a corpus, which is why the projection and link lanes
|
|
86
|
+
* classify lexically with `relativeEscapesRoot` and never call this.
|
|
87
|
+
*
|
|
88
|
+
* @param root - The directory the action must stay inside
|
|
89
|
+
* @param candidate - The path the action is about to touch
|
|
90
|
+
* @returns `'inside'`, `'outside'`, or `'absent'`
|
|
91
|
+
* @throws When the OS refuses to examine either path for any reason but
|
|
92
|
+
* absence — a refusal is reported, never read as "not there"
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // A delete sink: only an existing, contained target may go.
|
|
96
|
+
* if (isUnderRoot(skillsDir, target) !== 'inside') throw new Error(`refusing to remove ${target}: not inside ${skillsDir}`);
|
|
97
|
+
* await rm(target, { recursive: true, force: true });
|
|
98
|
+
*
|
|
99
|
+
* // A create sink: absent-but-contained is exactly the happy path.
|
|
100
|
+
* if (isUnderRoot(skillsDir, dest) === 'outside') throw new Error(`refusing to install to ${dest}: not inside ${skillsDir}`);
|
|
101
|
+
*/
|
|
102
|
+
export function isUnderRoot(root, candidate) {
|
|
103
|
+
const rootReal = canonicalFromAncestor(root).canonical;
|
|
104
|
+
const target = canonicalFromAncestor(candidate);
|
|
105
|
+
const prefix = rootReal.endsWith('/') ? rootReal : `${rootReal}/`;
|
|
106
|
+
if (!target.canonical.startsWith(prefix)) {
|
|
107
|
+
return 'outside';
|
|
108
|
+
}
|
|
109
|
+
return target.exists ? 'inside' : 'absent';
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=path-containment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-containment.js","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAgB1E;;;;;;;;;;;;;;GAcG;AACH,SAAS,qBAAqB,CAAC,MAAc;IAC3C,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,SAAS,GAAG,QAAQ,CAAC;IACzB,SAAS,CAAC;QACR,IAAI,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACL,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC;gBAC9E,MAAM,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;aAC7B,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QACvD,0EAA0E;QAC1E,0EAA0E;QAC1E,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAChD,CAAC;QACD,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;QAC1C,SAAS,GAAG,MAAM,CAAC;IACrB,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC;QACH,SAAS,CAAC,CAAC,CAAC,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,iBAAiB,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAC3C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,SAAiB;IACzD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;IACvD,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,GAAG,CAAC;IAClE,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC7C,CAAC"}
|