@vibe-agent-toolkit/utils 0.2.0-rc.6 → 0.2.0-rc.8
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 +43 -9
- 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/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 +46 -0
- package/dist/env-flag.d.ts.map +1 -0
- package/dist/env-flag.js +57 -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 +22 -0
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +83 -22
- 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 -217
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +535 -283
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +3 -2
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +5 -7
- 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-tracker.d.ts +41 -2
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +84 -19
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts +47 -2
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +123 -20
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +1 -0
- package/dist/git.d.ts.map +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/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -22
- 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 +77 -3
- package/dist/path-core.d.ts.map +1 -1
- package/dist/path-core.js +96 -6
- 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 +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 +26 -8
- 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 +56 -193
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +115 -284
- 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 +21 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +27 -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 +132 -60
- package/eslint/index.cjs +111 -147
- package/eslint/index.d.cts +14 -14
- 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 +1 -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 +216 -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 +7 -2
- package/eslint/rules/no-io-in-unit-tier.cjs +128 -0
- package/eslint/rules/no-literal-process-exit.cjs +129 -0
- package/eslint/rules/no-manual-path-normalize.cjs +7 -2
- 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 +5 -10
- 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`, `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
|
-
| `./fs` | `normalizePath`, `normalizedTmpdir`, `mkdirSyncReal`, `resolveFromImportMeta`, `dynamicImportPath`, `copyDirectory`, `
|
|
32
|
-
| `./testing` | `getTestOutputDir`, `getTestOutputBase`, `setupAsyncTempDirSuite`, `setupSyncTempDirSuite`, `removeScratchDir`, `symlinkCapability`, `createSymlink`, `createSymlinkAsync` | `crypto`, `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
|
|
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`, `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 |
|
|
@@ -134,6 +134,8 @@ These always return forward slashes on every platform, so they are safe for comp
|
|
|
134
134
|
- `getRelativePath()` - relative path between two absolute paths
|
|
135
135
|
- `isAbsolutePath()` / `isAbsoluteAnyPlatform()` - absolute-path predicates
|
|
136
136
|
- `hasParentTraversalSegment()` - detect `..` segments before using a caller-supplied path
|
|
137
|
+
- `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
|
|
138
|
+
- `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
139
|
- `issueLocation()` - format a `file:line`-style location relative to a project root
|
|
138
140
|
|
|
139
141
|
⚠️ **`toNfc()` produces a comparison key, never a path to open.** The same visible filename has two
|
|
@@ -158,12 +160,24 @@ These return **OS-native** separators, because they resolve real filesystem iden
|
|
|
158
160
|
- `mkdirSyncReal()` - create a directory and return its real path
|
|
159
161
|
- `resolveFromImportMeta()` - resolve paths relative to an `import.meta.url`
|
|
160
162
|
- `dynamicImportPath()` - `import()` an absolute path (works on Windows, which rejects bare absolute paths)
|
|
161
|
-
- `copyDirectory()`
|
|
162
|
-
- `
|
|
163
|
-
|
|
164
|
-
- `
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
- `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`
|
|
164
|
+
- `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
|
|
165
|
+
- `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)
|
|
166
|
+
- `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)
|
|
167
|
+
- `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
|
|
168
|
+
- `fillPathSpellings(requests, fsCache)` - pass 1 of the case-exact existence check: walk each
|
|
169
|
+
`{ referrer, target }` from the deepest directory the two share, listing every directory on the way
|
|
170
|
+
down exactly once. The only I/O in the pair
|
|
171
|
+
- `pathSpellingFrom(table, referrer, target)` - pass 2: pure judgement against the table pass 1
|
|
172
|
+
returned, reporting the path as disk spells it when the asked-for spelling differs only by case or
|
|
173
|
+
by Unicode normalization. Fill **once** over the whole set and then judge each reference — a fill
|
|
174
|
+
per reference reinstates the serialized `readdir` this shape removes.
|
|
175
|
+
⚠️ It judges **every component**, not just the basename: judging only the last one hands each
|
|
176
|
+
directory component back to the host filesystem's own folding, so a link that resolves on
|
|
177
|
+
macOS/APFS 404s on a case-sensitive filesystem — and a basename-only correction still leaves the
|
|
178
|
+
author with a broken path
|
|
179
|
+
- `DirectorySpellingIndex` / `spellingWalkRoot(referrer, target)` - the index the pair walks over, and
|
|
180
|
+
the root it walks from, for a caller that cannot enumerate its targets up front
|
|
167
181
|
- `FsLookupCache` - per-run memo for `realpath`/`readdir`, sharing in-flight promises. Construct one per
|
|
168
182
|
validation run and let it die with the run — never a module-level singleton, or a long-lived process
|
|
169
183
|
answers from a stale directory listing.
|
|
@@ -191,6 +205,10 @@ These return **OS-native** separators, because they resolve real filesystem iden
|
|
|
191
205
|
### Test helpers — `@vibe-agent-toolkit/utils/testing`
|
|
192
206
|
|
|
193
207
|
- `setupAsyncTempDirSuite()` / `setupSyncTempDirSuite()` - per-suite temp directories with cleanup
|
|
208
|
+
- `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
|
|
209
|
+
- `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
|
|
210
|
+
- `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.
|
|
211
|
+
- `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`
|
|
194
212
|
- `getTestOutputDir()` / `getTestOutputBase()` - isolated test output paths
|
|
195
213
|
- `symlinkCapability()` - probes once (memoized per process) whether this host can create symlinks (Windows needs Developer Mode or `SeCreateSymbolicLinkPrivilege`), returning a `SymlinkCapability` token or `null`
|
|
196
214
|
- `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()`)
|
|
@@ -207,10 +225,26 @@ These are CLI-boundary functions: inner libraries should take a root as a parame
|
|
|
207
225
|
|
|
208
226
|
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.
|
|
209
227
|
|
|
228
|
+
### Errors — `@vibe-agent-toolkit/utils` (barrel only)
|
|
229
|
+
|
|
230
|
+
- `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.
|
|
231
|
+
- `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`.
|
|
232
|
+
- `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.
|
|
233
|
+
- `PathEscapesRootError` (on `./path` too) - what `safePath.joinUnderRoot` throws; recognise it with `isVatError(error, PathEscapesRootError.code)`.
|
|
234
|
+
|
|
235
|
+
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.
|
|
236
|
+
|
|
210
237
|
### Directory crawling — `@vibe-agent-toolkit/utils/crawl`
|
|
211
238
|
|
|
212
239
|
- `crawlDirectory()` / `crawlDirectorySync()` - gitignore-aware directory walks
|
|
213
240
|
- `NEVER_CRAWL_GLOBS` / `BUILD_OUTPUT_GLOBS` - the standard exclusion sets
|
|
241
|
+
- `UnreadablePolicy` - the REQUIRED `unreadable` option on every crawl (and on `gitLsFiles` /
|
|
242
|
+
`gitLsOthers` under `./git`): `{ refuse: { root, remedy } }` throws `DirectoryListingRefusedError`
|
|
243
|
+
with an adopter-facing, root-relative sentence when a directory cannot be listed;
|
|
244
|
+
`{ degrade: (refusal) => … }` keeps walking and hands the gap to you to report. There is no
|
|
245
|
+
default — a shorter list nothing can tell from a complete one is the failure this option exists
|
|
246
|
+
to prevent, so the caller states which answer is honest for its lane, and an omitted policy is
|
|
247
|
+
refused up front by name.
|
|
214
248
|
|
|
215
249
|
Glob *pattern inspection* is a separate entry, `./glob`, and stays that way: `./glob` is dependency-free and reaches only `node:path`, whereas crawling reaches the filesystem, `git`, and `picomatch`.
|
|
216
250
|
|
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":"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,uEAAuE;QACvE,wEAAwE;QACxE,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7B,OAAO,CACL,sCAAsC,SAAS,KAAK;gBACpD,0CAA0C,WAAW,4CAA4C;gBACjG,kFAAkF;gBAClF,6GAA6G;gBAC7G,mFAAmF;gBACnF,eAAe,QAAQ,EAAE,CAC1B,CAAC;QACJ,CAAC;IACH,CAAC;IAED,kEAAkE;IAClE,IAAI,IAAI,KAAK,+BAA+B,EAAE,CAAC;QAC7C,OAAO,CACL,sCAAsC,SAAS,KAAK;YACpD,yEAAyE;YACzE,wHAAwH;YACxH,eAAe,QAAQ,EAAE,CAC1B,CAAC;IACJ,CAAC;IAED,qEAAqE;IACrE,OAAO,CACL,sCAAsC,SAAS,MAAM,QAAQ,IAAI;QACjE,0DAA0D,OAAO,GAAG,CACrE,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,qBAAqB,GAAG,8BAA8B,CAAC;AAE7D,SAAS,wBAAwB,CAAC,KAAc;IAC9C,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACxD,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,SAAS,eAAe,CAAC,KAAa;IACpC,OAAO,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAY;IACpC,OAAO,CACL,OAAO,GAAG,KAAK,QAAQ;QACvB,GAAG,KAAK,IAAI;QACZ,MAAM,IAAI,GAAG;QACZ,GAAwB,CAAC,IAAI,KAAK,kBAAkB,CACtD,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB,CAAC,GAAY;IACzC,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,uEAAuE;QACvE,wDAAwD;QACxD,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,OAAO,SAAS,IAAI,GAAG,CAAC,OAAO,CAAC;IAClC,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC"}
|
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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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one answer to "am I the script Node was asked to run?"
|
|
3
|
+
*
|
|
4
|
+
* Lives here, in the package every other one already depends on, because there
|
|
5
|
+
* has to be exactly one: the question was previously answered three different
|
|
6
|
+
* ways across this monorepo (`import.meta.main`, a raw `pathToFileURL` string
|
|
7
|
+
* compare, and this function), and two of those three answer `false` for the
|
|
8
|
+
* script they guard — which makes the process exit 0 having run nothing.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Was this module invoked as the process entrypoint, rather than imported?
|
|
12
|
+
*
|
|
13
|
+
* ⛔ **Do not reach for `import.meta.main` here.** It shipped in Node 24.2 and
|
|
14
|
+
* 22.18; this repo declares a floor of `>=22.13.0`, and on a real 22.13.0 the
|
|
15
|
+
* property is `undefined`:
|
|
16
|
+
*
|
|
17
|
+
* ```
|
|
18
|
+
* $ node-v22.13.0 --input-type=module -e "console.log(import.meta.main)" -> undefined
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* That is not a cosmetic gap. Every `if (import.meta.main)` guard in the
|
|
22
|
+
* dev-tools package was silently dead on the declared floor — running
|
|
23
|
+
* `validate-repo-structure.ts` under 22.13.0 printed nothing and exited 0, so a
|
|
24
|
+
* contributor sitting exactly on the supported Node got a green pre-commit
|
|
25
|
+
* structure gate that had run no rule at all. Raising the floor to 22.18 would
|
|
26
|
+
* have hidden the defect behind the number instead of fixing it.
|
|
27
|
+
*
|
|
28
|
+
* ⛔ **Nor the raw string compare it replaced**, which was the other seven
|
|
29
|
+
* guards in this repo:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* import.meta.url === pathToFileURL(process.argv[1]).href // ❌
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* A `node_modules/.bin` entry is a SYMLINK to the real script, so `argv[1]` is
|
|
36
|
+
* the link and `import.meta.url` is the resolved target. The two strings differ
|
|
37
|
+
* and the guard is false — measured false through a `.bin`-style symlink on both
|
|
38
|
+
* Node 22.14.0 and 24.13.1, where this function is true.
|
|
39
|
+
*
|
|
40
|
+
* `process.argv[1]` is defined on every Node this repo supports, so the
|
|
41
|
+
* comparison below is the portable form of the same question. The realpath pass
|
|
42
|
+
* covers the case where the script is reached through a symlink on one side of
|
|
43
|
+
* the comparison but not the other.
|
|
44
|
+
*
|
|
45
|
+
* `local/no-fragile-entrypoint-guard` (shipped on this package's `./eslint`
|
|
46
|
+
* subpath) is what keeps either of the two banned spellings from coming back —
|
|
47
|
+
* the prose comments that used to stand in for it did not.
|
|
48
|
+
*
|
|
49
|
+
* @param importMetaUrl - The calling module's `import.meta.url`
|
|
50
|
+
* @param entryPath - The invoked script path; defaults to `process.argv[1]`
|
|
51
|
+
* @returns `true` only when this module is the script Node was asked to run
|
|
52
|
+
*/
|
|
53
|
+
export declare function isEntrypoint(importMetaUrl: string, entryPath?: string | undefined): boolean;
|
|
54
|
+
//# sourceMappingURL=entrypoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entrypoint.d.ts","sourceRoot":"","sources":["../src/entrypoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,YAAY,CAC1B,aAAa,EAAE,MAAM,EACrB,SAAS,GAAE,MAAM,GAAG,SAA2B,GAC9C,OAAO,CAcT"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one answer to "am I the script Node was asked to run?"
|
|
3
|
+
*
|
|
4
|
+
* Lives here, in the package every other one already depends on, because there
|
|
5
|
+
* has to be exactly one: the question was previously answered three different
|
|
6
|
+
* ways across this monorepo (`import.meta.main`, a raw `pathToFileURL` string
|
|
7
|
+
* compare, and this function), and two of those three answer `false` for the
|
|
8
|
+
* script they guard — which makes the process exit 0 having run nothing.
|
|
9
|
+
*/
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { safePath, toForwardSlash } from './path-core.js';
|
|
12
|
+
import { normalizePath } from './path-utils.js';
|
|
13
|
+
/**
|
|
14
|
+
* Was this module invoked as the process entrypoint, rather than imported?
|
|
15
|
+
*
|
|
16
|
+
* ⛔ **Do not reach for `import.meta.main` here.** It shipped in Node 24.2 and
|
|
17
|
+
* 22.18; this repo declares a floor of `>=22.13.0`, and on a real 22.13.0 the
|
|
18
|
+
* property is `undefined`:
|
|
19
|
+
*
|
|
20
|
+
* ```
|
|
21
|
+
* $ node-v22.13.0 --input-type=module -e "console.log(import.meta.main)" -> undefined
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* That is not a cosmetic gap. Every `if (import.meta.main)` guard in the
|
|
25
|
+
* dev-tools package was silently dead on the declared floor — running
|
|
26
|
+
* `validate-repo-structure.ts` under 22.13.0 printed nothing and exited 0, so a
|
|
27
|
+
* contributor sitting exactly on the supported Node got a green pre-commit
|
|
28
|
+
* structure gate that had run no rule at all. Raising the floor to 22.18 would
|
|
29
|
+
* have hidden the defect behind the number instead of fixing it.
|
|
30
|
+
*
|
|
31
|
+
* ⛔ **Nor the raw string compare it replaced**, which was the other seven
|
|
32
|
+
* guards in this repo:
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* import.meta.url === pathToFileURL(process.argv[1]).href // ❌
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* A `node_modules/.bin` entry is a SYMLINK to the real script, so `argv[1]` is
|
|
39
|
+
* the link and `import.meta.url` is the resolved target. The two strings differ
|
|
40
|
+
* and the guard is false — measured false through a `.bin`-style symlink on both
|
|
41
|
+
* Node 22.14.0 and 24.13.1, where this function is true.
|
|
42
|
+
*
|
|
43
|
+
* `process.argv[1]` is defined on every Node this repo supports, so the
|
|
44
|
+
* comparison below is the portable form of the same question. The realpath pass
|
|
45
|
+
* covers the case where the script is reached through a symlink on one side of
|
|
46
|
+
* the comparison but not the other.
|
|
47
|
+
*
|
|
48
|
+
* `local/no-fragile-entrypoint-guard` (shipped on this package's `./eslint`
|
|
49
|
+
* subpath) is what keeps either of the two banned spellings from coming back —
|
|
50
|
+
* the prose comments that used to stand in for it did not.
|
|
51
|
+
*
|
|
52
|
+
* @param importMetaUrl - The calling module's `import.meta.url`
|
|
53
|
+
* @param entryPath - The invoked script path; defaults to `process.argv[1]`
|
|
54
|
+
* @returns `true` only when this module is the script Node was asked to run
|
|
55
|
+
*/
|
|
56
|
+
export function isEntrypoint(importMetaUrl, entryPath = process.argv[1]) {
|
|
57
|
+
// An empty argv[1] would resolve to the cwd and could then match a module by
|
|
58
|
+
// accident; `undefined` happens under `node -e`. Neither is an entrypoint.
|
|
59
|
+
if (entryPath === undefined || entryPath === '')
|
|
60
|
+
return false;
|
|
61
|
+
const modulePath = safePath.resolve(fileURLToPath(importMetaUrl));
|
|
62
|
+
const invokedPath = safePath.resolve(entryPath);
|
|
63
|
+
if (modulePath === invokedPath)
|
|
64
|
+
return true;
|
|
65
|
+
// `normalizePath` resolves symlinks and Windows 8.3 short names, and returns
|
|
66
|
+
// the path unchanged when it cannot — which is the right answer here, since
|
|
67
|
+
// the string comparison above has already decided the two differ and an
|
|
68
|
+
// unresolvable path carries no symlink information to compare.
|
|
69
|
+
return toForwardSlash(normalizePath(modulePath)) === toForwardSlash(normalizePath(invokedPath));
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=entrypoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entrypoint.js","sourceRoot":"","sources":["../src/entrypoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,UAAU,YAAY,CAC1B,aAAqB,EACrB,YAAgC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAE/C,6EAA6E;IAC7E,2EAA2E;IAC3E,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAE9D,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC;IAClE,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChD,IAAI,UAAU,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAE5C,6EAA6E;IAC7E,4EAA4E;IAC5E,wEAAwE;IACxE,+DAA+D;IAC/D,OAAO,cAAc,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,KAAK,cAAc,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;AAClG,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read an environment variable as a boolean.
|
|
3
|
+
*
|
|
4
|
+
* 🔑 **Returns `undefined` for anything it does not recognise, and never
|
|
5
|
+
* guesses.** That is the whole point: the caller — not this function — decides
|
|
6
|
+
* what an unreadable value means, because only the caller knows which way is
|
|
7
|
+
* safe. A token-fetching kill switch wants "deny"; a cache toggle wants
|
|
8
|
+
* "leave the cache on". A parser that folded the unknown case into `false`
|
|
9
|
+
* would have made that choice for both of them.
|
|
10
|
+
*
|
|
11
|
+
* 🚨 **Why this exists at all.** Three switches were each a comparison against
|
|
12
|
+
* the literal string `'0'`, so `=false` turned none of them off — measured for
|
|
13
|
+
* `VAT_LINKAUTH_ALLOW_COMMAND`, which *still spawned subprocesses*. Every
|
|
14
|
+
* spelling a human reaches for (`false`, `no`, `off`, `FALSE`, a value with a
|
|
15
|
+
* stray space) failed open. A switch whose off position is one exact string is
|
|
16
|
+
* not a switch.
|
|
17
|
+
*
|
|
18
|
+
* 📍 **Why it lives in `utils`.** It was written next to its first consumer in
|
|
19
|
+
* `resources`, on the rule that utils takes a utility when a SECOND package
|
|
20
|
+
* needs it rather than speculatively. That second package arrived:
|
|
21
|
+
* `packages/cli`'s `projectionStoreSelected()` reads the same `VAT_CACHE` the
|
|
22
|
+
* `resources` parse cache does, and two independent readings of one variable is
|
|
23
|
+
* the defect, not the fix. The three consumers today:
|
|
24
|
+
*
|
|
25
|
+
* | Caller | Variable | Reads `undefined` as | Why |
|
|
26
|
+
* |---|---|---|---|
|
|
27
|
+
* | `link-auth/resolve-token.ts` | `VAT_LINKAUTH_ALLOW_COMMAND` | **deny** | gates a capability — fail closed |
|
|
28
|
+
* | `resources/parse-cache.ts` | `VAT_CACHE` | cache stays on | gates a cache — an unreadable value must not silently change behaviour |
|
|
29
|
+
* | `cli/utils/projection-store.ts` | `VAT_CACHE` | not a veto | same variable, same reading, one implementation |
|
|
30
|
+
*
|
|
31
|
+
* Same parser, different safe sides, each chosen at its own call site. That is
|
|
32
|
+
* the contract; do not move a default in here.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Parse an env value as a boolean.
|
|
36
|
+
*
|
|
37
|
+
* Case-insensitive and surrounding whitespace is trimmed, because a value that
|
|
38
|
+
* arrived through a shell, a CI YAML block, or a `.env` file routinely carries
|
|
39
|
+
* both. Everything else — including the empty string, which is what an unset
|
|
40
|
+
* shell variable expands to — returns `undefined`.
|
|
41
|
+
*
|
|
42
|
+
* @param raw - The raw env value, or `undefined` when the variable is unset
|
|
43
|
+
* @returns `true` / `false` for a recognised spelling; `undefined` otherwise
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseEnvBoolean(raw: string | undefined): boolean | undefined;
|
|
46
|
+
//# sourceMappingURL=env-flag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-flag.d.ts","sourceRoot":"","sources":["../src/env-flag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAKH;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAM5E"}
|