@vibe-agent-toolkit/utils 0.1.41 → 0.1.42
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 +146 -31
- package/dist/asset.d.ts +9 -0
- package/dist/asset.d.ts.map +1 -0
- package/dist/asset.js +9 -0
- package/dist/asset.js.map +1 -0
- package/dist/crawl.d.ts +19 -0
- package/dist/crawl.d.ts.map +1 -0
- package/dist/crawl.js +19 -0
- package/dist/crawl.js.map +1 -0
- package/dist/file-crawler.d.ts +19 -2
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +19 -1
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs-utils.d.ts +60 -3
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +88 -10
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +7 -6
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +7 -6
- package/dist/fs.js.map +1 -1
- package/dist/git-utils.d.ts +2 -0
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +16 -0
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +23 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +23 -0
- package/dist/git.js.map +1 -0
- package/dist/gitignore-checker.d.ts +0 -9
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +0 -12
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/glob/glob-pattern.js +1 -1
- package/dist/glob/glob-pattern.js.map +1 -1
- package/dist/glob.d.ts +8 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +8 -0
- package/dist/glob.js.map +1 -0
- package/dist/path-core.d.ts +196 -0
- package/dist/path-core.d.ts.map +1 -0
- package/dist/path-core.js +251 -0
- package/dist/path-core.js.map +1 -0
- package/dist/path-utils.d.ts +12 -183
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +13 -237
- package/dist/path-utils.js.map +1 -1
- package/dist/path.d.ts +12 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +12 -0
- package/dist/path.js.map +1 -0
- package/dist/process.d.ts +7 -4
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +7 -4
- package/dist/process.js.map +1 -1
- package/dist/project.d.ts +31 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +31 -0
- package/dist/project.js.map +1 -0
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +8 -4
- package/dist/safe-exec.js.map +1 -1
- package/dist/spawn-hardened.d.ts.map +1 -1
- package/dist/spawn-hardened.js +4 -17
- package/dist/spawn-hardened.js.map +1 -1
- package/dist/template-entry.d.ts +10 -0
- package/dist/template-entry.d.ts.map +1 -0
- package/dist/template-entry.js +10 -0
- package/dist/template-entry.js.map +1 -0
- package/dist/testing.d.ts +8 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +8 -0
- package/dist/testing.js.map +1 -0
- package/dist/windows-shell.d.ts +140 -8
- package/dist/windows-shell.d.ts.map +1 -1
- package/dist/windows-shell.js +218 -11
- package/dist/windows-shell.js.map +1 -1
- package/dist/yaml.d.ts +9 -0
- package/dist/yaml.d.ts.map +1 -0
- package/dist/yaml.js +9 -0
- package/dist/yaml.js.map +1 -0
- package/dist/zod.d.ts +10 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +10 -0
- package/dist/zod.js.map +1 -0
- package/eslint/README.md +217 -0
- package/eslint/index.cjs +161 -0
- package/eslint/index.d.cts +60 -0
- package/eslint/rules/dead-import.cjs +201 -0
- package/eslint/rules/eslint-rule-factory.cjs +410 -0
- package/eslint/rules/exempt-path-matcher.cjs +265 -0
- package/eslint/rules/no-bare-dynamic-import-path.cjs +142 -0
- package/eslint/rules/no-child-process-execSync.cjs +23 -0
- package/eslint/rules/no-command-direct-factory.cjs +246 -0
- package/eslint/rules/no-file-url-string-concat.cjs +77 -0
- package/eslint/rules/no-fs-mkdirSync.cjs +23 -0
- package/eslint/rules/no-fs-promises-cp.cjs +36 -0
- package/eslint/rules/no-fs-realpathSync.cjs +23 -0
- package/eslint/rules/no-hardcoded-path-split.cjs +141 -0
- package/eslint/rules/no-manual-path-normalize.cjs +161 -0
- package/eslint/rules/no-os-tmpdir.cjs +24 -0
- package/eslint/rules/no-path-join.cjs +14 -0
- package/eslint/rules/no-path-operations-in-comparisons.cjs +148 -0
- package/eslint/rules/no-path-relative.cjs +14 -0
- package/eslint/rules/no-path-resolve.cjs +14 -0
- package/eslint/rules/no-path-sep-in-strings.cjs +130 -0
- package/eslint/rules/no-path-startswith.cjs +139 -0
- package/eslint/rules/no-test-scoped-functions.cjs +134 -0
- package/eslint/rules/no-unix-shell-commands.cjs +152 -0
- package/eslint/rules/no-unsafe-root-join.cjs +85 -0
- package/eslint/rules/no-url-pathname-for-fs.cjs +107 -0
- package/eslint/rules/path-function-rule-factory.cjs +417 -0
- package/eslint/rules/prefer-startswith-over-regex.cjs +257 -0
- package/eslint/rules/require-justified-skip.cjs +348 -0
- package/eslint/rules/safe-import.cjs +164 -0
- package/package.json +65 -7
package/README.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# @vibe-agent-toolkit/utils
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Cross-platform primitives for Node tooling that has to run correctly on both Windows and Linux — safe command execution, hardened process spawning, path normalization, and git introspection.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Projects building skills and agent tooling with the vibe-agent-toolkit write exactly this kind of Node code and hit exactly these platform potholes: `.cmd` shims that need a shell on Windows, 8.3 short paths from `tmpdir()`, backslash-versus-forward-slash comparisons, and `import()` of an absolute path failing on Windows without a `file://` URL. This package is the shared answer.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
If you need a utility function and multiple packages would benefit from it, add it here. Otherwise, keep it local to the package that needs it.
|
|
7
|
+
**Node-only.** Requires Node >= 22. See [Runtime support](#runtime-support).
|
|
10
8
|
|
|
11
9
|
## Installation
|
|
12
10
|
|
|
@@ -14,19 +12,62 @@ If you need a utility function and multiple packages would benefit from it, add
|
|
|
14
12
|
bun add @vibe-agent-toolkit/utils
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
##
|
|
15
|
+
## Import narrowly
|
|
16
|
+
|
|
17
|
+
Every area has its own subpath. Import the one you need.
|
|
18
|
+
|
|
19
|
+
The package sets `"sideEffects": false`, so a modern bundler will tree-shake unused code out of the `.` barrel — importing `safePath` from `.` and from `./path` produce near-identical bundles. **Subpaths are not primarily a size optimization.** What they control is what your build has to *resolve* and what your module graph *reaches*: the `.` barrel reaches `yaml`, `handlebars`, and `node:fs` no matter what you destructure from it, so it cannot be bundled for a browser target and requires every dependency to be installed. A narrow entry reaches only what it needs.
|
|
20
|
+
|
|
21
|
+
The last two columns are the ones that matter when choosing. **"Resolves with zero deps installed?"** is the sharper of the two: it separates an entry that is merely *heavy* from one that is *unbuildable* in an environment where the package's third-party dependencies are absent or unresolvable.
|
|
22
|
+
|
|
23
|
+
| Subpath | Contents | Node builtins reached | Third-party | Resolves with zero deps installed? |
|
|
24
|
+
|---|---|---|---|---|
|
|
25
|
+
| `./path` | `safePath`, `toForwardSlash`, `isAbsolutePath`, `isAbsoluteAnyPlatform`, `hasParentTraversalSegment`, `toAbsolutePath`, `getRelativePath`, `issueLocation` | `path` only | — | **yes** |
|
|
26
|
+
| `./zod` | `ZodTypeNames`, `getZodTypeName`, `isZodType`, `unwrapZodType`, `isZodOptional`, `isZodNullable` | **none** | — | **yes** |
|
|
27
|
+
| `./glob` | `isGlob`, static base extraction, magic remainder | `path` only | — | **yes** |
|
|
28
|
+
| `./fs` | `normalizePath`, `normalizedTmpdir`, `mkdirSyncReal`, `resolveFromImportMeta`, `dynamicImportPath`, `copyDirectory`, `verifyCaseSensitiveFilename`, `FsLookupCache` | `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
|
|
29
|
+
| `./testing` | `getTestOutputDir`, `getTestOutputBase`, `setupAsyncTempDirSuite`, `setupSyncTempDirSuite` | `crypto`, `fs`, `fs/promises`, `os`, `path`, `url` | — | **yes** |
|
|
30
|
+
| `./asset` | `resolveAssetReference` — paths and npm bare specifiers | `fs`, `module`, `os`, `path`, `url` | — | **yes** |
|
|
31
|
+
| `./yaml` | `updateYamlIn`, `verifyConfinedYamlEdit` — byte-surgical YAML edits | **none** | `yaml` | no — needs `yaml` |
|
|
32
|
+
| `./template` | `renderTemplate` — cached Handlebars | **none** | `handlebars` | no — needs `handlebars` |
|
|
33
|
+
| `./process` | `safeExecSync`, `safeExecResult`, `safeExecFromString`, `isToolAvailable`, `getToolVersion`, `hasShellSyntax`, `CommandExecutionError`, `spawnHardened`, `shouldUseShell`, `windowsShellQuote`, `buildWindowsShellLine`, `resolveShellCommandToken`, `isPathLike`, `makeStdioBlocking`, `describeStdioBlocking` | `child_process`, `path` | `which` | no — needs `which` |
|
|
34
|
+
| `./git` | `gitFindRoot`, `gitLsFiles`, `isGitIgnored`, `loadGitignoreRules`, `GitTracker`, `parseGitUrl`, `isGitUrl`, `nonInteractiveGitOverrides` | `child_process`, `fs`, `os`, `path`, `url` | `ignore`, `which` | no — needs `which`, `ignore` |
|
|
35
|
+
| `./crawl` | `crawlDirectory`, `crawlDirectorySync`, `NEVER_CRAWL_GLOBS`, `BUILD_OUTPUT_GLOBS` | `child_process`, `fs`, `os`, `path`, `url` | `picomatch`, `which` | no — needs `picomatch`, `which` |
|
|
36
|
+
| `./project` | `findProjectRoot`, `findConfigFile`, `findNodeWorkspaceRoot`, `resetProjectRootCaches` | `fs`, `path` | — | **yes** |
|
|
37
|
+
| `./eslint` | the 21 ESLint rules that enforce everything above — see [ESLint rules](#eslint-rules--vibe-agent-toolkitutilseslint) | **none** | — | **yes** |
|
|
38
|
+
| `.` | every runtime entry above (not `./eslint`) | all of the above, plus `stream` | `handlebars`, `ignore`, `picomatch`, `which`, `yaml` | no — needs all of them |
|
|
39
|
+
| `./package.json` | the manifest itself, for version reporting and resolution assertions | — | — | **yes** |
|
|
40
|
+
|
|
41
|
+
Note `./zod` reaches nothing at all: it detects Zod types by duck-typing `_def.typeName` rather than importing Zod, which is exactly why it works across Zod v3 and v4.
|
|
42
|
+
|
|
43
|
+
`./crawl` is the only *subpath* that reaches `picomatch` (the `.` barrel also reaches it, via linkAuth's host-pattern matching), and it is deliberately *not* folded into `./glob` — `./glob` is guarded as portable (`node:path`, no third-party), and directory crawling would break both halves of that guarantee.
|
|
18
44
|
|
|
19
45
|
```typescript
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
setupTestTempDir,
|
|
27
|
-
} from '@vibe-agent-toolkit/utils';
|
|
46
|
+
// Reaches node:path and nothing else
|
|
47
|
+
import { safePath, toForwardSlash } from '@vibe-agent-toolkit/utils/path';
|
|
48
|
+
import { safeExecSync, spawnHardened } from '@vibe-agent-toolkit/utils/process';
|
|
49
|
+
|
|
50
|
+
// Reaches yaml, handlebars, and node:fs regardless of what you destructure
|
|
51
|
+
import { safePath } from '@vibe-agent-toolkit/utils';
|
|
28
52
|
```
|
|
29
53
|
|
|
54
|
+
`./package.json` is exported as well, so `require('@vibe-agent-toolkit/utils/package.json')` (or a `with { type: 'json' }` import) works for version reporting and "which build am I on?" resolution assertions instead of failing with `ERR_PACKAGE_PATH_NOT_EXPORTED`.
|
|
55
|
+
|
|
56
|
+
Consumers who prefer a single seam over direct dependencies can re-export the subpaths they want from their own internal module. That pattern works well — just wrap the subpaths rather than the `.` barrel, or the narrowing is lost.
|
|
57
|
+
|
|
58
|
+
## Runtime support
|
|
59
|
+
|
|
60
|
+
This package targets **Node >= 22** and is not published for browsers. Most entry points reach `node:fs`, `node:child_process`, or `node:os`.
|
|
61
|
+
|
|
62
|
+
A guard test in `test/subpath-purity.test.ts` walks each entry's transitive source graph and enforces **both** of the table's last two columns:
|
|
63
|
+
|
|
64
|
+
- **Third-party reach** — the "Resolves with zero deps installed?" column. Every entry's expected third-party set is asserted exactly, so every **yes** row above is a tested claim rather than a documented intention, and adding a dependency to any entry is a deliberate, reviewed edit.
|
|
65
|
+
- **Builtin reach** — five entries are held to a stricter contract still: `./zod`, `./yaml`, `./template` reach **no Node builtin at all**, and `./path`, `./glob` reach **`node:path` and nothing else** — the one builtin every bundler shims.
|
|
66
|
+
|
|
67
|
+
That is an enforced invariant, not a browser-support commitment: there are no browser export conditions and no browser test lane. The guard exists so the property can't regress silently — it fails loudly if it cannot resolve a module, so it can't pass vacuously; `test/fixtures/dangling-import/` exercises that failure so the guarantee is demonstrated, not just claimed. If you add a new entry, add it to that test or nothing protects it.
|
|
68
|
+
|
|
69
|
+
`./eslint` is hand-written CommonJS rather than compiled TypeScript, so that walker cannot see it; `test/eslint/subpath-purity.test.ts` holds it to the same contract by walking its `require()` graph instead — no builtin, no third-party, and in particular never `eslint` itself.
|
|
70
|
+
|
|
30
71
|
## Available Utilities
|
|
31
72
|
|
|
32
73
|
### Zod Type Introspection (Version-Agnostic)
|
|
@@ -37,7 +78,7 @@ Uses duck typing via `_def.typeName` instead of `instanceof` checks, which fail
|
|
|
37
78
|
|
|
38
79
|
**Quick Example**:
|
|
39
80
|
```typescript
|
|
40
|
-
import { getZodTypeName, isZodType, ZodTypeNames } from '@vibe-agent-toolkit/utils';
|
|
81
|
+
import { getZodTypeName, isZodType, ZodTypeNames } from '@vibe-agent-toolkit/utils/zod';
|
|
41
82
|
import { z } from 'zod';
|
|
42
83
|
|
|
43
84
|
const schema = z.string().optional();
|
|
@@ -67,31 +108,105 @@ UNION, INTERSECTION, TUPLE, RECORD, MAP, SET,
|
|
|
67
108
|
FUNCTION, LAZY, PROMISE, and more...
|
|
68
109
|
```
|
|
69
110
|
|
|
70
|
-
**See**: [docs/zod-compatibility.md](
|
|
111
|
+
**See**: [docs/zod-compatibility.md](https://github.com/jdutton/vibe-agent-toolkit/blob/main/docs/zod-compatibility.md) for the complete guide
|
|
71
112
|
|
|
72
113
|
**Peer Dependency**: Requires `zod ^3.25.0 || ^4.0.0`
|
|
73
114
|
|
|
74
115
|
---
|
|
75
116
|
|
|
76
|
-
###
|
|
77
|
-
|
|
117
|
+
### Path strings — `@vibe-agent-toolkit/utils/path`
|
|
118
|
+
|
|
119
|
+
These always return forward slashes on every platform, so they are safe for comparisons, `Map` keys, globs, and display.
|
|
120
|
+
|
|
121
|
+
- `safePath.join()` / `.resolve()` / `.relative()` - forward-slash equivalents of the `node:path` functions
|
|
122
|
+
- `toForwardSlash()` - explicit converter for any path string
|
|
123
|
+
- `toAbsolutePath()` - resolve a path relative to a base directory
|
|
124
|
+
- `getRelativePath()` - relative path between two absolute paths
|
|
125
|
+
- `isAbsolutePath()` / `isAbsoluteAnyPlatform()` - absolute-path predicates
|
|
126
|
+
- `hasParentTraversalSegment()` - detect `..` segments before using a caller-supplied path
|
|
127
|
+
- `issueLocation()` - format a `file:line`-style location relative to a project root
|
|
128
|
+
|
|
129
|
+
### Filesystem — `@vibe-agent-toolkit/utils/fs`
|
|
130
|
+
|
|
131
|
+
These return **OS-native** separators, because they resolve real filesystem identity via `realpathSync.native()` (which is what resolves Windows 8.3 short names). Wrap with `toForwardSlash()` if you need forward slashes.
|
|
132
|
+
|
|
133
|
+
⚠️ **`normalizePath()` has an input-dependent split personality.** It lives on `./fs` (rather than `./path`) for the right reason — it calls `realpathSync.native()` — but which of two different things it does depends on its argument: given a single *relative* path it is pure string work, equivalent to `path.normalize`, touching no filesystem; given anything else it resolves to absolute and performs a filesystem `realpath`, so it follows symlinks, resolves 8.3 short names, and — when the path does not exist — silently falls back to the merely-resolved path. Two semantics under one name: check which case you are in before relying on either.
|
|
134
|
+
|
|
135
|
+
- `normalizePath()` - resolve 8.3 short names and return the real path (see the warning above)
|
|
136
|
+
- `normalizedTmpdir()` - `os.tmpdir()` with short names resolved
|
|
137
|
+
- `mkdirSyncReal()` - create a directory and return its real path
|
|
138
|
+
- `resolveFromImportMeta()` - resolve paths relative to an `import.meta.url`
|
|
139
|
+
- `dynamicImportPath()` - `import()` an absolute path (works on Windows, which rejects bare absolute paths)
|
|
140
|
+
- `copyDirectory()`
|
|
141
|
+
- `verifyCaseSensitiveFilename(filePath, fsCache)` - case-exact existence check; takes a per-run `FsLookupCache`
|
|
142
|
+
- `FsLookupCache` - per-run memo for `realpath`/`readdir`, sharing in-flight promises. Construct one per
|
|
143
|
+
validation run and let it die with the run — never a module-level singleton, or a long-lived process
|
|
144
|
+
answers from a stale directory listing.
|
|
145
|
+
|
|
146
|
+
### Processes — `@vibe-agent-toolkit/utils/process`
|
|
147
|
+
|
|
148
|
+
- `safeExecSync()` / `safeExecResult()` / `safeExecFromString()` - cross-platform command execution with no shell injection
|
|
149
|
+
- `spawnHardened()` - async spawn with streaming stdio and correct Windows `.cmd`/`.bat` launching
|
|
150
|
+
- `shouldUseShell()` / `windowsShellQuote()` / `buildWindowsShellLine()` - Windows shell-invocation helpers
|
|
151
|
+
- `isToolAvailable()` / `getToolVersion()` / `hasShellSyntax()`
|
|
152
|
+
- `makeStdioBlocking()` - stop `process.exit()` truncating output in a published bin
|
|
153
|
+
|
|
154
|
+
### Git — `@vibe-agent-toolkit/utils/git`
|
|
155
|
+
|
|
156
|
+
- `gitFindRoot()` - find the repository root from a starting directory
|
|
157
|
+
- `gitLsFiles()` - enumerate tracked files
|
|
158
|
+
- `isGitIgnored()` - check whether a path is gitignored (outside a repository it answers from the filesystem, spawning nothing)
|
|
159
|
+
- `loadGitignoreRules()` - load a repository's ignore rules
|
|
160
|
+
- `GitTracker` - cached tracked-file lookups for repeated checks
|
|
161
|
+
- `parseGitUrl()` / `isGitUrl()` - recognize and decompose git URLs, including `owner/repo` shorthand and `#ref:subpath` fragments
|
|
162
|
+
- `nonInteractiveGitOverrides()` - env and `git -c` overrides that stop a clone blocking on a credential prompt
|
|
163
|
+
|
|
164
|
+
**One root finder, on purpose.** `gitFindRoot()` is the only one in the package. There used to be a second name, `findGitRoot()`, whose entire body was `return gitFindRoot(startDir)`; two names for one function guarantees half of all callers pick each. Keeping it off this entry was not enough — it stayed on the `.` barrel, so both names were still one import away — so it has been removed outright. Replace any `findGitRoot(` with `gitFindRoot(`; the behavior is identical.
|
|
165
|
+
|
|
166
|
+
### Test helpers — `@vibe-agent-toolkit/utils/testing`
|
|
167
|
+
|
|
168
|
+
- `setupAsyncTempDirSuite()` / `setupSyncTempDirSuite()` - per-suite temp directories with cleanup
|
|
169
|
+
- `getTestOutputDir()` / `getTestOutputBase()` - isolated test output paths
|
|
170
|
+
|
|
171
|
+
### Project roots — `@vibe-agent-toolkit/utils` (barrel only)
|
|
172
|
+
|
|
173
|
+
- `findProjectRoot()` - the VAT project root: nearest `vibe-agent-toolkit.config.yaml`, else nearest `.git/`, else `null`
|
|
174
|
+
- `findConfigFile()` / `findNodeWorkspaceRoot()` - the narrower individual probes
|
|
175
|
+
- `resetProjectRootCaches()` - invalidate the module-level walk-up cache (long-lived processes and tests that mutate fixtures)
|
|
176
|
+
|
|
177
|
+
These are CLI-boundary functions: inner libraries should take a root as a parameter rather than discovering one. They return `string | null` with no internal fallback, so a caller with no root has to decide one rather than silently landing on an absolute path.
|
|
178
|
+
|
|
179
|
+
**These four are VAT-shaped — read this before reaching for them.** `findProjectRoot()` looks for `vibe-agent-toolkit.config.yaml` and then `.git/`; if your repo's notion of "root" is a `pnpm-workspace.yaml`, a `turbo.json`, or a lockfile, that ladder is not your ladder — and for a *published* package, keying anything on `.git/` is a bug, since it will not be there at install time. `findNodeWorkspaceRoot()` is narrower still: it needs a `package.json` carrying a `"workspaces"` key, which pnpm and Bun workspaces do not have. `findConfigFile()` hardcodes VAT's config filename. If you want a git root, take `gitFindRoot()` from [`./git`](#git--vibe-agent-toolkitutilsgit); if you want your own marker, a six-line walk-up is more honest than a helper whose ladder you have to work around.
|
|
180
|
+
|
|
181
|
+
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: barrel-only, these four cost `handlebars`, `yaml`, `picomatch`, `ignore` and `which` 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 five dependencies.
|
|
182
|
+
|
|
183
|
+
### Directory crawling — `@vibe-agent-toolkit/utils/crawl`
|
|
184
|
+
|
|
185
|
+
- `crawlDirectory()` / `crawlDirectorySync()` - gitignore-aware directory walks
|
|
186
|
+
- `NEVER_CRAWL_GLOBS` / `BUILD_OUTPUT_GLOBS` - the standard exclusion sets
|
|
187
|
+
|
|
188
|
+
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`.
|
|
189
|
+
|
|
190
|
+
### ESLint rules — `@vibe-agent-toolkit/utils/eslint`
|
|
191
|
+
|
|
192
|
+
A safety helper is only as good as its enforcement: `safePath.join()` prevents a class of Windows bug precisely once — the moment someone writes `path.join()` instead, the helper's existence has bought nothing. So the 21 rules that direct code to these helpers ship with them, on their own subpath:
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
// eslint.config.js
|
|
196
|
+
import vat from '@vibe-agent-toolkit/utils/eslint';
|
|
197
|
+
|
|
198
|
+
export default [
|
|
199
|
+
vat.configs.recommended,
|
|
200
|
+
];
|
|
201
|
+
```
|
|
78
202
|
|
|
79
|
-
|
|
80
|
-
- `toForwardSlash()` - Convert paths to forward slashes (Windows/Unix compatibility)
|
|
81
|
-
- `getRelativePath()` - Get relative path between two absolute paths
|
|
82
|
-
- `normalizeFilePath()` - Normalize file paths for consistent comparisons
|
|
203
|
+
`configs.recommended` registers the rules under the `@vibe-agent-toolkit` namespace and turns on the cross-platform safety core (18 of the 21 — three rules are opt-in). Most rules auto-fix, and every message names the replacement and the subpath it lives on. Rules that ban a primitive take an `exemptFiles` option naming the file that implements *your* wrapper; there are deliberately no built-in exemptions.
|
|
83
204
|
|
|
84
|
-
|
|
85
|
-
- `crawlDirectory()` - Recursively crawl directories with pattern filtering
|
|
86
|
-
- `readFileContent()` - Read file content with encoding detection
|
|
205
|
+
**[Full rule table, severities, and exemption semantics →](./eslint/README.md)**
|
|
87
206
|
|
|
88
|
-
|
|
89
|
-
- `isGitIgnored()` - Check if file is gitignored (cached per directory)
|
|
90
|
-
- `getGitRootDir()` - Find git repository root directory
|
|
91
|
-
- `ensureGitRepository()` - Verify current directory is in a git repo
|
|
207
|
+
Requires ESLint 9+ (flat config). `eslint` is an **optional** peer dependency and adds nothing to the entries above: an ESLint plugin is data, not code that runs — the rule modules export plain objects and none of them `require('eslint')` — so this subpath reaches no Node builtin and no third-party package at all, and installing `utils` for `safePath.join()` alone pulls in nothing extra.
|
|
92
208
|
|
|
93
|
-
|
|
94
|
-
- `setupTestTempDir()` - Create temp directory for tests with cleanup
|
|
209
|
+
Shipping them here rather than as a separate `eslint-plugin` package is deliberate: one install, one version, and no way for a rule to name a helper signature the installed `utils` no longer has.
|
|
95
210
|
|
|
96
211
|
## License
|
|
97
212
|
|
package/dist/asset.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-agent-toolkit/utils/asset
|
|
3
|
+
*
|
|
4
|
+
* Asset reference resolution: accepts both filesystem paths (relative to a
|
|
5
|
+
* base directory, or absolute) and npm bare specifiers (`@scope/pkg/subpath`),
|
|
6
|
+
* honoring the target package's `exports` map. Node-only.
|
|
7
|
+
*/
|
|
8
|
+
export * from './asset-reference.js';
|
|
9
|
+
//# sourceMappingURL=asset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset.d.ts","sourceRoot":"","sources":["../src/asset.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,sBAAsB,CAAC"}
|
package/dist/asset.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-agent-toolkit/utils/asset
|
|
3
|
+
*
|
|
4
|
+
* Asset reference resolution: accepts both filesystem paths (relative to a
|
|
5
|
+
* base directory, or absolute) and npm bare specifiers (`@scope/pkg/subpath`),
|
|
6
|
+
* honoring the target package's `exports` map. Node-only.
|
|
7
|
+
*/
|
|
8
|
+
export * from './asset-reference.js';
|
|
9
|
+
//# sourceMappingURL=asset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset.js","sourceRoot":"","sources":["../src/asset.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,sBAAsB,CAAC"}
|
package/dist/crawl.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-agent-toolkit/utils/crawl
|
|
3
|
+
*
|
|
4
|
+
* Directory crawling (`crawlDirectory`, `crawlDirectorySync`) plus the
|
|
5
|
+
* never-crawl / build-output glob constants. Node-only — reads the filesystem
|
|
6
|
+
* and shells out to `git` for the gitignore-aware fast path.
|
|
7
|
+
*
|
|
8
|
+
* This is the only *subpath* entry that reaches `picomatch` — `./crawl` is
|
|
9
|
+
* therefore the narrow entry that makes `picomatch` a required install. It is
|
|
10
|
+
* not the package's only consumer of it: `link-auth/select-provider.ts` uses
|
|
11
|
+
* `picomatch.isMatch` for host-pattern matching, and that is reachable from the
|
|
12
|
+
* `.` barrel.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately NOT folded into `./glob`. `./glob` is guarded as portable —
|
|
15
|
+
* `node:path` and no third-party dependency (see `test/subpath-purity.test.ts`)
|
|
16
|
+
* — and file-crawler would break both halves of that guarantee.
|
|
17
|
+
*/
|
|
18
|
+
export * from './file-crawler.js';
|
|
19
|
+
//# sourceMappingURL=crawl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crawl.d.ts","sourceRoot":"","sources":["../src/crawl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,cAAc,mBAAmB,CAAC"}
|
package/dist/crawl.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-agent-toolkit/utils/crawl
|
|
3
|
+
*
|
|
4
|
+
* Directory crawling (`crawlDirectory`, `crawlDirectorySync`) plus the
|
|
5
|
+
* never-crawl / build-output glob constants. Node-only — reads the filesystem
|
|
6
|
+
* and shells out to `git` for the gitignore-aware fast path.
|
|
7
|
+
*
|
|
8
|
+
* This is the only *subpath* entry that reaches `picomatch` — `./crawl` is
|
|
9
|
+
* therefore the narrow entry that makes `picomatch` a required install. It is
|
|
10
|
+
* not the package's only consumer of it: `link-auth/select-provider.ts` uses
|
|
11
|
+
* `picomatch.isMatch` for host-pattern matching, and that is reachable from the
|
|
12
|
+
* `.` barrel.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately NOT folded into `./glob`. `./glob` is guarded as portable —
|
|
15
|
+
* `node:path` and no third-party dependency (see `test/subpath-purity.test.ts`)
|
|
16
|
+
* — and file-crawler would break both halves of that guarantee.
|
|
17
|
+
*/
|
|
18
|
+
export * from './file-crawler.js';
|
|
19
|
+
//# sourceMappingURL=crawl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crawl.js","sourceRoot":"","sources":["../src/crawl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,cAAc,mBAAmB,CAAC"}
|
package/dist/file-crawler.d.ts
CHANGED
|
@@ -48,10 +48,27 @@ export interface CrawlOptions {
|
|
|
48
48
|
* worktrees at `.claude/worktrees/`, inside a dot-directory that `dot: true`
|
|
49
49
|
* (see {@link PICOMATCH_OPTIONS}) deliberately makes reachable.
|
|
50
50
|
*
|
|
51
|
+
* `.turbo` is here rather than in {@link BUILD_OUTPUT_GLOBS}, and the placement
|
|
52
|
+
* is the decision. The line between the two lists is not "who produced it" —
|
|
53
|
+
* `coverage/` is tool output too — it is *does any lane exist precisely to look
|
|
54
|
+
* at it*. Something does walk `dist/`; nothing walks `.turbo`, which holds
|
|
55
|
+
* `turbo-<task>.log` telemetry plus, when `cacheDir` points inside it,
|
|
56
|
+
* hash-keyed cache entries that are COPIES of package build output. That is the
|
|
57
|
+
* `.worktrees` failure two lines up, not the `dist` one: a crawl that descends
|
|
58
|
+
* into it reports the same file twice under two paths. Filing it as build
|
|
59
|
+
* output would have it exactly backwards, since a lane spreading only this list
|
|
60
|
+
* is by definition a lane that wants to see built output — and so is precisely
|
|
61
|
+
* the lane that must not walk a cache of copies. (Turborepo is a common enough
|
|
62
|
+
* monorepo layout that this is not hypothetical: every package in THIS repo has
|
|
63
|
+
* a `.turbo/`.)
|
|
64
|
+
*
|
|
51
65
|
* Note these patterns only bite when `respectGitignore` is false; the fast
|
|
52
|
-
* `git ls-files` path never sees ignored directories in the first place.
|
|
66
|
+
* `git ls-files` path never sees ignored directories in the first place. For
|
|
67
|
+
* `.turbo` that is the only path it could bite on — it is gitignored by every
|
|
68
|
+
* turborepo setup — which is the same position `coverage/` and `.test-output/`
|
|
69
|
+
* are in.
|
|
53
70
|
*/
|
|
54
|
-
export declare const NEVER_CRAWL_GLOBS: readonly ["**/node_modules/**", "**/.git/**", "**/coverage/**", "**/.test-output/**", "**/.worktrees/**", "**/.claude/worktrees/**"];
|
|
71
|
+
export declare const NEVER_CRAWL_GLOBS: readonly ["**/node_modules/**", "**/.git/**", "**/coverage/**", "**/.test-output/**", "**/.worktrees/**", "**/.claude/worktrees/**", "**/.turbo/**"];
|
|
55
72
|
/**
|
|
56
73
|
* Build output — excluded by DEFAULT, but deliberately NOT part of
|
|
57
74
|
* {@link NEVER_CRAWL_GLOBS}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-crawler.d.ts","sourceRoot":"","sources":["../src/file-crawler.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAkBD
|
|
1
|
+
{"version":3,"file":"file-crawler.d.ts","sourceRoot":"","sources":["../src/file-crawler.ts"],"names":[],"mappings":"AAOA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAkBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,iBAAiB,sJAQpB,CAAC;AAEX;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,yBAA0B,CAAC;AAI1D;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAE7E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,EAAE,CA0KlE"}
|
package/dist/file-crawler.js
CHANGED
|
@@ -29,8 +29,25 @@ const PICOMATCH_OPTIONS = { dot: true };
|
|
|
29
29
|
* worktrees at `.claude/worktrees/`, inside a dot-directory that `dot: true`
|
|
30
30
|
* (see {@link PICOMATCH_OPTIONS}) deliberately makes reachable.
|
|
31
31
|
*
|
|
32
|
+
* `.turbo` is here rather than in {@link BUILD_OUTPUT_GLOBS}, and the placement
|
|
33
|
+
* is the decision. The line between the two lists is not "who produced it" —
|
|
34
|
+
* `coverage/` is tool output too — it is *does any lane exist precisely to look
|
|
35
|
+
* at it*. Something does walk `dist/`; nothing walks `.turbo`, which holds
|
|
36
|
+
* `turbo-<task>.log` telemetry plus, when `cacheDir` points inside it,
|
|
37
|
+
* hash-keyed cache entries that are COPIES of package build output. That is the
|
|
38
|
+
* `.worktrees` failure two lines up, not the `dist` one: a crawl that descends
|
|
39
|
+
* into it reports the same file twice under two paths. Filing it as build
|
|
40
|
+
* output would have it exactly backwards, since a lane spreading only this list
|
|
41
|
+
* is by definition a lane that wants to see built output — and so is precisely
|
|
42
|
+
* the lane that must not walk a cache of copies. (Turborepo is a common enough
|
|
43
|
+
* monorepo layout that this is not hypothetical: every package in THIS repo has
|
|
44
|
+
* a `.turbo/`.)
|
|
45
|
+
*
|
|
32
46
|
* Note these patterns only bite when `respectGitignore` is false; the fast
|
|
33
|
-
* `git ls-files` path never sees ignored directories in the first place.
|
|
47
|
+
* `git ls-files` path never sees ignored directories in the first place. For
|
|
48
|
+
* `.turbo` that is the only path it could bite on — it is gitignored by every
|
|
49
|
+
* turborepo setup — which is the same position `coverage/` and `.test-output/`
|
|
50
|
+
* are in.
|
|
34
51
|
*/
|
|
35
52
|
export const NEVER_CRAWL_GLOBS = [
|
|
36
53
|
'**/node_modules/**', // Dependencies (40K+ files), never user content
|
|
@@ -39,6 +56,7 @@ export const NEVER_CRAWL_GLOBS = [
|
|
|
39
56
|
'**/.test-output/**', // Test artifacts
|
|
40
57
|
'**/.worktrees/**', // Git worktrees are full repo copies — never traverse
|
|
41
58
|
'**/.claude/worktrees/**', // Claude Code worktrees, same reason
|
|
59
|
+
'**/.turbo/**', // turborepo task logs + cache — see below
|
|
42
60
|
];
|
|
43
61
|
/**
|
|
44
62
|
* Build output — excluded by DEFAULT, but deliberately NOT part of
|
package/dist/file-crawler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-crawler.js","sourceRoot":"","sources":["../src/file-crawler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,OAAO,SAAS,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAyC3D;;;;;;;;;;;;;GAaG;AACH,MAAM,iBAAiB,GAAG,EAAE,GAAG,EAAE,IAAI,EAAW,CAAC;AAEjD
|
|
1
|
+
{"version":3,"file":"file-crawler.js","sourceRoot":"","sources":["../src/file-crawler.ts"],"names":[],"mappings":"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"}
|
package/dist/fs-utils.d.ts
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Filesystem utilities
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Per-run memo for the two filesystem lookups that validation repeats on values
|
|
6
|
+
* which are constant for the whole run: `realpath` of roots, and `readdir` of the
|
|
7
|
+
* directories link targets live in.
|
|
8
|
+
*
|
|
9
|
+
* A markdown corpus resolves thousands of links into a few hundred directories, so
|
|
10
|
+
* the uncached form is an N+1: measured at 9,963 `readdir` calls on a 3,437-document
|
|
11
|
+
* tree and 7,443 on a 1,132-document monorepo. Concurrent callers share the
|
|
12
|
+
* in-flight promise rather than each starting their own syscall.
|
|
13
|
+
*
|
|
14
|
+
* **Instance-based on purpose — never make this a module-level singleton.** The
|
|
15
|
+
* cache holds a *snapshot* of directory contents, and a long-lived process (watch
|
|
16
|
+
* mode, a language server, a daemon) would then answer from a listing taken
|
|
17
|
+
* arbitrarily long ago. The intended lifetime is one instance per validation run,
|
|
18
|
+
* constructed as a local and collected with the run.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const fsCache = new FsLookupCache(); // one per run
|
|
23
|
+
* for (const link of links) {
|
|
24
|
+
* await verifyCaseSensitiveFilename(link.target, fsCache);
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare class FsLookupCache {
|
|
29
|
+
#private;
|
|
30
|
+
/**
|
|
31
|
+
* Canonical path for `targetPath`, falling back to `safePath.resolve()` when the
|
|
32
|
+
* path does not exist or cannot be resolved (a non-existent file has no realpath,
|
|
33
|
+
* and callers comparing paths still need an answer).
|
|
34
|
+
*
|
|
35
|
+
* @param targetPath - Path to canonicalize
|
|
36
|
+
* @returns Canonical path with forward slashes on every platform
|
|
37
|
+
*/
|
|
38
|
+
realpath(targetPath: string): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Entry names of `dirPath`, or `null` when it cannot be read (missing directory,
|
|
41
|
+
* no permission). The unreadable answer is cached too — re-asking is the same
|
|
42
|
+
* failed syscall.
|
|
43
|
+
*
|
|
44
|
+
* @param dirPath - Directory to list
|
|
45
|
+
* @returns Entry names, or `null` if the directory could not be read
|
|
46
|
+
*/
|
|
47
|
+
readdir(dirPath: string): Promise<string[] | null>;
|
|
48
|
+
}
|
|
4
49
|
/**
|
|
5
50
|
* Recursively copy a directory
|
|
6
51
|
*
|
|
@@ -18,20 +63,32 @@ export declare function copyDirectory(src: string, dest: string): Promise<void>;
|
|
|
18
63
|
* the case doesn't match. This function checks that the actual filename on disk
|
|
19
64
|
* matches the requested path exactly (case-sensitive).
|
|
20
65
|
*
|
|
66
|
+
* Answering requires listing the target's parent directory. Callers checking many
|
|
67
|
+
* paths (every link in a corpus) hit the same handful of directories over and over,
|
|
68
|
+
* so the listing comes from a caller-supplied {@link FsLookupCache}.
|
|
69
|
+
*
|
|
70
|
+
* The cache parameter is **required rather than defaulted on purpose**: a default
|
|
71
|
+
* would let an unmigrated call site silently keep the un-memoized behaviour, which
|
|
72
|
+
* is a no-op wearing the shape of a fix. `new FsLookupCache()` per call reproduces
|
|
73
|
+
* the old behaviour exactly, so migrating is mechanical — but it has to be a
|
|
74
|
+
* decision someone made.
|
|
75
|
+
*
|
|
21
76
|
* @param filePath - Absolute path to the file to verify
|
|
77
|
+
* @param fsCache - Per-run lookup cache (one instance per validation run)
|
|
22
78
|
* @returns Object with exists flag and actual filename (or null if not found)
|
|
23
79
|
*
|
|
24
80
|
* @example
|
|
25
81
|
* ```typescript
|
|
26
82
|
* // On case-insensitive filesystem with file "README.md"
|
|
27
|
-
* const
|
|
83
|
+
* const fsCache = new FsLookupCache();
|
|
84
|
+
* const result1 = await verifyCaseSensitiveFilename('/project/README.md', fsCache);
|
|
28
85
|
* // { exists: true, actualName: 'README.md' }
|
|
29
86
|
*
|
|
30
|
-
* const result2 = await verifyCaseSensitiveFilename('/project/readme.md');
|
|
87
|
+
* const result2 = await verifyCaseSensitiveFilename('/project/readme.md', fsCache);
|
|
31
88
|
* // { exists: false, actualName: 'README.md' } - case mismatch!
|
|
32
89
|
* ```
|
|
33
90
|
*/
|
|
34
|
-
export declare function verifyCaseSensitiveFilename(filePath: string): Promise<{
|
|
91
|
+
export declare function verifyCaseSensitiveFilename(filePath: string, fsCache: FsLookupCache): Promise<{
|
|
35
92
|
exists: boolean;
|
|
36
93
|
actualName: string | null;
|
|
37
94
|
}>;
|
package/dist/fs-utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs-utils.d.ts","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"fs-utils.d.ts","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,aAAa;;IAOxB;;;;;;;OAOG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAe7C;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC;CASnD;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAgCzD"}
|
package/dist/fs-utils.js
CHANGED
|
@@ -3,7 +3,77 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import fs from 'node:fs/promises';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { toForwardSlash } from './path-core.js';
|
|
6
7
|
import { safePath } from './path-utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Per-run memo for the two filesystem lookups that validation repeats on values
|
|
10
|
+
* which are constant for the whole run: `realpath` of roots, and `readdir` of the
|
|
11
|
+
* directories link targets live in.
|
|
12
|
+
*
|
|
13
|
+
* A markdown corpus resolves thousands of links into a few hundred directories, so
|
|
14
|
+
* the uncached form is an N+1: measured at 9,963 `readdir` calls on a 3,437-document
|
|
15
|
+
* tree and 7,443 on a 1,132-document monorepo. Concurrent callers share the
|
|
16
|
+
* in-flight promise rather than each starting their own syscall.
|
|
17
|
+
*
|
|
18
|
+
* **Instance-based on purpose — never make this a module-level singleton.** The
|
|
19
|
+
* cache holds a *snapshot* of directory contents, and a long-lived process (watch
|
|
20
|
+
* mode, a language server, a daemon) would then answer from a listing taken
|
|
21
|
+
* arbitrarily long ago. The intended lifetime is one instance per validation run,
|
|
22
|
+
* constructed as a local and collected with the run.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const fsCache = new FsLookupCache(); // one per run
|
|
27
|
+
* for (const link of links) {
|
|
28
|
+
* await verifyCaseSensitiveFilename(link.target, fsCache);
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export class FsLookupCache {
|
|
33
|
+
/** Directory path → its entry names, or `null` when the directory is unreadable. */
|
|
34
|
+
#listings = new Map();
|
|
35
|
+
/** Path → its canonical path, falling back to the resolved path. */
|
|
36
|
+
#realpaths = new Map();
|
|
37
|
+
/**
|
|
38
|
+
* Canonical path for `targetPath`, falling back to `safePath.resolve()` when the
|
|
39
|
+
* path does not exist or cannot be resolved (a non-existent file has no realpath,
|
|
40
|
+
* and callers comparing paths still need an answer).
|
|
41
|
+
*
|
|
42
|
+
* @param targetPath - Path to canonicalize
|
|
43
|
+
* @returns Canonical path with forward slashes on every platform
|
|
44
|
+
*/
|
|
45
|
+
realpath(targetPath) {
|
|
46
|
+
const cached = this.#realpaths.get(targetPath);
|
|
47
|
+
if (cached !== undefined)
|
|
48
|
+
return cached;
|
|
49
|
+
// Stored before the first `await` anywhere can run, so concurrent callers
|
|
50
|
+
// reaching this method share the one in-flight promise.
|
|
51
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- caller-validated path
|
|
52
|
+
const pending = fs
|
|
53
|
+
.realpath(targetPath)
|
|
54
|
+
.then(toForwardSlash)
|
|
55
|
+
.catch(() => safePath.resolve(targetPath));
|
|
56
|
+
this.#realpaths.set(targetPath, pending);
|
|
57
|
+
return pending;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Entry names of `dirPath`, or `null` when it cannot be read (missing directory,
|
|
61
|
+
* no permission). The unreadable answer is cached too — re-asking is the same
|
|
62
|
+
* failed syscall.
|
|
63
|
+
*
|
|
64
|
+
* @param dirPath - Directory to list
|
|
65
|
+
* @returns Entry names, or `null` if the directory could not be read
|
|
66
|
+
*/
|
|
67
|
+
readdir(dirPath) {
|
|
68
|
+
const cached = this.#listings.get(dirPath);
|
|
69
|
+
if (cached !== undefined)
|
|
70
|
+
return cached;
|
|
71
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- caller-validated path
|
|
72
|
+
const pending = fs.readdir(dirPath).catch(() => null);
|
|
73
|
+
this.#listings.set(dirPath, pending);
|
|
74
|
+
return pending;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
7
77
|
/**
|
|
8
78
|
* Recursively copy a directory
|
|
9
79
|
*
|
|
@@ -36,31 +106,39 @@ export async function copyDirectory(src, dest) {
|
|
|
36
106
|
* the case doesn't match. This function checks that the actual filename on disk
|
|
37
107
|
* matches the requested path exactly (case-sensitive).
|
|
38
108
|
*
|
|
109
|
+
* Answering requires listing the target's parent directory. Callers checking many
|
|
110
|
+
* paths (every link in a corpus) hit the same handful of directories over and over,
|
|
111
|
+
* so the listing comes from a caller-supplied {@link FsLookupCache}.
|
|
112
|
+
*
|
|
113
|
+
* The cache parameter is **required rather than defaulted on purpose**: a default
|
|
114
|
+
* would let an unmigrated call site silently keep the un-memoized behaviour, which
|
|
115
|
+
* is a no-op wearing the shape of a fix. `new FsLookupCache()` per call reproduces
|
|
116
|
+
* the old behaviour exactly, so migrating is mechanical — but it has to be a
|
|
117
|
+
* decision someone made.
|
|
118
|
+
*
|
|
39
119
|
* @param filePath - Absolute path to the file to verify
|
|
120
|
+
* @param fsCache - Per-run lookup cache (one instance per validation run)
|
|
40
121
|
* @returns Object with exists flag and actual filename (or null if not found)
|
|
41
122
|
*
|
|
42
123
|
* @example
|
|
43
124
|
* ```typescript
|
|
44
125
|
* // On case-insensitive filesystem with file "README.md"
|
|
45
|
-
* const
|
|
126
|
+
* const fsCache = new FsLookupCache();
|
|
127
|
+
* const result1 = await verifyCaseSensitiveFilename('/project/README.md', fsCache);
|
|
46
128
|
* // { exists: true, actualName: 'README.md' }
|
|
47
129
|
*
|
|
48
|
-
* const result2 = await verifyCaseSensitiveFilename('/project/readme.md');
|
|
130
|
+
* const result2 = await verifyCaseSensitiveFilename('/project/readme.md', fsCache);
|
|
49
131
|
* // { exists: false, actualName: 'README.md' } - case mismatch!
|
|
50
132
|
* ```
|
|
51
133
|
*/
|
|
52
|
-
export async function verifyCaseSensitiveFilename(filePath) {
|
|
134
|
+
export async function verifyCaseSensitiveFilename(filePath, fsCache) {
|
|
53
135
|
// Get parent directory and expected filename
|
|
54
136
|
const parentDir = path.dirname(filePath);
|
|
55
137
|
const expectedName = path.basename(filePath);
|
|
56
138
|
// Read actual directory entries
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
entries = await fs.readdir(parentDir);
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
// Parent directory doesn't exist
|
|
139
|
+
const entries = await fsCache.readdir(parentDir);
|
|
140
|
+
if (entries === null) {
|
|
141
|
+
// Parent directory doesn't exist (or can't be read)
|
|
64
142
|
return { exists: false, actualName: null };
|
|
65
143
|
}
|
|
66
144
|
// Find the actual filename (case-sensitive exact match)
|
package/dist/fs-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs-utils.js","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW,EAAE,IAAY;IAC3D,mGAAmG;IACnG,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,mGAAmG;IACnG,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAEjD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;AACH,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"fs-utils.js","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"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"}
|