@vibe-agent-toolkit/utils 0.2.0-rc.1 → 0.2.0-rc.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -23
- package/dist/asset-reference.js +3 -11
- package/dist/asset-reference.js.map +1 -1
- package/dist/compare-code-units.d.ts +28 -0
- package/dist/compare-code-units.d.ts.map +1 -0
- package/dist/compare-code-units.js +33 -0
- package/dist/compare-code-units.js.map +1 -0
- package/dist/crawl-timing.d.ts +657 -0
- package/dist/crawl-timing.d.ts.map +1 -0
- package/dist/crawl-timing.js +765 -0
- package/dist/crawl-timing.js.map +1 -0
- package/dist/crawl.d.ts +2 -4
- package/dist/crawl.d.ts.map +1 -1
- package/dist/crawl.js +2 -4
- package/dist/crawl.js.map +1 -1
- package/dist/dirent-kind.d.ts +83 -0
- package/dist/dirent-kind.d.ts.map +1 -0
- package/dist/dirent-kind.js +129 -0
- package/dist/dirent-kind.js.map +1 -0
- package/dist/entrypoint.d.ts +54 -0
- package/dist/entrypoint.d.ts.map +1 -0
- package/dist/entrypoint.js +71 -0
- package/dist/entrypoint.js.map +1 -0
- package/dist/env-flag.d.ts +57 -0
- package/dist/env-flag.d.ts.map +1 -0
- package/dist/env-flag.js +68 -0
- package/dist/env-flag.js.map +1 -0
- package/dist/errors/errno.d.ts +51 -0
- package/dist/errors/errno.d.ts.map +1 -0
- package/dist/errors/errno.js +112 -0
- package/dist/errors/errno.js.map +1 -0
- package/dist/errors/vat-error.d.ts +60 -0
- package/dist/errors/vat-error.d.ts.map +1 -0
- package/dist/errors/vat-error.js +86 -0
- package/dist/errors/vat-error.js.map +1 -0
- package/dist/file-crawler.d.ts +60 -0
- package/dist/file-crawler.d.ts.map +1 -1
- package/dist/file-crawler.js +133 -29
- package/dist/file-crawler.js.map +1 -1
- package/dist/fs/file-hash.d.ts.map +1 -1
- package/dist/fs/file-hash.js +0 -1
- package/dist/fs/file-hash.js.map +1 -1
- package/dist/fs-utils.d.ts +422 -191
- package/dist/fs-utils.d.ts.map +1 -1
- package/dist/fs-utils.js +534 -204
- package/dist/fs-utils.js.map +1 -1
- package/dist/fs.d.ts +6 -2
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +10 -7
- package/dist/fs.js.map +1 -1
- package/dist/git-run.d.ts +167 -0
- package/dist/git-run.d.ts.map +1 -0
- package/dist/git-run.js +154 -0
- package/dist/git-run.js.map +1 -0
- package/dist/git-snapshot.d.ts +199 -0
- package/dist/git-snapshot.d.ts.map +1 -0
- package/dist/git-snapshot.js +250 -0
- package/dist/git-snapshot.js.map +1 -0
- package/dist/git-tracker.d.ts +166 -3
- package/dist/git-tracker.d.ts.map +1 -1
- package/dist/git-tracker.js +250 -22
- package/dist/git-tracker.js.map +1 -1
- package/dist/git-utils.d.ts +84 -1
- package/dist/git-utils.d.ts.map +1 -1
- package/dist/git-utils.js +208 -82
- package/dist/git-utils.js.map +1 -1
- package/dist/git.d.ts +13 -4
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +12 -4
- package/dist/git.js.map +1 -1
- package/dist/gitignore-checker.d.ts.map +1 -1
- package/dist/gitignore-checker.js +15 -6
- package/dist/gitignore-checker.js.map +1 -1
- package/dist/glob/glob-pattern.js +5 -5
- package/dist/glob/glob-pattern.js.map +1 -1
- package/dist/index.d.ts +32 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +95 -50
- package/dist/index.js.map +1 -1
- package/dist/listing-refusal.d.ts +112 -0
- package/dist/listing-refusal.d.ts.map +1 -0
- package/dist/listing-refusal.js +105 -0
- package/dist/listing-refusal.js.map +1 -0
- package/dist/path-containment.d.ts +79 -0
- package/dist/path-containment.d.ts.map +1 -0
- package/dist/path-containment.js +128 -0
- package/dist/path-containment.js.map +1 -0
- package/dist/path-core.d.ts +113 -18
- package/dist/path-core.d.ts.map +1 -1
- package/dist/path-core.js +139 -23
- package/dist/path-core.js.map +1 -1
- package/dist/path-utils.d.ts.map +1 -1
- package/dist/path-utils.js +42 -47
- package/dist/path-utils.js.map +1 -1
- package/dist/path.d.ts +1 -1
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +1 -1
- package/dist/path.js.map +1 -1
- package/dist/process.d.ts +7 -0
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +7 -0
- package/dist/process.js.map +1 -1
- package/dist/project-utils.d.ts.map +1 -1
- package/dist/project-utils.js +15 -9
- package/dist/project-utils.js.map +1 -1
- package/dist/project.d.ts +7 -5
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +7 -5
- package/dist/project.js.map +1 -1
- package/dist/safe-exec.d.ts +40 -11
- package/dist/safe-exec.d.ts.map +1 -1
- package/dist/safe-exec.js +111 -25
- package/dist/safe-exec.js.map +1 -1
- package/dist/skill-targets.d.ts +2 -2
- package/dist/skill-targets.js +2 -2
- package/dist/skill-test/auth-resolver.d.ts +10 -2
- package/dist/skill-test/auth-resolver.d.ts.map +1 -1
- package/dist/skill-test/auth-resolver.js +26 -11
- package/dist/skill-test/auth-resolver.js.map +1 -1
- package/dist/skill-test/index.d.ts +1 -1
- package/dist/skill-test/index.d.ts.map +1 -1
- package/dist/skill-test/index.js +1 -1
- package/dist/skill-test/index.js.map +1 -1
- package/dist/skill-test/spawn-claude.d.ts +30 -1
- package/dist/skill-test/spawn-claude.d.ts.map +1 -1
- package/dist/skill-test/spawn-claude.js +71 -5
- package/dist/skill-test/spawn-claude.js.map +1 -1
- package/dist/skill-test/transcript.d.ts +41 -2
- package/dist/skill-test/transcript.d.ts.map +1 -1
- package/dist/skill-test/transcript.js +55 -10
- package/dist/skill-test/transcript.js.map +1 -1
- package/dist/spawn-hardened.js +0 -1
- package/dist/spawn-hardened.js.map +1 -1
- package/dist/stdio-blocking.js +13 -14
- package/dist/stdio-blocking.js.map +1 -1
- package/dist/test-helpers.d.ts +135 -100
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +224 -146
- package/dist/test-helpers.js.map +1 -1
- package/dist/testing/executables.d.ts +32 -0
- package/dist/testing/executables.d.ts.map +1 -0
- package/dist/testing/executables.js +70 -0
- package/dist/testing/executables.js.map +1 -0
- package/dist/testing/hostile-tree.d.ts +79 -0
- package/dist/testing/hostile-tree.d.ts.map +1 -0
- package/dist/testing/hostile-tree.js +164 -0
- package/dist/testing/hostile-tree.js.map +1 -0
- package/dist/testing/platform-gates.d.ts +31 -0
- package/dist/testing/platform-gates.d.ts.map +1 -0
- package/dist/testing/platform-gates.js +31 -0
- package/dist/testing/platform-gates.js.map +1 -0
- package/dist/testing/temp-dir.d.ts +255 -0
- package/dist/testing/temp-dir.d.ts.map +1 -0
- package/dist/testing/temp-dir.js +363 -0
- package/dist/testing/temp-dir.js.map +1 -0
- package/dist/testing.d.ts +94 -3
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +119 -3
- package/dist/testing.js.map +1 -1
- package/dist/text-content.d.ts +226 -0
- package/dist/text-content.d.ts.map +1 -0
- package/dist/text-content.js +418 -0
- package/dist/text-content.js.map +1 -0
- package/dist/text-file.d.ts +84 -0
- package/dist/text-file.d.ts.map +1 -0
- package/dist/text-file.js +118 -0
- package/dist/text-file.js.map +1 -0
- package/dist/text.d.ts +16 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +16 -0
- package/dist/text.js.map +1 -0
- package/dist/timing-dump.d.ts +136 -0
- package/dist/timing-dump.d.ts.map +1 -0
- package/dist/timing-dump.js +229 -0
- package/dist/timing-dump.js.map +1 -0
- package/dist/windows-shell.d.ts +0 -1
- package/dist/windows-shell.d.ts.map +1 -1
- package/dist/windows-shell.js +0 -1
- package/dist/windows-shell.js.map +1 -1
- package/dist/zod-introspection.js +2 -2
- package/dist/zod-introspection.js.map +1 -1
- package/eslint/README.md +223 -49
- package/eslint/index.cjs +111 -97
- package/eslint/index.d.cts +16 -6
- package/eslint/rules/callee-name.cjs +23 -0
- package/eslint/rules/commands-import-boundary.cjs +148 -0
- package/eslint/rules/dead-import.cjs +1 -1
- package/eslint/rules/dirent-type-needs-symlink-check.cjs +282 -0
- package/eslint/rules/eslint-rule-factory.cjs +5 -2
- package/eslint/rules/exempt-path-matcher.cjs +2 -1
- package/eslint/rules/explicit-zod-strictness.cjs +120 -0
- package/eslint/rules/no-bare-dynamic-import-path.cjs +5 -1
- package/eslint/rules/no-bare-executable-spawn.cjs +70 -0
- package/eslint/rules/no-bare-symlink-in-tests.cjs +213 -0
- package/eslint/rules/no-blind-catch.cjs +171 -0
- package/eslint/rules/no-child-process-execSync.cjs +8 -0
- package/eslint/rules/no-decaying-referent.cjs +217 -0
- package/eslint/rules/no-dotdot-containment.cjs +160 -0
- package/eslint/rules/no-file-url-string-concat.cjs +4 -1
- package/eslint/rules/no-fragile-entrypoint-guard.cjs +228 -0
- package/eslint/rules/no-fs-mkdirSync.cjs +8 -0
- package/eslint/rules/no-fs-promises-cp.cjs +7 -0
- package/eslint/rules/no-fs-realpathSync.cjs +8 -0
- package/eslint/rules/no-hardcoded-path-split.cjs +29 -18
- package/eslint/rules/no-io-in-unit-tier.cjs +129 -0
- package/eslint/rules/no-literal-process-exit.cjs +230 -0
- package/eslint/rules/no-manual-path-normalize.cjs +136 -107
- package/eslint/rules/no-os-tmpdir.cjs +8 -0
- package/eslint/rules/no-path-operations-in-comparisons.cjs +7 -2
- package/eslint/rules/no-path-sep-in-strings.cjs +7 -2
- package/eslint/rules/no-path-startswith.cjs +7 -2
- package/eslint/rules/no-process-exit-in-phase.cjs +127 -0
- package/eslint/rules/no-raw-node-path.cjs +563 -0
- package/eslint/rules/no-raw-text-decode.cjs +305 -0
- package/eslint/rules/no-registry-count-pin.cjs +255 -0
- package/eslint/rules/no-self-package-import.cjs +168 -0
- package/eslint/rules/no-test-scoped-functions.cjs +10 -3
- package/eslint/rules/no-unix-shell-commands.cjs +6 -2
- package/eslint/rules/no-unsafe-root-join.cjs +23 -2
- package/eslint/rules/no-url-pathname-for-fs.cjs +5 -1
- package/eslint/rules/no-version-literal.cjs +151 -0
- package/eslint/rules/prefer-startswith-over-regex.cjs +9 -0
- package/eslint/rules/require-justified-skip.cjs +8 -2
- package/eslint/rules/safe-import.cjs +1 -1
- package/package.json +16 -16
- package/dist/link-auth/build-headers.d.ts +0 -34
- package/dist/link-auth/build-headers.d.ts.map +0 -1
- package/dist/link-auth/build-headers.js +0 -58
- package/dist/link-auth/build-headers.js.map +0 -1
- package/dist/link-auth/expand-macro.d.ts +0 -38
- package/dist/link-auth/expand-macro.d.ts.map +0 -1
- package/dist/link-auth/expand-macro.js +0 -133
- package/dist/link-auth/expand-macro.js.map +0 -1
- package/dist/link-auth/macros.yaml +0 -50
- package/dist/link-auth/resolve-token.d.ts +0 -83
- package/dist/link-auth/resolve-token.d.ts.map +0 -1
- package/dist/link-auth/resolve-token.js +0 -101
- package/dist/link-auth/resolve-token.js.map +0 -1
- package/dist/link-auth/resolve.d.ts +0 -102
- package/dist/link-auth/resolve.d.ts.map +0 -1
- package/dist/link-auth/resolve.js +0 -66
- package/dist/link-auth/resolve.js.map +0 -1
- package/dist/link-auth/rewrite.d.ts +0 -52
- package/dist/link-auth/rewrite.d.ts.map +0 -1
- package/dist/link-auth/rewrite.js +0 -102
- package/dist/link-auth/rewrite.js.map +0 -1
- package/dist/link-auth/select-provider.d.ts +0 -30
- package/dist/link-auth/select-provider.d.ts.map +0 -1
- package/dist/link-auth/select-provider.js +0 -55
- package/dist/link-auth/select-provider.js.map +0 -1
- package/dist/link-auth/template.d.ts +0 -40
- package/dist/link-auth/template.d.ts.map +0 -1
- package/dist/link-auth/template.js +0 -89
- package/dist/link-auth/template.js.map +0 -1
- package/dist/link-auth/transforms.d.ts +0 -46
- package/dist/link-auth/transforms.d.ts.map +0 -1
- package/dist/link-auth/transforms.js +0 -52
- package/dist/link-auth/transforms.js.map +0 -1
- package/dist/template-entry.d.ts +0 -10
- package/dist/template-entry.d.ts.map +0 -1
- package/dist/template-entry.js +0 -10
- package/dist/template-entry.js.map +0 -1
- package/dist/template.d.ts +0 -7
- package/dist/template.d.ts.map +0 -1
- package/dist/template.js +0 -18
- package/dist/template.js.map +0 -1
- package/eslint/rules/no-path-join.cjs +0 -14
- package/eslint/rules/no-path-relative.cjs +0 -14
- package/eslint/rules/no-path-resolve.cjs +0 -14
- package/eslint/rules/path-function-rule-factory.cjs +0 -496
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a file and decode it through the one content-decoding seam.
|
|
3
|
+
*
|
|
4
|
+
* The replacement for `readFile(path, 'utf-8')`, which decodes inside `fs` with
|
|
5
|
+
* no byte-order-mark handling and no way to express UTF-16BE at all. The
|
|
6
|
+
* *decision* about what the bytes say lives in `text-content.ts`, which is pure
|
|
7
|
+
* and reaches no `node:*` module; this file is only the two lines that get the
|
|
8
|
+
* bytes off disk, and it lives on the `./fs` entry with everything else here
|
|
9
|
+
* that touches the filesystem.
|
|
10
|
+
*
|
|
11
|
+
* Two functions rather than one, because the callers genuinely differ: an
|
|
12
|
+
* enumeration or parse lane is asynchronous throughout, while a manifest or
|
|
13
|
+
* config probe on a startup path is not, and handing the latter a Promise makes
|
|
14
|
+
* it worse rather than more consistent.
|
|
15
|
+
*
|
|
16
|
+
* A caller that needs the RAW bytes as well as the text — to hash them, key
|
|
17
|
+
* them, or report `stat().size` — must not use these: read the bytes once with
|
|
18
|
+
* {@link readDecodableBytes} and call `decodeTextContent` on them, so the digest
|
|
19
|
+
* and the characters come from the same read. `readContentWithKey` in
|
|
20
|
+
* `@vibe-agent-toolkit/resources` is that caller and is shaped exactly that way.
|
|
21
|
+
*/
|
|
22
|
+
import { type DecodedText } from './text-content.js';
|
|
23
|
+
/**
|
|
24
|
+
* Read a file and decode it through {@link decodeTextContent}.
|
|
25
|
+
*
|
|
26
|
+
* @param filePath - Path to read
|
|
27
|
+
* @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
|
|
28
|
+
* @throws Whatever `readFile` throws — callers decide whether that is fatal — and
|
|
29
|
+
* {@link TextTooLargeError} for a file too large to decode, before reading it
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const { text, encoding } = await readTextContent(docPath);
|
|
34
|
+
* // A PowerShell-written document: encoding 'utf-16le', text with no BOM
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function readTextContent(filePath: string): Promise<DecodedText>;
|
|
38
|
+
/**
|
|
39
|
+
* {@link readTextContent}, synchronously.
|
|
40
|
+
*
|
|
41
|
+
* @param filePath - Path to read
|
|
42
|
+
* @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
|
|
43
|
+
* @throws Whatever `readFileSync` throws, and {@link TextTooLargeError} as {@link readDecodableBytes} does
|
|
44
|
+
*/
|
|
45
|
+
export declare function readTextContentSync(filePath: string): DecodedText;
|
|
46
|
+
/**
|
|
47
|
+
* The most bytes that can be decoded into one JS string: the engine's own
|
|
48
|
+
* string-length limit, read from `node:buffer` rather than written down.
|
|
49
|
+
*
|
|
50
|
+
* A bound on BYTES stated in UTF-16 code units, which is exact-or-safe for UTF-8
|
|
51
|
+
* (a UTF-8 byte never yields more than one code unit) and conservative for
|
|
52
|
+
* UTF-16/UTF-32, whose bytes yield half or a quarter as many.
|
|
53
|
+
*/
|
|
54
|
+
export declare const MAX_DECODABLE_BYTES: number;
|
|
55
|
+
/**
|
|
56
|
+
* Whether content of this many bytes is past what can be decoded to a string.
|
|
57
|
+
*
|
|
58
|
+
* @param byteLength - The content's size in bytes, as `stat` reports it
|
|
59
|
+
* @returns True when decoding it would exceed {@link MAX_DECODABLE_BYTES}
|
|
60
|
+
*/
|
|
61
|
+
export declare function exceedsDecodableLength(byteLength: number): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Read a file's raw bytes for decoding — refusing, by `stat` and before reading
|
|
64
|
+
* a byte, one too large to decode at all.
|
|
65
|
+
*
|
|
66
|
+
* The one read path for "bytes that are about to go through
|
|
67
|
+
* {@link decodeTextContent}": {@link readTextContent} uses it, and so does
|
|
68
|
+
* `readContentWithKey` in `@vibe-agent-toolkit/resources`, which needs the raw
|
|
69
|
+
* bytes as well to key them. Without the stat, a 966 MB file is read whole and
|
|
70
|
+
* only then refused by the decoder, and past 2 GiB `readFile` fails first with
|
|
71
|
+
* its own `ERR_FS_FILE_TOO_LARGE` — two different errors for one fact.
|
|
72
|
+
*
|
|
73
|
+
* The size comes from `fstat` on the handle that is then read, not from a
|
|
74
|
+
* separate `stat` of the path, so a rename between the two cannot substitute
|
|
75
|
+
* another file. A file that GROWS past the limit after the `fstat` still reaches
|
|
76
|
+
* the decoder, which raises the same {@link TextTooLargeError}.
|
|
77
|
+
*
|
|
78
|
+
* @param filePath - Path to read
|
|
79
|
+
* @returns The file's bytes
|
|
80
|
+
* @throws {@link TextTooLargeError} when the file is past {@link MAX_DECODABLE_BYTES},
|
|
81
|
+
* and whatever `open`/`readFile` throw otherwise
|
|
82
|
+
*/
|
|
83
|
+
export declare function readDecodableBytes(filePath: string): Promise<Buffer>;
|
|
84
|
+
//# sourceMappingURL=text-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-file.d.ts","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAO,EAAwC,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE3F;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE5E;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAQjE;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAoC,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAElE;AAWD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ1E"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a file and decode it through the one content-decoding seam.
|
|
3
|
+
*
|
|
4
|
+
* The replacement for `readFile(path, 'utf-8')`, which decodes inside `fs` with
|
|
5
|
+
* no byte-order-mark handling and no way to express UTF-16BE at all. The
|
|
6
|
+
* *decision* about what the bytes say lives in `text-content.ts`, which is pure
|
|
7
|
+
* and reaches no `node:*` module; this file is only the two lines that get the
|
|
8
|
+
* bytes off disk, and it lives on the `./fs` entry with everything else here
|
|
9
|
+
* that touches the filesystem.
|
|
10
|
+
*
|
|
11
|
+
* Two functions rather than one, because the callers genuinely differ: an
|
|
12
|
+
* enumeration or parse lane is asynchronous throughout, while a manifest or
|
|
13
|
+
* config probe on a startup path is not, and handing the latter a Promise makes
|
|
14
|
+
* it worse rather than more consistent.
|
|
15
|
+
*
|
|
16
|
+
* A caller that needs the RAW bytes as well as the text — to hash them, key
|
|
17
|
+
* them, or report `stat().size` — must not use these: read the bytes once with
|
|
18
|
+
* {@link readDecodableBytes} and call `decodeTextContent` on them, so the digest
|
|
19
|
+
* and the characters come from the same read. `readContentWithKey` in
|
|
20
|
+
* `@vibe-agent-toolkit/resources` is that caller and is shaped exactly that way.
|
|
21
|
+
*/
|
|
22
|
+
import { constants } from 'node:buffer';
|
|
23
|
+
import { closeSync, fstatSync, openSync, readFileSync } from 'node:fs';
|
|
24
|
+
import { open } from 'node:fs/promises';
|
|
25
|
+
import { decodeTextContent, TextTooLargeError } from './text-content.js';
|
|
26
|
+
/**
|
|
27
|
+
* Read a file and decode it through {@link decodeTextContent}.
|
|
28
|
+
*
|
|
29
|
+
* @param filePath - Path to read
|
|
30
|
+
* @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
|
|
31
|
+
* @throws Whatever `readFile` throws — callers decide whether that is fatal — and
|
|
32
|
+
* {@link TextTooLargeError} for a file too large to decode, before reading it
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const { text, encoding } = await readTextContent(docPath);
|
|
37
|
+
* // A PowerShell-written document: encoding 'utf-16le', text with no BOM
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export async function readTextContent(filePath) {
|
|
41
|
+
return decodeTextContent(await readDecodableBytes(filePath));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* {@link readTextContent}, synchronously.
|
|
45
|
+
*
|
|
46
|
+
* @param filePath - Path to read
|
|
47
|
+
* @returns The decoded text, the encoding used, whether that was a fact, and the replacement-character count
|
|
48
|
+
* @throws Whatever `readFileSync` throws, and {@link TextTooLargeError} as {@link readDecodableBytes} does
|
|
49
|
+
*/
|
|
50
|
+
export function readTextContentSync(filePath) {
|
|
51
|
+
const handle = openSync(filePath, 'r');
|
|
52
|
+
try {
|
|
53
|
+
refuseUndecodableSize(fstatSync(handle).size);
|
|
54
|
+
return decodeTextContent(readFileSync(handle));
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
closeSync(handle);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The most bytes that can be decoded into one JS string: the engine's own
|
|
62
|
+
* string-length limit, read from `node:buffer` rather than written down.
|
|
63
|
+
*
|
|
64
|
+
* A bound on BYTES stated in UTF-16 code units, which is exact-or-safe for UTF-8
|
|
65
|
+
* (a UTF-8 byte never yields more than one code unit) and conservative for
|
|
66
|
+
* UTF-16/UTF-32, whose bytes yield half or a quarter as many.
|
|
67
|
+
*/
|
|
68
|
+
export const MAX_DECODABLE_BYTES = constants.MAX_STRING_LENGTH;
|
|
69
|
+
/**
|
|
70
|
+
* Whether content of this many bytes is past what can be decoded to a string.
|
|
71
|
+
*
|
|
72
|
+
* @param byteLength - The content's size in bytes, as `stat` reports it
|
|
73
|
+
* @returns True when decoding it would exceed {@link MAX_DECODABLE_BYTES}
|
|
74
|
+
*/
|
|
75
|
+
export function exceedsDecodableLength(byteLength) {
|
|
76
|
+
return byteLength > MAX_DECODABLE_BYTES;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Throw {@link TextTooLargeError} for a size no decode can hold.
|
|
80
|
+
*
|
|
81
|
+
* @param byteLength - The content's size in bytes
|
|
82
|
+
*/
|
|
83
|
+
function refuseUndecodableSize(byteLength) {
|
|
84
|
+
if (exceedsDecodableLength(byteLength))
|
|
85
|
+
throw new TextTooLargeError(byteLength);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Read a file's raw bytes for decoding — refusing, by `stat` and before reading
|
|
89
|
+
* a byte, one too large to decode at all.
|
|
90
|
+
*
|
|
91
|
+
* The one read path for "bytes that are about to go through
|
|
92
|
+
* {@link decodeTextContent}": {@link readTextContent} uses it, and so does
|
|
93
|
+
* `readContentWithKey` in `@vibe-agent-toolkit/resources`, which needs the raw
|
|
94
|
+
* bytes as well to key them. Without the stat, a 966 MB file is read whole and
|
|
95
|
+
* only then refused by the decoder, and past 2 GiB `readFile` fails first with
|
|
96
|
+
* its own `ERR_FS_FILE_TOO_LARGE` — two different errors for one fact.
|
|
97
|
+
*
|
|
98
|
+
* The size comes from `fstat` on the handle that is then read, not from a
|
|
99
|
+
* separate `stat` of the path, so a rename between the two cannot substitute
|
|
100
|
+
* another file. A file that GROWS past the limit after the `fstat` still reaches
|
|
101
|
+
* the decoder, which raises the same {@link TextTooLargeError}.
|
|
102
|
+
*
|
|
103
|
+
* @param filePath - Path to read
|
|
104
|
+
* @returns The file's bytes
|
|
105
|
+
* @throws {@link TextTooLargeError} when the file is past {@link MAX_DECODABLE_BYTES},
|
|
106
|
+
* and whatever `open`/`readFile` throw otherwise
|
|
107
|
+
*/
|
|
108
|
+
export async function readDecodableBytes(filePath) {
|
|
109
|
+
const handle = await open(filePath, 'r');
|
|
110
|
+
try {
|
|
111
|
+
refuseUndecodableSize((await handle.stat()).size);
|
|
112
|
+
return await handle.readFile();
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
await handle.close();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=text-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-file.js","sourceRoot":"","sources":["../src/text-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAoB,MAAM,mBAAmB,CAAC;AAE3F;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,OAAO,iBAAiB,CAAC,MAAM,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACvC,IAAI,CAAC;QACH,qBAAqB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;QAC9C,OAAO,iBAAiB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,MAAM,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAW,SAAS,CAAC,iBAAiB,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB;IACvD,OAAO,UAAU,GAAG,mBAAmB,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,UAAkB;IAC/C,IAAI,sBAAsB,CAAC,UAAU,CAAC;QAAE,MAAM,IAAI,iBAAiB,CAAC,UAAU,CAAC,CAAC;AAClF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IACvD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,qBAAqB,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAClD,OAAO,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;IACjC,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-agent-toolkit/utils/text
|
|
3
|
+
*
|
|
4
|
+
* **The one way bytes become text.** Encoding detection from a byte-order mark,
|
|
5
|
+
* BOM stripping, and a stated default when there is no BOM — see
|
|
6
|
+
* `text-content.ts` for what is a fact, what is an assumption, and the two
|
|
7
|
+
* limitations that are deliberately not guessed around.
|
|
8
|
+
*
|
|
9
|
+
* Pure: this entry reaches no `node:*` builtin and no third-party package, so
|
|
10
|
+
* bytes from a git blob, an HTTP response or a zip entry decode through exactly
|
|
11
|
+
* the same function as bytes from disk. For the read-a-file case, import
|
|
12
|
+
* `readTextContent` from `@vibe-agent-toolkit/utils/fs`, which is this plus a
|
|
13
|
+
* `readFile`.
|
|
14
|
+
*/
|
|
15
|
+
export { decodeTextContent, type DecodedText, type EncodingSource, type TextEncoding, type TextProvenance, TextTooLargeError, } from './text-content.js';
|
|
16
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vibe-agent-toolkit/utils/text
|
|
3
|
+
*
|
|
4
|
+
* **The one way bytes become text.** Encoding detection from a byte-order mark,
|
|
5
|
+
* BOM stripping, and a stated default when there is no BOM — see
|
|
6
|
+
* `text-content.ts` for what is a fact, what is an assumption, and the two
|
|
7
|
+
* limitations that are deliberately not guessed around.
|
|
8
|
+
*
|
|
9
|
+
* Pure: this entry reaches no `node:*` builtin and no third-party package, so
|
|
10
|
+
* bytes from a git blob, an HTTP response or a zip entry decode through exactly
|
|
11
|
+
* the same function as bytes from disk. For the read-a-file case, import
|
|
12
|
+
* `readTextContent` from `@vibe-agent-toolkit/utils/fs`, which is this plus a
|
|
13
|
+
* `readFile`.
|
|
14
|
+
*/
|
|
15
|
+
export { decodeTextContent, TextTooLargeError, } from './text-content.js';
|
|
16
|
+
//# sourceMappingURL=text.js.map
|
package/dist/text.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,iBAAiB,EAKjB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The on-disk plumbing every VAT timing seam shares.
|
|
3
|
+
*
|
|
4
|
+
* Two seams write per-process JSON dumps to an operator-named directory:
|
|
5
|
+
* `@vibe-agent-toolkit/resources`' `parse-timing.ts` (which pass inside a parser
|
|
6
|
+
* owns the time) and this package's `crawl-timing.ts` (which contributor or
|
|
7
|
+
* crawler owns the time). They sit in different packages because `crawl-timing`
|
|
8
|
+
* has to bracket `GitTracker`, which is here — so this module lives at the lower
|
|
9
|
+
* of the two and is exported for the higher one. What they
|
|
10
|
+
* MEASURE could not be less alike — one axis is a closed enum of parser passes,
|
|
11
|
+
* the other is an open set of contributor ids — but every property that makes
|
|
12
|
+
* the *file* trustworthy is identical between them, and the lab's
|
|
13
|
+
* `harness/dumps.ts` already says so from the reading end:
|
|
14
|
+
*
|
|
15
|
+
* 1. A directory that cannot be created is reported the moment the seam turns
|
|
16
|
+
* on, while there is still a run to abandon — never at exit, where a failure
|
|
17
|
+
* costs the whole measurement.
|
|
18
|
+
* 2. A dump failure is written to **stderr and never thrown**. These run from
|
|
19
|
+
* an `exit` listener, where a throw changes the process's exit behaviour, and
|
|
20
|
+
* they must never touch stdout, which carries vat's report.
|
|
21
|
+
* 3. A pid can file more than one dump, and the name is CLAIMED rather than
|
|
22
|
+
* checked. `vat validate` spawns the vat binary once per phase and pids are
|
|
23
|
+
* reused, so `<stem>-<pid>.json` genuinely collides; worse, worker threads
|
|
24
|
+
* SHARE their parent's pid, so a thread pool's whole cohort competes for one
|
|
25
|
+
* `<stem>-<pid>` sequence at the same instant. The name gains a counter
|
|
26
|
+
* rather than overwriting, and the counter is settled by
|
|
27
|
+
* {@link EXCLUSIVE_CREATE} so the OS — not a check-then-write gap — decides
|
|
28
|
+
* who won it.
|
|
29
|
+
* 4. The process's own wall and CPU time is read ONCE, at dump time. It is a
|
|
30
|
+
* lifetime figure for the process and never a duration of the measured work;
|
|
31
|
+
* its value is the RATIO, which tells a reader whether the wall-timed
|
|
32
|
+
* brackets above it were competing with a loaded machine.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ **A reader must never SUM these across dumps.** Point 3 means one
|
|
35
|
+
* command routinely files several, and a vat command's phase processes
|
|
36
|
+
* overlap in time — so summing their lifetimes counts the same wall clock
|
|
37
|
+
* more than once and produces a "total" longer than the command took. The
|
|
38
|
+
* figure is per process, and the only honest aggregate over several is the
|
|
39
|
+
* per-process ratio read one dump at a time. The `crawl` facet keeps one
|
|
40
|
+
* record per dump and publishes no total for exactly this reason; `parse`
|
|
41
|
+
* still sums, which is a known review finding annotated at
|
|
42
|
+
* `facets/parse/dump.ts`. This list is where both seams learn what makes a
|
|
43
|
+
* dump trustworthy, so the hazard belongs here rather than only beside the
|
|
44
|
+
* consumer that already fixed it.
|
|
45
|
+
*
|
|
46
|
+
* Writing that twice would give two seams two chances to diverge on the one
|
|
47
|
+
* thing a reader has to be able to trust identically. What each seam keeps for
|
|
48
|
+
* itself is its accumulator shape, its dump body and the noun it is called by.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* Process-level wall and CPU time, read ONCE when a dump is written.
|
|
52
|
+
*
|
|
53
|
+
* All three are lifetime figures for the whole process, not for the measured
|
|
54
|
+
* work: the point of carrying them is the *ratio*. CPU well below wall means the
|
|
55
|
+
* process was waiting rather than computing, and every wall-timed bracket in the
|
|
56
|
+
* dump carries that waiting inside it. CPU above wall is normal and not an
|
|
57
|
+
* error — `process.cpuUsage()` sums every thread, including libuv's pool.
|
|
58
|
+
*/
|
|
59
|
+
export interface TimingProcess {
|
|
60
|
+
/** Wall clock since this process started. */
|
|
61
|
+
wallMs: number;
|
|
62
|
+
/** User CPU consumed by the process, across all its threads. */
|
|
63
|
+
cpuUserMs: number;
|
|
64
|
+
/** System CPU consumed by the process, across all its threads. */
|
|
65
|
+
cpuSystemMs: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Ceiling on the pid-collision search. A directory holding this many dumps for
|
|
69
|
+
* one pid is a runaway, not a collision, and giving up on a reported line is a
|
|
70
|
+
* better outcome than spinning.
|
|
71
|
+
*
|
|
72
|
+
* Exhausting it does NOT fall back to overwriting the last slot. That was the
|
|
73
|
+
* old behaviour, and it destroyed a dump for the same reason the check-then-
|
|
74
|
+
* write gap did — silently, and precisely when the directory was busiest.
|
|
75
|
+
*/
|
|
76
|
+
export declare const MAX_DUMP_COLLISIONS = 1000;
|
|
77
|
+
/**
|
|
78
|
+
* Reduce a raw env value to a directory or `null`.
|
|
79
|
+
*
|
|
80
|
+
* An empty-string value counts as absent: `VAT_PARSE_TIMING=` in a shell profile
|
|
81
|
+
* is a variable somebody meant to unset, not a request to dump into the process's
|
|
82
|
+
* working directory.
|
|
83
|
+
*
|
|
84
|
+
* @param raw - The env var's value, if set
|
|
85
|
+
* @returns The dump directory, or `null` when the seam is off
|
|
86
|
+
*/
|
|
87
|
+
export declare function normalizeTimingDirectory(raw: string | undefined): string | null;
|
|
88
|
+
/**
|
|
89
|
+
* Report a dump problem on stderr.
|
|
90
|
+
*
|
|
91
|
+
* Never throws and never touches stdout: vat's stdout carries a YAML report, and
|
|
92
|
+
* an exit handler that threw would change the process's exit behaviour.
|
|
93
|
+
*
|
|
94
|
+
* @param noun - What the seam is called, so a reader knows which instrument failed
|
|
95
|
+
* @param target - Path the failure concerns
|
|
96
|
+
* @param error - Whatever was caught
|
|
97
|
+
*/
|
|
98
|
+
export declare function reportTimingDumpFailure(noun: string, target: string, error: unknown): void;
|
|
99
|
+
/**
|
|
100
|
+
* Create the dump directory, swallowing failure.
|
|
101
|
+
*
|
|
102
|
+
* Done once when the seam turns on rather than at exit, so a bad path is
|
|
103
|
+
* reported while there is still a run to abandon — and so the exit handler does
|
|
104
|
+
* the minimum possible work.
|
|
105
|
+
*
|
|
106
|
+
* @param noun - What the seam is called, for the failure line
|
|
107
|
+
* @param directory - Directory dumps will be written to
|
|
108
|
+
*/
|
|
109
|
+
export declare function ensureTimingDirectory(noun: string, directory: string): void;
|
|
110
|
+
/**
|
|
111
|
+
* Read this process's lifetime wall and CPU time.
|
|
112
|
+
*
|
|
113
|
+
* Called ONCE per dump — two syscalls for a whole run, which is why the process
|
|
114
|
+
* level can afford a CPU reading that a per-bracket level cannot. Deliberately
|
|
115
|
+
* not an accumulator and deliberately not reset: it describes the process, not
|
|
116
|
+
* the measurement window.
|
|
117
|
+
*
|
|
118
|
+
* @returns Wall clock and CPU since process start, in milliseconds
|
|
119
|
+
*/
|
|
120
|
+
export declare function readTimingProcess(): TimingProcess;
|
|
121
|
+
/**
|
|
122
|
+
* Write one seam's dump, if the seam is on.
|
|
123
|
+
*
|
|
124
|
+
* The body is built lazily, inside this call, so a disabled seam never pays to
|
|
125
|
+
* snapshot accumulators nobody will read — and a `build` that throws is reported
|
|
126
|
+
* like any other failure, because this runs from an `exit` listener where a
|
|
127
|
+
* throw would change the process's exit behaviour.
|
|
128
|
+
*
|
|
129
|
+
* @param noun - What the seam is called, for any failure line
|
|
130
|
+
* @param directory - Where to write, or `null` when the seam is off
|
|
131
|
+
* @param basename - Basename stem for the file
|
|
132
|
+
* @param build - Produces the dump body
|
|
133
|
+
* @returns The path written, or `null` when the seam is off or nothing was written
|
|
134
|
+
*/
|
|
135
|
+
export declare function writeTimingDump(noun: string, directory: string | null, basename: string, build: () => unknown): string | null;
|
|
136
|
+
//# sourceMappingURL=timing-dump.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing-dump.d.ts","sourceRoot":"","sources":["../src/timing-dump.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAOH;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;CACrB;AAQD;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,OAAO,CAAC;AA6BxC;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAE/E;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAG1F;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAM3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,IAAI,aAAa,CAOjD;AAyDD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,OAAO,GACnB,MAAM,GAAG,IAAI,CAoBf"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The on-disk plumbing every VAT timing seam shares.
|
|
3
|
+
*
|
|
4
|
+
* Two seams write per-process JSON dumps to an operator-named directory:
|
|
5
|
+
* `@vibe-agent-toolkit/resources`' `parse-timing.ts` (which pass inside a parser
|
|
6
|
+
* owns the time) and this package's `crawl-timing.ts` (which contributor or
|
|
7
|
+
* crawler owns the time). They sit in different packages because `crawl-timing`
|
|
8
|
+
* has to bracket `GitTracker`, which is here — so this module lives at the lower
|
|
9
|
+
* of the two and is exported for the higher one. What they
|
|
10
|
+
* MEASURE could not be less alike — one axis is a closed enum of parser passes,
|
|
11
|
+
* the other is an open set of contributor ids — but every property that makes
|
|
12
|
+
* the *file* trustworthy is identical between them, and the lab's
|
|
13
|
+
* `harness/dumps.ts` already says so from the reading end:
|
|
14
|
+
*
|
|
15
|
+
* 1. A directory that cannot be created is reported the moment the seam turns
|
|
16
|
+
* on, while there is still a run to abandon — never at exit, where a failure
|
|
17
|
+
* costs the whole measurement.
|
|
18
|
+
* 2. A dump failure is written to **stderr and never thrown**. These run from
|
|
19
|
+
* an `exit` listener, where a throw changes the process's exit behaviour, and
|
|
20
|
+
* they must never touch stdout, which carries vat's report.
|
|
21
|
+
* 3. A pid can file more than one dump, and the name is CLAIMED rather than
|
|
22
|
+
* checked. `vat validate` spawns the vat binary once per phase and pids are
|
|
23
|
+
* reused, so `<stem>-<pid>.json` genuinely collides; worse, worker threads
|
|
24
|
+
* SHARE their parent's pid, so a thread pool's whole cohort competes for one
|
|
25
|
+
* `<stem>-<pid>` sequence at the same instant. The name gains a counter
|
|
26
|
+
* rather than overwriting, and the counter is settled by
|
|
27
|
+
* {@link EXCLUSIVE_CREATE} so the OS — not a check-then-write gap — decides
|
|
28
|
+
* who won it.
|
|
29
|
+
* 4. The process's own wall and CPU time is read ONCE, at dump time. It is a
|
|
30
|
+
* lifetime figure for the process and never a duration of the measured work;
|
|
31
|
+
* its value is the RATIO, which tells a reader whether the wall-timed
|
|
32
|
+
* brackets above it were competing with a loaded machine.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ **A reader must never SUM these across dumps.** Point 3 means one
|
|
35
|
+
* command routinely files several, and a vat command's phase processes
|
|
36
|
+
* overlap in time — so summing their lifetimes counts the same wall clock
|
|
37
|
+
* more than once and produces a "total" longer than the command took. The
|
|
38
|
+
* figure is per process, and the only honest aggregate over several is the
|
|
39
|
+
* per-process ratio read one dump at a time. The `crawl` facet keeps one
|
|
40
|
+
* record per dump and publishes no total for exactly this reason; `parse`
|
|
41
|
+
* still sums, which is a known review finding annotated at
|
|
42
|
+
* `facets/parse/dump.ts`. This list is where both seams learn what makes a
|
|
43
|
+
* dump trustworthy, so the hazard belongs here rather than only beside the
|
|
44
|
+
* consumer that already fixed it.
|
|
45
|
+
*
|
|
46
|
+
* Writing that twice would give two seams two chances to diverge on the one
|
|
47
|
+
* thing a reader has to be able to trust identically. What each seam keeps for
|
|
48
|
+
* itself is its accumulator shape, its dump body and the noun it is called by.
|
|
49
|
+
*/
|
|
50
|
+
import { writeFileSync } from 'node:fs';
|
|
51
|
+
import { safePath } from './path-core.js';
|
|
52
|
+
import { mkdirSyncReal } from './path-utils.js';
|
|
53
|
+
/** `process.cpuUsage()` reports microseconds; a dump reports milliseconds. */
|
|
54
|
+
const MICROSECONDS_PER_MS = 1000;
|
|
55
|
+
/** `process.uptime()` reports seconds; a dump reports milliseconds. */
|
|
56
|
+
const MS_PER_SECOND = 1000;
|
|
57
|
+
/**
|
|
58
|
+
* Ceiling on the pid-collision search. A directory holding this many dumps for
|
|
59
|
+
* one pid is a runaway, not a collision, and giving up on a reported line is a
|
|
60
|
+
* better outcome than spinning.
|
|
61
|
+
*
|
|
62
|
+
* Exhausting it does NOT fall back to overwriting the last slot. That was the
|
|
63
|
+
* old behaviour, and it destroyed a dump for the same reason the check-then-
|
|
64
|
+
* write gap did — silently, and precisely when the directory was busiest.
|
|
65
|
+
*/
|
|
66
|
+
export const MAX_DUMP_COLLISIONS = 1000;
|
|
67
|
+
/**
|
|
68
|
+
* The `writeFileSync` flag that makes claiming a name atomic.
|
|
69
|
+
*
|
|
70
|
+
* `wx` is create-exclusive: the OS fails the call with `EEXIST` when the path
|
|
71
|
+
* already exists, so exactly one caller wins each name no matter how many are
|
|
72
|
+
* racing for it. Asking `existsSync` first and writing second is two operations
|
|
73
|
+
* with a gap in between, and threads sharing a pid land in that gap routinely —
|
|
74
|
+
* a measured 8-worker parse pool lost 6 of 9 dumps to it, reporting 44 of 172
|
|
75
|
+
* documents as if that were the whole run.
|
|
76
|
+
*/
|
|
77
|
+
const EXCLUSIVE_CREATE = 'wx';
|
|
78
|
+
/** How the failure line names the ceiling case, so it reads unlike an OS error. */
|
|
79
|
+
const CEILING_DETAIL = `every name up to the ${String(MAX_DUMP_COLLISIONS)}-collision ceiling was already claimed`;
|
|
80
|
+
/**
|
|
81
|
+
* Reduce a raw env value to a directory or `null`.
|
|
82
|
+
*
|
|
83
|
+
* An empty-string value counts as absent: `VAT_PARSE_TIMING=` in a shell profile
|
|
84
|
+
* is a variable somebody meant to unset, not a request to dump into the process's
|
|
85
|
+
* working directory.
|
|
86
|
+
*
|
|
87
|
+
* @param raw - The env var's value, if set
|
|
88
|
+
* @returns The dump directory, or `null` when the seam is off
|
|
89
|
+
*/
|
|
90
|
+
export function normalizeTimingDirectory(raw) {
|
|
91
|
+
return raw === undefined || raw === '' ? null : raw;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Report a dump problem on stderr.
|
|
95
|
+
*
|
|
96
|
+
* Never throws and never touches stdout: vat's stdout carries a YAML report, and
|
|
97
|
+
* an exit handler that threw would change the process's exit behaviour.
|
|
98
|
+
*
|
|
99
|
+
* @param noun - What the seam is called, so a reader knows which instrument failed
|
|
100
|
+
* @param target - Path the failure concerns
|
|
101
|
+
* @param error - Whatever was caught
|
|
102
|
+
*/
|
|
103
|
+
export function reportTimingDumpFailure(noun, target, error) {
|
|
104
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
105
|
+
process.stderr.write(`vat: ${noun} dump failed for ${target}: ${detail}\n`);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Create the dump directory, swallowing failure.
|
|
109
|
+
*
|
|
110
|
+
* Done once when the seam turns on rather than at exit, so a bad path is
|
|
111
|
+
* reported while there is still a run to abandon — and so the exit handler does
|
|
112
|
+
* the minimum possible work.
|
|
113
|
+
*
|
|
114
|
+
* @param noun - What the seam is called, for the failure line
|
|
115
|
+
* @param directory - Directory dumps will be written to
|
|
116
|
+
*/
|
|
117
|
+
export function ensureTimingDirectory(noun, directory) {
|
|
118
|
+
try {
|
|
119
|
+
mkdirSyncReal(directory, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
reportTimingDumpFailure(noun, directory, error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Read this process's lifetime wall and CPU time.
|
|
127
|
+
*
|
|
128
|
+
* Called ONCE per dump — two syscalls for a whole run, which is why the process
|
|
129
|
+
* level can afford a CPU reading that a per-bracket level cannot. Deliberately
|
|
130
|
+
* not an accumulator and deliberately not reset: it describes the process, not
|
|
131
|
+
* the measurement window.
|
|
132
|
+
*
|
|
133
|
+
* @returns Wall clock and CPU since process start, in milliseconds
|
|
134
|
+
*/
|
|
135
|
+
export function readTimingProcess() {
|
|
136
|
+
const cpu = process.cpuUsage();
|
|
137
|
+
return {
|
|
138
|
+
wallMs: process.uptime() * MS_PER_SECOND,
|
|
139
|
+
cpuUserMs: cpu.user / MICROSECONDS_PER_MS,
|
|
140
|
+
cpuSystemMs: cpu.system / MICROSECONDS_PER_MS,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The nth candidate name in one pid's dump sequence.
|
|
145
|
+
*
|
|
146
|
+
* @param directory - Directory dumps are written to
|
|
147
|
+
* @param stem - Basename and pid, already joined
|
|
148
|
+
* @param collision - 0 for the unsuffixed name, then the collision counter
|
|
149
|
+
* @returns The candidate path
|
|
150
|
+
*/
|
|
151
|
+
function timingDumpCandidate(directory, stem, collision) {
|
|
152
|
+
const suffix = collision === 0 ? '' : `-${String(collision)}`;
|
|
153
|
+
return safePath.join(directory, `${stem}${suffix}.json`);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Whether a caught write failure means somebody else already holds the name.
|
|
157
|
+
*
|
|
158
|
+
* @param error - Whatever `writeFileSync` threw
|
|
159
|
+
* @returns `true` only for `EEXIST`, which is a lost race and not a fault
|
|
160
|
+
*/
|
|
161
|
+
function isNameAlreadyTaken(error) {
|
|
162
|
+
return error instanceof Error && 'code' in error && error.code === 'EEXIST';
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Claim a name by creating it, and fill it in the same operation.
|
|
166
|
+
*
|
|
167
|
+
* Choosing the name and writing it CANNOT be two steps: whatever separates them
|
|
168
|
+
* is a window in which another writer takes the name that was just declared
|
|
169
|
+
* free, and the loser's dump disappears with no error raised anywhere. So each
|
|
170
|
+
* candidate is written create-exclusively ({@link EXCLUSIVE_CREATE}) and an
|
|
171
|
+
* `EEXIST` — the OS saying somebody else got there — advances to the next
|
|
172
|
+
* counter rather than overwriting.
|
|
173
|
+
*
|
|
174
|
+
* @param directory - Directory dumps are written to
|
|
175
|
+
* @param basename - Basename stem; the pid and any collision counter follow
|
|
176
|
+
* @param contents - The already-serialized dump body
|
|
177
|
+
* @returns Which name was claimed, or why none could be
|
|
178
|
+
*/
|
|
179
|
+
function claimTimingDump(directory, basename, contents) {
|
|
180
|
+
const stem = `${basename}-${String(process.pid)}`;
|
|
181
|
+
for (let collision = 0; collision <= MAX_DUMP_COLLISIONS; collision += 1) {
|
|
182
|
+
const candidate = timingDumpCandidate(directory, stem, collision);
|
|
183
|
+
try {
|
|
184
|
+
writeFileSync(candidate, contents, { encoding: 'utf-8', flag: EXCLUSIVE_CREATE });
|
|
185
|
+
return { outcome: 'written', path: candidate };
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
if (!isNameAlreadyTaken(error))
|
|
189
|
+
return { outcome: 'failed', path: candidate, error };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
outcome: 'exhausted',
|
|
194
|
+
path: timingDumpCandidate(directory, stem, MAX_DUMP_COLLISIONS),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Write one seam's dump, if the seam is on.
|
|
199
|
+
*
|
|
200
|
+
* The body is built lazily, inside this call, so a disabled seam never pays to
|
|
201
|
+
* snapshot accumulators nobody will read — and a `build` that throws is reported
|
|
202
|
+
* like any other failure, because this runs from an `exit` listener where a
|
|
203
|
+
* throw would change the process's exit behaviour.
|
|
204
|
+
*
|
|
205
|
+
* @param noun - What the seam is called, for any failure line
|
|
206
|
+
* @param directory - Where to write, or `null` when the seam is off
|
|
207
|
+
* @param basename - Basename stem for the file
|
|
208
|
+
* @param build - Produces the dump body
|
|
209
|
+
* @returns The path written, or `null` when the seam is off or nothing was written
|
|
210
|
+
*/
|
|
211
|
+
export function writeTimingDump(noun, directory, basename, build) {
|
|
212
|
+
if (directory === null)
|
|
213
|
+
return null;
|
|
214
|
+
let claim;
|
|
215
|
+
try {
|
|
216
|
+
claim = claimTimingDump(directory, basename, `${JSON.stringify(build(), null, 2)}\n`);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
// Only `build()` and its serialization can reach here; every filesystem
|
|
220
|
+
// failure is already an outcome rather than a throw.
|
|
221
|
+
reportTimingDumpFailure(noun, directory, error);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
if (claim.outcome === 'written')
|
|
225
|
+
return claim.path;
|
|
226
|
+
reportTimingDumpFailure(noun, claim.path, claim.outcome === 'exhausted' ? CEILING_DETAIL : claim.error);
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=timing-dump.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing-dump.js","sourceRoot":"","sources":["../src/timing-dump.ts"],"names":[],"mappings":"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"}
|
package/dist/windows-shell.d.ts
CHANGED
|
@@ -68,7 +68,6 @@ export declare function isPathLike(command: string): boolean;
|
|
|
68
68
|
*
|
|
69
69
|
* - Node.js deprecation: https://nodejs.org/api/deprecations.html#DEP0190
|
|
70
70
|
* - Security tests: `packages/utils/test/safe-exec.test.ts`
|
|
71
|
-
* - Windows fix: PR #94 (fix/windows-shell-independence-v2)
|
|
72
71
|
*
|
|
73
72
|
* @param commandPath - Resolved absolute path to command
|
|
74
73
|
* @returns true if shell should be used, false otherwise
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"windows-shell.d.ts","sourceRoot":"","sources":["../src/windows-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED
|
|
1
|
+
{"version":3,"file":"windows-shell.d.ts","sourceRoot":"","sources":["../src/windows-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAiB3D;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA2BrD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEtF;AAsCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAWlF"}
|
package/dist/windows-shell.js
CHANGED
|
@@ -71,7 +71,6 @@ export function isPathLike(command) {
|
|
|
71
71
|
*
|
|
72
72
|
* - Node.js deprecation: https://nodejs.org/api/deprecations.html#DEP0190
|
|
73
73
|
* - Security tests: `packages/utils/test/safe-exec.test.ts`
|
|
74
|
-
* - Windows fix: PR #94 (fix/windows-shell-independence-v2)
|
|
75
74
|
*
|
|
76
75
|
* @param commandPath - Resolved absolute path to command
|
|
77
76
|
* @returns true if shell should be used, false otherwise
|