@vibe-agent-toolkit/utils 0.2.0-rc.7 → 0.2.0-rc.9
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 +24 -4
- package/dist/asset-reference.js +3 -11
- package/dist/asset-reference.js.map +1 -1
- package/dist/crawl-timing.d.ts +3 -3
- package/dist/crawl-timing.js +3 -3
- 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/env-flag.d.ts +6 -1
- package/dist/env-flag.d.ts.map +1 -1
- package/dist/env-flag.js +6 -1
- package/dist/env-flag.js.map +1 -1
- 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.js +9 -9
- 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 +16 -18
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +56 -80
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +2 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +2 -1
- package/dist/fs.js.map +1 -1
- package/dist/git-run.d.ts +1 -1
- package/dist/git-run.js +1 -1
- package/dist/git-snapshot.d.ts +18 -0
- package/dist/git-snapshot.d.ts.map +1 -1
- package/dist/git-snapshot.js +18 -0
- package/dist/git-snapshot.js.map +1 -1
- package/dist/git-tracker.d.ts +1 -1
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +1 -2
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +0 -1
- 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 +9 -3
- 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 +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/listing-refusal.d.ts +2 -1
- package/dist/listing-refusal.d.ts.map +1 -1
- package/dist/listing-refusal.js +3 -3
- package/dist/listing-refusal.js.map +1 -1
- 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 +112 -17
- package/dist/path-core.d.ts.map +1 -1
- package/dist/path-core.js +138 -22
- 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/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +9 -6
- package/dist/project-utils.js.map +1 -1
- package/dist/safe-exec.d.ts +2 -1
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +25 -7
- 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/spawn-claude.d.ts +2 -1
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +20 -3
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/skill-test/transcript.d.ts.map +1 -1
- package/dist/skill-test/transcript.js +18 -17
- package/dist/skill-test/transcript.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 +47 -204
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +105 -310
- 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 +5 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +10 -1
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts.map +1 -1
- package/dist/text-content.js +7 -1
- package/dist/text-content.js.map +1 -1
- package/dist/text-file.d.ts.map +1 -1
- package/dist/text-file.js +0 -2
- package/dist/text-file.js.map +1 -1
- package/dist/timing-dump.d.ts +1 -1
- package/dist/timing-dump.d.ts.map +1 -1
- package/dist/timing-dump.js +1 -2
- package/dist/timing-dump.js.map +1 -1
- 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 +126 -68
- package/eslint/index.cjs +111 -162
- package/eslint/index.d.cts +14 -17
- 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 +16 -3
- 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 +14 -1
- 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 +12 -2
- package/eslint/rules/no-raw-node-path.cjs +563 -0
- package/eslint/rules/no-raw-text-decode.cjs +10 -1
- package/eslint/rules/no-registry-count-pin.cjs +255 -0
- package/eslint/rules/no-self-package-import.cjs +13 -2
- 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 +4 -9
- 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/README.md
CHANGED
|
@@ -24,12 +24,12 @@ The last two columns are the ones that matter when choosing. **"Resolves with ze
|
|
|
24
24
|
|
|
25
25
|
| Subpath | Contents | Node builtins reached | Third-party | Resolves with zero deps installed? |
|
|
26
26
|
|---|---|---|---|---|
|
|
27
|
-
| `./path` | `safePath`, `toForwardSlash`, `toNfc`, `isAbsolutePath`, `isAbsoluteAnyPlatform`, `hasParentTraversalSegment`, `toAbsolutePath`, `getRelativePath`, `issueLocation` | `path` only | — | **yes** |
|
|
27
|
+
| `./path` | `safePath`, `toForwardSlash`, `toForwardSlashAnyPlatform`, `toNfc`, `isAbsolutePath`, `isAbsoluteAnyPlatform`, `hasParentTraversalSegment`, `relativeEscapesRoot`, `isSingleFsSegment`, `toAbsolutePath`, `getRelativePath`, `issueLocation` | `path` only | — | **yes** |
|
|
28
28
|
| `./text` | `decodeTextContent` — the one bytes-to-text seam: BOM-announced UTF-8/UTF-16LE/UTF-16BE/UTF-32LE/UTF-32BE, BOM stripped, UTF-8 assumed otherwise; reports the encoding, whether it was a BOM fact or an assumption, and how many U+FFFD the decode substituted | **none** | — | **yes** |
|
|
29
29
|
| `./zod` | `ZodTypeNames`, `getZodTypeName`, `isZodType`, `unwrapZodType`, `isZodOptional`, `isZodNullable` | **none** | — | **yes** |
|
|
30
30
|
| `./glob` | `isGlob`, static base extraction, magic remainder | `path` only | — | **yes** |
|
|
31
31
|
| `./fs` | `normalizePath`, `normalizedTmpdir`, `mkdirSyncReal`, `resolveFromImportMeta`, `dynamicImportPath`, `copyDirectory`, `fillPathSpellings`, `pathSpellingFrom`, `DirectorySpellingIndex`, `spellingWalkRoot`, `fillRealpaths`, `realpathFrom`, `FsLookupCache`, `readTextContent`, `readTextContentSync` | `fs`, `fs/promises`, `os`, `path`, `url`, `util` | — | **yes** |
|
|
32
|
-
| `./testing` | `getTestOutputDir`, `getTestOutputBase`, `setupAsyncTempDirSuite`, `setupSyncTempDirSuite`, `removeScratchDir`, `symlinkCapability`, `createSymlink`, `createSymlinkAsync` | `crypto`, `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
|
|
32
|
+
| `./testing` | `getTestOutputDir`, `getTestOutputBase`, `setupAsyncTempDirSuite`, `setupSyncTempDirSuite`, `removeScratchDir`, `createTempDir`, `removeTempDir`, `tempDirTracker`, `CANNOT_DENY_READS`, `buildHostileTree`, `HOSTILE_NAMES`, `symlinkCapability`, `createSymlink`, `createSymlinkAsync` | `crypto`, `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
|
|
33
33
|
| `./asset` | `resolveAssetReference` — paths and npm bare specifiers | `fs`, `module`, `os`, `path`, `url` | — | **yes** |
|
|
34
34
|
| `./yaml` | `updateYamlIn`, `verifyConfinedYamlEdit` — byte-surgical YAML edits | **none** | `yaml` | no — needs `yaml` |
|
|
35
35
|
| `./process` | `safeExecSync`, `safeExecResult`, `safeExecFromString`, `isToolAvailable`, `getToolVersion`, `hasShellSyntax`, `CommandExecutionError`, `spawnHardened`, `shouldUseShell`, `windowsShellQuote`, `buildWindowsShellLine`, `resolveShellCommandToken`, `isPathLike`, `makeStdioBlocking`, `describeStdioBlocking` | `child_process`, `path` | `@vibe-validate/git`, `which` | no — needs both |
|
|
@@ -128,12 +128,15 @@ FUNCTION, LAZY, PROMISE, and more...
|
|
|
128
128
|
These always return forward slashes on every platform, so they are safe for comparisons, `Map` keys, globs, and display.
|
|
129
129
|
|
|
130
130
|
- `safePath.join()` / `.resolve()` / `.relative()` - forward-slash equivalents of the `node:path` functions
|
|
131
|
-
- `toForwardSlash()` -
|
|
131
|
+
- `toForwardSlash()` - converter for a NATIVE path (fs, `path.*`, git output): converts only where the host separator is a backslash (win32); on POSIX a backslash is a filename character and is kept
|
|
132
|
+
- `toForwardSlashAnyPlatform()` - converter for AUTHOR-WRITTEN text (hrefs, globs, config values, CLI arguments, archive entry names): converts every backslash on every host. Never for a path read from disk — on POSIX it would turn the one file `docs/x\y.md` into a phantom `docs/x/y.md`
|
|
132
133
|
- `toNfc()` - Unicode-NFC normalizer for filename **comparison keys** (see the warning below)
|
|
133
134
|
- `toAbsolutePath()` - resolve a path relative to a base directory
|
|
134
135
|
- `getRelativePath()` - relative path between two absolute paths
|
|
135
136
|
- `isAbsolutePath()` / `isAbsoluteAnyPlatform()` - absolute-path predicates
|
|
136
137
|
- `hasParentTraversalSegment()` - detect `..` segments before using a caller-supplied path
|
|
138
|
+
- `relativeEscapesRoot()` - classify a root-relative path (`safePath.relative(root, p)`) as escaping the root: `..`, `../x`, or an absolute answer (Windows cross-drive). Lexical, for identities and reports; a sink asks `isUnderRoot()` instead
|
|
139
|
+
- `isSingleFsSegment()` - true when a caller-supplied NAME can only ever be one entry directly under whatever it is joined to (no separators, not `.`/`..`, no NUL, no drive spelling). `..cache` passes; `../victim` does not
|
|
137
140
|
- `issueLocation()` - format a `file:line`-style location relative to a project root
|
|
138
141
|
|
|
139
142
|
⚠️ **`toNfc()` produces a comparison key, never a path to open.** The same visible filename has two
|
|
@@ -158,7 +161,11 @@ These return **OS-native** separators, because they resolve real filesystem iden
|
|
|
158
161
|
- `mkdirSyncReal()` - create a directory and return its real path
|
|
159
162
|
- `resolveFromImportMeta()` - resolve paths relative to an `import.meta.url`
|
|
160
163
|
- `dynamicImportPath()` - `import()` an absolute path (works on Windows, which rejects bare absolute paths)
|
|
161
|
-
- `copyDirectory()`
|
|
164
|
+
- `copyDirectory()` - recursive copy that follows a symlinked directory into the tree it points at — contained: a link whose target is outside the source throws `CopyLinkEscapesSourceError`, and a link back into the tree throws `DirectoryWalkRevisitedError`
|
|
165
|
+
- `isUnderRoot(root, candidate)` - the containment verdict a delete/copy/uninstall sink asks, answered by the filesystem: `'inside'` (a strict descendant, realpath-judged), `'outside'` (including the root itself and any symlink that leaves it), or `'absent'` (nothing there yet, and creating it would land inside). Both sides are canonicalized from their deepest existing ancestor, so a root reached through a symlink still contains its members; a refused path throws rather than reading as absent
|
|
166
|
+
- `direntKind(entry)` - what a `Dirent` itself is: `'file' | 'directory' | 'symlink' | 'other'` — the link question asked first, for walks that must not follow (a delete, a size count)
|
|
167
|
+
- `direntKindFollowing(dir, entry)` / `direntKindFollowingSync()` - what a `Dirent` resolves to, one `stat` for a link: `'file' | 'directory' | 'dangling' | 'other'` — for walks over a trusted tree where a link is how the entry got there. Both exist because `isFile()` and `isDirectory()` are BOTH false for a symlink, so a walk that tests only those drops every link silently (`local/dirent-type-needs-symlink-check` refuses that shape)
|
|
168
|
+
- `FollowedWalk` - the cycle guard every following walk holds: `enter(dir)` on the root and each directory recursed into; a directory reached again under a second spelling (a link back into the tree) throws `DirectoryWalkRevisitedError` instead of recursing until the path length runs out
|
|
162
169
|
- `fillPathSpellings(requests, fsCache)` - pass 1 of the case-exact existence check: walk each
|
|
163
170
|
`{ referrer, target }` from the deepest directory the two share, listing every directory on the way
|
|
164
171
|
down exactly once. The only I/O in the pair
|
|
@@ -199,6 +206,10 @@ These return **OS-native** separators, because they resolve real filesystem iden
|
|
|
199
206
|
### Test helpers — `@vibe-agent-toolkit/utils/testing`
|
|
200
207
|
|
|
201
208
|
- `setupAsyncTempDirSuite()` / `setupSyncTempDirSuite()` - per-suite temp directories with cleanup
|
|
209
|
+
- `createTempDir()` / `createTempDirAsync()` / `removeTempDir()` / `tempDirTracker()` - the per-call temp-directory primitives; `removeTempDir` refuses, by name, anything not inside the host tmpdir as the filesystem sees it
|
|
210
|
+
- `CANNOT_DENY_READS` / `PERMISSIONS_ENFORCED` - whether a `chmod 000` denies anything on this host (Windows and uid 0 read everything); route through `skip()` so the skip is visible
|
|
211
|
+
- `NODE_EXECUTABLE` / `gitExecutable()` / `resolveExecutable(name)` / `executableCandidates(name, platform, pathext)` - absolute paths for the binaries a fixture spawns: the running node, and `git` found by walking `PATH` once for the first regular executable file (`PATHEXT` names on Windows) and cached. A fixture spawns these instead of a bare `'git'` or `'node'`, so no writable directory on `PATH` decides what runs.
|
|
212
|
+
- `buildHostileTree(base)` / `hostileTreePerTest(prefix)` / `HOSTILE_NAMES` - the one hostile fixture every sink is tested against: a root with a member, a `..`-named member, a symlink pointing out, one pointing in, a dangling one, an alias link to the root, an unreadable directory, a 200-character name, plus a sibling `victim/secret.txt` outside the root; and the table of names (`../victim`, `..`, `.`, `''`, `/victim`, `C:\victim`, a NUL byte, …) a sink must refuse. Fields the host cannot build are `null`
|
|
202
213
|
- `getTestOutputDir()` / `getTestOutputBase()` - isolated test output paths
|
|
203
214
|
- `symlinkCapability()` - probes once (memoized per process) whether this host can create symlinks (Windows needs Developer Mode or `SeCreateSymbolicLinkPrivilege`), returning a `SymlinkCapability` token or `null`
|
|
204
215
|
- `createSymlink()` / `createSymlinkAsync()` - the sanctioned way to create a symlink in test code; both require a `SymlinkCapability` from `symlinkCapability()`, so a test cannot reach the raw syscall without first proving the host supports it (or explicitly skipping via vitest's `skip()`)
|
|
@@ -215,6 +226,15 @@ These are CLI-boundary functions: inner libraries should take a root as a parame
|
|
|
215
226
|
|
|
216
227
|
They are nonetheless on their own [`./project`](#import-narrowly) entry rather than the barrel alone. The entry was briefly withdrawn on the grounds that the functions fit few repos — which is true, and is what the paragraph above says — but that answered the wrong question. What decides whether an *entry* exists is how heavy the only remaining door is, and barrel-only these four once cost five third-party packages to reach while their own code imports nothing but `node:fs` and `node:path`. Publishing the entry is not a claim that the ladder fits you — only that finding out shouldn't cost a dependency graph. The barrel is dependency-free now, which is that same rule applied everywhere rather than a reason to fold `./project` back in.
|
|
217
228
|
|
|
229
|
+
### Errors — `@vibe-agent-toolkit/utils` (barrel only)
|
|
230
|
+
|
|
231
|
+
- `VatError` - the base of every error VAT throws on purpose: `new VatError(code, message, { cause })`. `code` is a stable `SCREAMING_SNAKE` identity a catch block dispatches on; `name` is taken from the subclass.
|
|
232
|
+
- `isVatError(error, code?)` - the dispatch predicate. Reads a `Symbol.for('vat.error')` brand plus `code`, never the prototype chain, so a `dist` copy of a class still matches a `src` instance. A foreign error carrying a `code` (every `node:fs` errno) answers `false`.
|
|
233
|
+
- `prefixMessageOnce(error, prefix)` - prefix a message in place, once per error object — for a seam a retried item passes through more than once, without re-reading the message to find out.
|
|
234
|
+
- `PathEscapesRootError` (on `./path` too) - what `safePath.joinUnderRoot` throws; recognise it with `isVatError(error, PathEscapesRootError.code)`.
|
|
235
|
+
|
|
236
|
+
Never dispatch on `error.message` — the repo's ESLint config refuses `.message.includes(…)` and friends under `packages/*/src`. Prose is for humans and changes when it is improved; a code changes only when the meaning does.
|
|
237
|
+
|
|
218
238
|
### Directory crawling — `@vibe-agent-toolkit/utils/crawl`
|
|
219
239
|
|
|
220
240
|
- `crawlDirectory()` / `crawlDirectorySync()` - gitignore-aware directory walks
|
package/dist/asset-reference.js
CHANGED
|
@@ -74,8 +74,9 @@ function formatActionableError(specifier, baseDir, cause) {
|
|
|
74
74
|
// but a build step in the target package didn't run (or produced different
|
|
75
75
|
// output). Name the missing file explicitly and point at the publisher.
|
|
76
76
|
if (code === 'MODULE_NOT_FOUND' && missingPath && missingPath !== specifier && isAbsolutePath(missingPath)) {
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
// `existsSync` answers false for every failure and never throws, so no
|
|
78
|
+
// guard around it: the question here is only whether the file is there.
|
|
79
|
+
if (!existsSync(missingPath)) {
|
|
79
80
|
return (`Failed to resolve asset reference '${specifier}': ` +
|
|
80
81
|
`the package's "exports" map points to '${missingPath}', but that file does not exist on disk.\n` +
|
|
81
82
|
`Hint: the target package was found, but a build step did not produce this file. ` +
|
|
@@ -108,15 +109,6 @@ function extractMissingModulePath(cause) {
|
|
|
108
109
|
const match = CANNOT_FIND_MODULE_RE.exec(cause.message);
|
|
109
110
|
return match?.[1];
|
|
110
111
|
}
|
|
111
|
-
function safeExistsSync(filePath) {
|
|
112
|
-
try {
|
|
113
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- filePath is a Node-resolved exports target, used only to refine the error message
|
|
114
|
-
return existsSync(filePath);
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
112
|
function isBareSpecifier(value) {
|
|
121
113
|
return BARE_SPECIFIER_RE.test(value);
|
|
122
114
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-reference.js","sourceRoot":"","sources":["../src/asset-reference.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3D,uEAAuE;AACvE,wEAAwE;AACxE,uEAAuE;AACvE,cAAc;AACd,MAAM,iBAAiB,GAAG,8CAA8C,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAiB,EAAE,OAAe;IACtE,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,EAAE,CAAC;QAChC,OAAO,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,SAAS,GAAG,aAAa,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAE5F,IAAI,CAAC;QACH,OAAO,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,qEAAqE;QACrE,qEAAqE;QACrE,oEAAoE;QACpE,qBAAqB;QACrB,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1D,OAAO,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC9C,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,KAAc,EAAE,CAAC,CAAC;IAC/F,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,SAAiB,EAAE,OAAe,EAAE,KAAc;IAC/E,MAAM,QAAQ,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAI,KAAkC,EAAE,IAAI,CAAC;IACvD,MAAM,WAAW,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAEpD,wEAAwE;IACxE,wEAAwE;IACxE,2EAA2E;IAC3E,2EAA2E;IAC3E,wEAAwE;IACxE,IAAI,IAAI,KAAK,kBAAkB,IAAI,WAAW,IAAI,WAAW,KAAK,SAAS,IAAI,cAAc,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3G,
|
|
1
|
+
{"version":3,"file":"asset-reference.js","sourceRoot":"","sources":["../src/asset-reference.ts"],"names":[],"mappings":"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"}
|
package/dist/crawl-timing.d.ts
CHANGED
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
*
|
|
116
116
|
* ⚠️ A rollup that sums a stratum's rows without regard to pass double-counts
|
|
117
117
|
* every nested bracket. That is a real reading hazard, not a hypothetical: it is
|
|
118
|
-
* what `packages/lab/src/facets/crawl/dump.ts`
|
|
118
|
+
* what `packages/lab/src/facets/crawl/dump.ts` used to do, and it
|
|
119
119
|
* inflated the two arms by DIFFERENT factors, because they nest to different
|
|
120
120
|
* depths. That reader now implements the rule above — `crawlRowRole` there is
|
|
121
121
|
* the executable copy of it — so anyone adding a bracket to this seam should
|
|
@@ -204,8 +204,8 @@
|
|
|
204
204
|
* It carries the process's own wall and CPU lifetime, like the parse dump, and
|
|
205
205
|
* for the same reason: these brackets are wall-timed, so a reader has to be able
|
|
206
206
|
* to see that the process spent its life waiting. It does **not** invite that
|
|
207
|
-
* figure to be summed across processes. `parse-timing.ts`'s review finding
|
|
208
|
-
*
|
|
207
|
+
* figure to be summed across processes. `parse-timing.ts`'s review finding
|
|
208
|
+
* records that the lab sums `process.wallMs` across dumps, which
|
|
209
209
|
* double-counts real time under a multi-process verb because the parent
|
|
210
210
|
* orchestrator's lifetime contains every child's. The reader for THIS dump
|
|
211
211
|
* publishes one lifetime per process and never a total — see
|
package/dist/crawl-timing.js
CHANGED
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
*
|
|
116
116
|
* ⚠️ A rollup that sums a stratum's rows without regard to pass double-counts
|
|
117
117
|
* every nested bracket. That is a real reading hazard, not a hypothetical: it is
|
|
118
|
-
* what `packages/lab/src/facets/crawl/dump.ts`
|
|
118
|
+
* what `packages/lab/src/facets/crawl/dump.ts` used to do, and it
|
|
119
119
|
* inflated the two arms by DIFFERENT factors, because they nest to different
|
|
120
120
|
* depths. That reader now implements the rule above — `crawlRowRole` there is
|
|
121
121
|
* the executable copy of it — so anyone adding a bracket to this seam should
|
|
@@ -204,8 +204,8 @@
|
|
|
204
204
|
* It carries the process's own wall and CPU lifetime, like the parse dump, and
|
|
205
205
|
* for the same reason: these brackets are wall-timed, so a reader has to be able
|
|
206
206
|
* to see that the process spent its life waiting. It does **not** invite that
|
|
207
|
-
* figure to be summed across processes. `parse-timing.ts`'s review finding
|
|
208
|
-
*
|
|
207
|
+
* figure to be summed across processes. `parse-timing.ts`'s review finding
|
|
208
|
+
* records that the lab sums `process.wallMs` across dumps, which
|
|
209
209
|
* double-counts real time under a multi-process verb because the parent
|
|
210
210
|
* orchestrator's lifetime contains every child's. The reader for THIS dump
|
|
211
211
|
* publishes one lifetime per process and never a total — see
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a directory entry IS — with the symlink question asked first.
|
|
3
|
+
*
|
|
4
|
+
* A `Dirent` from `readdir(…, { withFileTypes: true })` describes the entry
|
|
5
|
+
* itself, so for a symlink BOTH `isFile()` and `isDirectory()` are false. A
|
|
6
|
+
* walk written `if (e.isDirectory()) recurse(); else if (e.isFile()) read();`
|
|
7
|
+
* therefore neither refuses a link nor follows it: it drops the entry on the
|
|
8
|
+
* floor without a word. The sweep found staged skill trees, size accounting
|
|
9
|
+
* and packaging walks with exactly that shape, every one reporting a tree
|
|
10
|
+
* with a symlink in it as clean. `local/dirent-type-needs-symlink-check`
|
|
11
|
+
* refuses that shape; these two helpers are what a walk says instead, and
|
|
12
|
+
* each makes the caller pick a policy by name.
|
|
13
|
+
*
|
|
14
|
+
* - {@link direntKind} — lstat semantics. A link is `'symlink'`, never what it
|
|
15
|
+
* points at. For a walk that must NOT follow (a delete, a size count of
|
|
16
|
+
* what `rm -rf` will remove, a bundle that travels as a tarball).
|
|
17
|
+
* - {@link direntKindFollowing} — stat semantics. A link is answered by its
|
|
18
|
+
* target: `'file'`, `'directory'`, or `'dangling'`. For a walk over a tree
|
|
19
|
+
* the caller trusts (its own build output, a dev-mode install, a fixture),
|
|
20
|
+
* where a link is simply how the entry got there.
|
|
21
|
+
*
|
|
22
|
+
* Neither helper decides for the caller. A `'symlink'` or `'dangling'` that
|
|
23
|
+
* the caller ignores is still ignored — but it is ignored in a `case` the
|
|
24
|
+
* reader can see, which is the whole difference.
|
|
25
|
+
*/
|
|
26
|
+
import type { Dirent } from 'node:fs';
|
|
27
|
+
import { VatError } from './errors/vat-error.js';
|
|
28
|
+
/** What the entry itself is. A link is a link. */
|
|
29
|
+
export type DirentKind = 'file' | 'directory' | 'symlink' | 'other';
|
|
30
|
+
/** What the entry resolves to. A link is answered by its target, or is `dangling`. */
|
|
31
|
+
export type FollowedKind = 'file' | 'directory' | 'dangling' | 'other';
|
|
32
|
+
/**
|
|
33
|
+
* Classify an entry WITHOUT following it.
|
|
34
|
+
*
|
|
35
|
+
* @param entry - A `Dirent` from a `withFileTypes` listing
|
|
36
|
+
* @returns `'symlink'` for any link, else the entry's own type
|
|
37
|
+
*/
|
|
38
|
+
export declare function direntKind(entry: Dirent): DirentKind;
|
|
39
|
+
/**
|
|
40
|
+
* Classify an entry by what it resolves to, following a link with one `stat`.
|
|
41
|
+
*
|
|
42
|
+
* A non-link entry costs no syscall. A link whose target is absent is
|
|
43
|
+
* `'dangling'`; a link the OS refuses to resolve (`ELOOP`, `EACCES`) is a
|
|
44
|
+
* refusal, not an absence, and stays loud.
|
|
45
|
+
*
|
|
46
|
+
* @param dir - The directory `entry` was listed from
|
|
47
|
+
* @param entry - A `Dirent` from a `withFileTypes` listing of `dir`
|
|
48
|
+
* @returns The target's kind, or `'dangling'`
|
|
49
|
+
*/
|
|
50
|
+
export declare function direntKindFollowingSync(dir: string, entry: Dirent): FollowedKind;
|
|
51
|
+
/** The async counterpart of {@link direntKindFollowingSync}. */
|
|
52
|
+
export declare function direntKindFollowing(dir: string, entry: Dirent): Promise<FollowedKind>;
|
|
53
|
+
/** Thrown when a following walk is led back into a directory it has already entered. */
|
|
54
|
+
export declare class DirectoryWalkRevisitedError extends VatError {
|
|
55
|
+
constructor(dir: string, enteredAs: string);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The directories a FOLLOWING walk has entered, by realpath, so a link that
|
|
59
|
+
* leads back into the walk is refused instead of recursed until
|
|
60
|
+
* `ENAMETOOLONG`.
|
|
61
|
+
*
|
|
62
|
+
* A walk that follows links (`direntKindFollowing`) has no cycle guard by
|
|
63
|
+
* construction: `scripts/loop -> .` makes a copy create `dest/loop/loop/…`,
|
|
64
|
+
* writing every file at every level first, and a hashing walk do the same in
|
|
65
|
+
* memory. Every following walk holds one of these and calls {@link enter}
|
|
66
|
+
* on every directory it recurses into — the root included. A REVISIT is
|
|
67
|
+
* refused, not just a cycle: two links to one directory make the walk's
|
|
68
|
+
* output ambiguous (which spelling is the file's path?), and a refusal that
|
|
69
|
+
* names both spellings is the answer the author can act on.
|
|
70
|
+
*/
|
|
71
|
+
export declare class FollowedWalk {
|
|
72
|
+
#private;
|
|
73
|
+
/**
|
|
74
|
+
* Record that the walk is entering `dir`. Synchronous on purpose — one
|
|
75
|
+
* realpath per directory entered is nothing beside the listing itself, and
|
|
76
|
+
* the sync and async walkers then share one guard.
|
|
77
|
+
*
|
|
78
|
+
* @param dir - A directory the walk is about to list, in any spelling
|
|
79
|
+
* @throws {DirectoryWalkRevisitedError} When its realpath was entered before
|
|
80
|
+
*/
|
|
81
|
+
enter(dir: string): void;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=dirent-kind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dirent-kind.d.ts","sourceRoot":"","sources":["../src/dirent-kind.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAKtC,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAIjD,kDAAkD;AAClD,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpE,sFAAsF;AACtF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAKpD;AASD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAQhF;AAED,gEAAgE;AAChE,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAQ3F;AAED,wFAAwF;AACxF,qBAAa,2BAA4B,SAAQ,QAAQ;gBAC3C,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;CAM3C;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,YAAY;;IAGvB;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAMzB"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a directory entry IS — with the symlink question asked first.
|
|
3
|
+
*
|
|
4
|
+
* A `Dirent` from `readdir(…, { withFileTypes: true })` describes the entry
|
|
5
|
+
* itself, so for a symlink BOTH `isFile()` and `isDirectory()` are false. A
|
|
6
|
+
* walk written `if (e.isDirectory()) recurse(); else if (e.isFile()) read();`
|
|
7
|
+
* therefore neither refuses a link nor follows it: it drops the entry on the
|
|
8
|
+
* floor without a word. The sweep found staged skill trees, size accounting
|
|
9
|
+
* and packaging walks with exactly that shape, every one reporting a tree
|
|
10
|
+
* with a symlink in it as clean. `local/dirent-type-needs-symlink-check`
|
|
11
|
+
* refuses that shape; these two helpers are what a walk says instead, and
|
|
12
|
+
* each makes the caller pick a policy by name.
|
|
13
|
+
*
|
|
14
|
+
* - {@link direntKind} — lstat semantics. A link is `'symlink'`, never what it
|
|
15
|
+
* points at. For a walk that must NOT follow (a delete, a size count of
|
|
16
|
+
* what `rm -rf` will remove, a bundle that travels as a tarball).
|
|
17
|
+
* - {@link direntKindFollowing} — stat semantics. A link is answered by its
|
|
18
|
+
* target: `'file'`, `'directory'`, or `'dangling'`. For a walk over a tree
|
|
19
|
+
* the caller trusts (its own build output, a dev-mode install, a fixture),
|
|
20
|
+
* where a link is simply how the entry got there.
|
|
21
|
+
*
|
|
22
|
+
* Neither helper decides for the caller. A `'symlink'` or `'dangling'` that
|
|
23
|
+
* the caller ignores is still ignored — but it is ignored in a `case` the
|
|
24
|
+
* reader can see, which is the whole difference.
|
|
25
|
+
*/
|
|
26
|
+
import { statSync } from 'node:fs';
|
|
27
|
+
import fs from 'node:fs/promises';
|
|
28
|
+
import { isPathAbsentError } from './errors/errno.js';
|
|
29
|
+
import { VatError } from './errors/vat-error.js';
|
|
30
|
+
import { toForwardSlash } from './path-core.js';
|
|
31
|
+
import { normalizePath, safePath } from './path-utils.js';
|
|
32
|
+
/**
|
|
33
|
+
* Classify an entry WITHOUT following it.
|
|
34
|
+
*
|
|
35
|
+
* @param entry - A `Dirent` from a `withFileTypes` listing
|
|
36
|
+
* @returns `'symlink'` for any link, else the entry's own type
|
|
37
|
+
*/
|
|
38
|
+
export function direntKind(entry) {
|
|
39
|
+
if (entry.isSymbolicLink())
|
|
40
|
+
return 'symlink';
|
|
41
|
+
if (entry.isDirectory())
|
|
42
|
+
return 'directory';
|
|
43
|
+
if (entry.isFile())
|
|
44
|
+
return 'file';
|
|
45
|
+
return 'other';
|
|
46
|
+
}
|
|
47
|
+
/** The kind a `stat` result reports. */
|
|
48
|
+
function kindOfStats(stats) {
|
|
49
|
+
if (stats.isDirectory())
|
|
50
|
+
return 'directory';
|
|
51
|
+
if (stats.isFile())
|
|
52
|
+
return 'file';
|
|
53
|
+
return 'other';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Classify an entry by what it resolves to, following a link with one `stat`.
|
|
57
|
+
*
|
|
58
|
+
* A non-link entry costs no syscall. A link whose target is absent is
|
|
59
|
+
* `'dangling'`; a link the OS refuses to resolve (`ELOOP`, `EACCES`) is a
|
|
60
|
+
* refusal, not an absence, and stays loud.
|
|
61
|
+
*
|
|
62
|
+
* @param dir - The directory `entry` was listed from
|
|
63
|
+
* @param entry - A `Dirent` from a `withFileTypes` listing of `dir`
|
|
64
|
+
* @returns The target's kind, or `'dangling'`
|
|
65
|
+
*/
|
|
66
|
+
export function direntKindFollowingSync(dir, entry) {
|
|
67
|
+
if (!entry.isSymbolicLink())
|
|
68
|
+
return kindOfStats(entry);
|
|
69
|
+
try {
|
|
70
|
+
return kindOfStats(statSync(safePath.join(dir, entry.name)));
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (isPathAbsentError(error))
|
|
74
|
+
return 'dangling';
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** The async counterpart of {@link direntKindFollowingSync}. */
|
|
79
|
+
export async function direntKindFollowing(dir, entry) {
|
|
80
|
+
if (!entry.isSymbolicLink())
|
|
81
|
+
return kindOfStats(entry);
|
|
82
|
+
try {
|
|
83
|
+
return kindOfStats(await fs.stat(safePath.join(dir, entry.name)));
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (isPathAbsentError(error))
|
|
87
|
+
return 'dangling';
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Thrown when a following walk is led back into a directory it has already entered. */
|
|
92
|
+
export class DirectoryWalkRevisitedError extends VatError {
|
|
93
|
+
constructor(dir, enteredAs) {
|
|
94
|
+
super('DIRECTORY_WALK_REVISITED', `Refusing to enter ${dir}: it is the directory already walked as ${enteredAs} — a symlink leads the walk back into itself.`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The directories a FOLLOWING walk has entered, by realpath, so a link that
|
|
99
|
+
* leads back into the walk is refused instead of recursed until
|
|
100
|
+
* `ENAMETOOLONG`.
|
|
101
|
+
*
|
|
102
|
+
* A walk that follows links (`direntKindFollowing`) has no cycle guard by
|
|
103
|
+
* construction: `scripts/loop -> .` makes a copy create `dest/loop/loop/…`,
|
|
104
|
+
* writing every file at every level first, and a hashing walk do the same in
|
|
105
|
+
* memory. Every following walk holds one of these and calls {@link enter}
|
|
106
|
+
* on every directory it recurses into — the root included. A REVISIT is
|
|
107
|
+
* refused, not just a cycle: two links to one directory make the walk's
|
|
108
|
+
* output ambiguous (which spelling is the file's path?), and a refusal that
|
|
109
|
+
* names both spellings is the answer the author can act on.
|
|
110
|
+
*/
|
|
111
|
+
export class FollowedWalk {
|
|
112
|
+
#entered = new Map();
|
|
113
|
+
/**
|
|
114
|
+
* Record that the walk is entering `dir`. Synchronous on purpose — one
|
|
115
|
+
* realpath per directory entered is nothing beside the listing itself, and
|
|
116
|
+
* the sync and async walkers then share one guard.
|
|
117
|
+
*
|
|
118
|
+
* @param dir - A directory the walk is about to list, in any spelling
|
|
119
|
+
* @throws {DirectoryWalkRevisitedError} When its realpath was entered before
|
|
120
|
+
*/
|
|
121
|
+
enter(dir) {
|
|
122
|
+
const real = toForwardSlash(normalizePath(dir));
|
|
123
|
+
const before = this.#entered.get(real);
|
|
124
|
+
if (before !== undefined)
|
|
125
|
+
throw new DirectoryWalkRevisitedError(dir, before);
|
|
126
|
+
this.#entered.set(real, dir);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=dirent-kind.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dirent-kind.js","sourceRoot":"","sources":["../src/dirent-kind.ts"],"names":[],"mappings":"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"}
|
package/dist/env-flag.d.ts
CHANGED
|
@@ -20,16 +20,21 @@
|
|
|
20
20
|
* needs it rather than speculatively. That second package arrived:
|
|
21
21
|
* `packages/cli`'s `projectionStoreSelected()` reads the same `VAT_CACHE` the
|
|
22
22
|
* `resources` parse cache does, and two independent readings of one variable is
|
|
23
|
-
* the defect, not the fix. The
|
|
23
|
+
* the defect, not the fix. The four consumers today:
|
|
24
24
|
*
|
|
25
25
|
* | Caller | Variable | Reads `undefined` as | Why |
|
|
26
26
|
* |---|---|---|---|
|
|
27
27
|
* | `link-auth/resolve-token.ts` | `VAT_LINKAUTH_ALLOW_COMMAND` | **deny** | gates a capability — fail closed |
|
|
28
28
|
* | `resources/parse-cache.ts` | `VAT_CACHE` | cache stays on | gates a cache — an unreadable value must not silently change behaviour |
|
|
29
29
|
* | `cli/utils/projection-store.ts` | `VAT_CACHE` | not a veto | same variable, same reading, one implementation |
|
|
30
|
+
* | `cli/utils/projection-store.ts` | `VAT_PROJECTION_STORE` | not a veto | the projection store is ON by default; only an explicit off spelling (or an empty value, which that call site handles before asking) turns it off |
|
|
30
31
|
*
|
|
31
32
|
* Same parser, different safe sides, each chosen at its own call site. That is
|
|
32
33
|
* the contract; do not move a default in here.
|
|
34
|
+
*
|
|
35
|
+
* 🔑 The last two rows are the interesting pair: one variable defaults OFF and
|
|
36
|
+
* the other defaults ON, and both are still read through this one function,
|
|
37
|
+
* because what it answers is not "is this on" but "did the operator say no".
|
|
33
38
|
*/
|
|
34
39
|
/**
|
|
35
40
|
* Parse an env value as a boolean.
|
package/dist/env-flag.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-flag.d.ts","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"env-flag.d.ts","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAKH;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAM5E"}
|
package/dist/env-flag.js
CHANGED
|
@@ -20,16 +20,21 @@
|
|
|
20
20
|
* needs it rather than speculatively. That second package arrived:
|
|
21
21
|
* `packages/cli`'s `projectionStoreSelected()` reads the same `VAT_CACHE` the
|
|
22
22
|
* `resources` parse cache does, and two independent readings of one variable is
|
|
23
|
-
* the defect, not the fix. The
|
|
23
|
+
* the defect, not the fix. The four consumers today:
|
|
24
24
|
*
|
|
25
25
|
* | Caller | Variable | Reads `undefined` as | Why |
|
|
26
26
|
* |---|---|---|---|
|
|
27
27
|
* | `link-auth/resolve-token.ts` | `VAT_LINKAUTH_ALLOW_COMMAND` | **deny** | gates a capability — fail closed |
|
|
28
28
|
* | `resources/parse-cache.ts` | `VAT_CACHE` | cache stays on | gates a cache — an unreadable value must not silently change behaviour |
|
|
29
29
|
* | `cli/utils/projection-store.ts` | `VAT_CACHE` | not a veto | same variable, same reading, one implementation |
|
|
30
|
+
* | `cli/utils/projection-store.ts` | `VAT_PROJECTION_STORE` | not a veto | the projection store is ON by default; only an explicit off spelling (or an empty value, which that call site handles before asking) turns it off |
|
|
30
31
|
*
|
|
31
32
|
* Same parser, different safe sides, each chosen at its own call site. That is
|
|
32
33
|
* the contract; do not move a default in here.
|
|
34
|
+
*
|
|
35
|
+
* 🔑 The last two rows are the interesting pair: one variable defaults OFF and
|
|
36
|
+
* the other defaults ON, and both are still read through this one function,
|
|
37
|
+
* because what it answers is not "is this on" but "did the operator say no".
|
|
33
38
|
*/
|
|
34
39
|
const TRUE_SPELLINGS = new Set(['1', 'true', 'yes', 'y', 'on']);
|
|
35
40
|
const FALSE_SPELLINGS = new Set(['0', 'false', 'no', 'n', 'off']);
|
package/dist/env-flag.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-flag.js","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"env-flag.js","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AACrF,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;AAEvF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,GAAuB;IACrD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Errno predicates — the two questions every catch site asks of an OS error,
|
|
3
|
+
* answered by the `code` on the error or down its `cause` chain.
|
|
4
|
+
*
|
|
5
|
+
* ⚠️ This module imports NOTHING. It is the leaf that `fs-utils.ts`,
|
|
6
|
+
* `path-utils.ts`, `dirent-kind.ts`, `path-containment.ts` and the rest of the
|
|
7
|
+
* package share; it lived in `fs-utils.ts` until that put three of them in an
|
|
8
|
+
* import cycle (`fs-utils → path-utils → fs-utils` and two more), and a leaf
|
|
9
|
+
* is the one place a shared predicate cannot re-open one. (The cycle was once
|
|
10
|
+
* blamed for a platform-dependent knip verdict on these two names; breaking it
|
|
11
|
+
* changed nothing there — the cause was a missing knip entry, see
|
|
12
|
+
* `docs/contributing/traps.md`, "A subpath module's re-exports flap by platform".)
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Whether `error` is the filesystem refusing a path rather than a bug.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately NOT `error instanceof Error`: the point of every caller is to
|
|
18
|
+
* degrade on a hostile tree, and a `TypeError` from a validator is not that.
|
|
19
|
+
* Treating one as environmental turns a real defect into a warning about
|
|
20
|
+
* whichever file it happened on — which makes a tool quietest exactly when it is
|
|
21
|
+
* most wrong.
|
|
22
|
+
*
|
|
23
|
+
* Walks `cause`, because the errno is routinely re-wrapped on its way up. The CLI
|
|
24
|
+
* config loader turns a read failure into `new Error('Failed to load config: …')`;
|
|
25
|
+
* without following the chain the predicate answered "not a filesystem error" for
|
|
26
|
+
* a plain `EACCES`, and an unreadable config aborted a whole `vat audit` run. Any
|
|
27
|
+
* layer that adds context to an OS error defeats a `code`-only check, so the check
|
|
28
|
+
* cannot be `code`-only.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isFilesystemAccessError(error: unknown): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether `error` means **there is nothing at this path** — `ENOENT`, or
|
|
33
|
+
* `ENOTDIR` for a path whose component turned out to be a file — and nothing
|
|
34
|
+
* else.
|
|
35
|
+
*
|
|
36
|
+
* This is the narrowing a `try { stat(p) } catch { return null }` is rewritten
|
|
37
|
+
* to under the `no-blind-catch` lint rule: the sentinel stands for *absent*,
|
|
38
|
+
* so only an absence may produce it; a refusal (`EACCES`, `EPERM`, `ELOOP`) or
|
|
39
|
+
* a bug (`TypeError`) is rethrown and stays loud.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ Deliberately NOT {@link isFilesystemAccessError}. That predicate answers
|
|
42
|
+
* "is this the environment's fault?" and to answer it groups `ENOENT` with
|
|
43
|
+
* `EACCES` — the exact conflation that once turned an unreadable directory
|
|
44
|
+
* into an empty one. The two questions have two predicates on purpose; see
|
|
45
|
+
* also {@link listingFailure}, which makes the same split for `readdir`.
|
|
46
|
+
*
|
|
47
|
+
* Walks `cause` for the same reason its sibling does: the errno is routinely
|
|
48
|
+
* re-wrapped on its way up.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isPathAbsentError(error: unknown): boolean;
|
|
51
|
+
//# sourceMappingURL=errno.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errno.d.ts","sourceRoot":"","sources":["../../src/errors/errno.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAwCH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEzD"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Errno predicates — the two questions every catch site asks of an OS error,
|
|
3
|
+
* answered by the `code` on the error or down its `cause` chain.
|
|
4
|
+
*
|
|
5
|
+
* ⚠️ This module imports NOTHING. It is the leaf that `fs-utils.ts`,
|
|
6
|
+
* `path-utils.ts`, `dirent-kind.ts`, `path-containment.ts` and the rest of the
|
|
7
|
+
* package share; it lived in `fs-utils.ts` until that put three of them in an
|
|
8
|
+
* import cycle (`fs-utils → path-utils → fs-utils` and two more), and a leaf
|
|
9
|
+
* is the one place a shared predicate cannot re-open one. (The cycle was once
|
|
10
|
+
* blamed for a platform-dependent knip verdict on these two names; breaking it
|
|
11
|
+
* changed nothing there — the cause was a missing knip entry, see
|
|
12
|
+
* `docs/contributing/traps.md`, "A subpath module's re-exports flap by platform".)
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Errno codes meaning "the filesystem refused this path", as opposed to a defect
|
|
16
|
+
* in our own code.
|
|
17
|
+
*
|
|
18
|
+
* Shared because two lanes need the same answer and must not drift: `vat audit`
|
|
19
|
+
* decides whether to degrade a scan over a tree it does not own, and the skill
|
|
20
|
+
* packager decides whether a `files:` match is copyable. A second, independently
|
|
21
|
+
* written list is how those two come to disagree about what counts as the
|
|
22
|
+
* environment's fault.
|
|
23
|
+
*
|
|
24
|
+
* The set is deliberately broad. An earlier, "conservative" version omitted
|
|
25
|
+
* `ENOTSUP` — the errno of the very issue this was written for — along with
|
|
26
|
+
* `EEXIST`, which an ordinary two-entry `files:` config reaches with no
|
|
27
|
+
* permissions involved at all. Both escaped raw. Every code here means the OS
|
|
28
|
+
* refused a syscall on a path; none of them can be produced by a type error or a
|
|
29
|
+
* logic bug in our own code, which is the only distinction the callers need.
|
|
30
|
+
*
|
|
31
|
+
* `EIO` and `EBUSY` are included even though they can indicate failing hardware:
|
|
32
|
+
* neither caller *swallows* anything, each reports the path and the OS message,
|
|
33
|
+
* so a dying disk surfaces once per affected path. Aborting the run instead would
|
|
34
|
+
* report less. `ENOENT` is included because a bulk scan races real filesystems —
|
|
35
|
+
* an entry listed by `readdir` can be gone by the time it is opened.
|
|
36
|
+
*/
|
|
37
|
+
const FILESYSTEM_ACCESS_ERRNOS = new Set([
|
|
38
|
+
// Permission and ownership
|
|
39
|
+
'EACCES', 'EPERM', 'EROFS',
|
|
40
|
+
// Presence and shape
|
|
41
|
+
'ENOENT', 'EEXIST', 'ENOTDIR', 'EISDIR', 'ENOTEMPTY', 'ELOOP', 'ENAMETOOLONG',
|
|
42
|
+
// Capability of the object or filesystem
|
|
43
|
+
'ENOTSUP', 'EOPNOTSUPP', 'EXDEV', 'ETXTBSY', 'EINVAL',
|
|
44
|
+
// Resource exhaustion and transient device state
|
|
45
|
+
'ENOSPC', 'EDQUOT', 'EMFILE', 'ENFILE', 'EIO', 'EBUSY', 'EAGAIN',
|
|
46
|
+
// Network filesystems
|
|
47
|
+
'ESTALE', 'ETIMEDOUT', 'EHOSTDOWN', 'ENETDOWN',
|
|
48
|
+
// Windows surfaces this for reparse points and some network paths
|
|
49
|
+
'UNKNOWN',
|
|
50
|
+
]);
|
|
51
|
+
/**
|
|
52
|
+
* Whether `error` is the filesystem refusing a path rather than a bug.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately NOT `error instanceof Error`: the point of every caller is to
|
|
55
|
+
* degrade on a hostile tree, and a `TypeError` from a validator is not that.
|
|
56
|
+
* Treating one as environmental turns a real defect into a warning about
|
|
57
|
+
* whichever file it happened on — which makes a tool quietest exactly when it is
|
|
58
|
+
* most wrong.
|
|
59
|
+
*
|
|
60
|
+
* Walks `cause`, because the errno is routinely re-wrapped on its way up. The CLI
|
|
61
|
+
* config loader turns a read failure into `new Error('Failed to load config: …')`;
|
|
62
|
+
* without following the chain the predicate answered "not a filesystem error" for
|
|
63
|
+
* a plain `EACCES`, and an unreadable config aborted a whole `vat audit` run. Any
|
|
64
|
+
* layer that adds context to an OS error defeats a `code`-only check, so the check
|
|
65
|
+
* cannot be `code`-only.
|
|
66
|
+
*/
|
|
67
|
+
export function isFilesystemAccessError(error) {
|
|
68
|
+
return hasErrnoCode(error, (code) => FILESYSTEM_ACCESS_ERRNOS.has(code));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Whether `error` means **there is nothing at this path** — `ENOENT`, or
|
|
72
|
+
* `ENOTDIR` for a path whose component turned out to be a file — and nothing
|
|
73
|
+
* else.
|
|
74
|
+
*
|
|
75
|
+
* This is the narrowing a `try { stat(p) } catch { return null }` is rewritten
|
|
76
|
+
* to under the `no-blind-catch` lint rule: the sentinel stands for *absent*,
|
|
77
|
+
* so only an absence may produce it; a refusal (`EACCES`, `EPERM`, `ELOOP`) or
|
|
78
|
+
* a bug (`TypeError`) is rethrown and stays loud.
|
|
79
|
+
*
|
|
80
|
+
* ⚠️ Deliberately NOT {@link isFilesystemAccessError}. That predicate answers
|
|
81
|
+
* "is this the environment's fault?" and to answer it groups `ENOENT` with
|
|
82
|
+
* `EACCES` — the exact conflation that once turned an unreadable directory
|
|
83
|
+
* into an empty one. The two questions have two predicates on purpose; see
|
|
84
|
+
* also {@link listingFailure}, which makes the same split for `readdir`.
|
|
85
|
+
*
|
|
86
|
+
* Walks `cause` for the same reason its sibling does: the errno is routinely
|
|
87
|
+
* re-wrapped on its way up.
|
|
88
|
+
*/
|
|
89
|
+
export function isPathAbsentError(error) {
|
|
90
|
+
return hasErrnoCode(error, (code) => code === 'ENOENT' || code === 'ENOTDIR');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Whether any string `code` on `error` or down its `cause` chain satisfies
|
|
94
|
+
* `accept`. Bounded: a malformed `cause` chain must not become an infinite
|
|
95
|
+
* loop inside an error path, which is the worst place to hang.
|
|
96
|
+
*/
|
|
97
|
+
function hasErrnoCode(error, accept) {
|
|
98
|
+
for (let current = error, depth = 0; depth < 10; depth++) {
|
|
99
|
+
if (typeof current !== 'object' || current === null)
|
|
100
|
+
return false;
|
|
101
|
+
if ('code' in current) {
|
|
102
|
+
const { code } = current;
|
|
103
|
+
if (typeof code === 'string' && accept(code))
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (!('cause' in current))
|
|
107
|
+
return false;
|
|
108
|
+
current = current.cause;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=errno.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errno.js","sourceRoot":"","sources":["../../src/errors/errno.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,wBAAwB,GAAwB,IAAI,GAAG,CAAC;IAC5D,2BAA2B;IAC3B,QAAQ,EAAE,OAAO,EAAE,OAAO;IAC1B,qBAAqB;IACrB,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc;IAC7E,yCAAyC;IACzC,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ;IACrD,iDAAiD;IACjD,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ;IAChE,sBAAsB;IACtB,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU;IAC9C,kEAAkE;IAClE,SAAS;CACV,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,wBAAwB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,YAAY,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,CAAC,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,KAAc,EAAE,MAAiC;IACrE,KAAK,IAAI,OAAO,GAAY,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC;QAClE,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;QAClE,IAAI,MAAM,IAAI,OAAO,EAAE,CAAC;YACtB,MAAM,EAAE,IAAI,EAAE,GAAG,OAA4B,CAAC;YAC9C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QACxC,OAAO,GAAI,OAA8B,CAAC,KAAK,CAAC;IAClD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|