@linxiraos/pi-utils 1.1.7 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -8
- package/README.md +18 -18
- package/package.json +57 -63
- package/src/acp/connection.ts +6 -10
- package/src/ar/entries.ts +1 -1
- package/src/ar/sevenzip.ts +17 -2
- package/src/async.ts +25 -4
- package/src/browsers.ts +11 -5
- package/src/dirs.ts +15 -10
- package/src/dom/core.ts +4 -4
- package/src/env.ts +12 -0
- package/src/fetch-retry.ts +36 -1
- package/src/format.ts +1 -1
- package/src/frontmatter.ts +1 -0
- package/src/incoming-json.ts +856 -0
- package/src/index.ts +2 -0
- package/src/json-lexer.ts +413 -0
- package/src/json-parse.ts +93 -350
- package/src/marked/core.ts +8 -1
- package/src/materialize-string.ts +10 -0
- package/src/path.ts +14 -0
- package/src/postmortem.ts +33 -6
- package/src/prompt.ts +1 -0
- package/src/ring.ts +1 -0
- package/src/temp.ts +16 -4
- package/src/template.ts +2 -2
- package/src/tls-fetch.ts +2 -16
- package/src/turndown/html.ts +1 -1
- package/src/vterm/query-responder.ts +76 -0
- package/src/vterm.ts +1 -0
- package/THIRD-PARTY-NOTICES.txt +0 -22909
- package/dist/types/abortable.d.ts +0 -32
- package/dist/types/acp/connection.d.ts +0 -118
- package/dist/types/acp/protocol.d.ts +0 -526
- package/dist/types/acp/schema.d.ts +0 -41
- package/dist/types/acp/stream.d.ts +0 -8
- package/dist/types/acp/transport.d.ts +0 -88
- package/dist/types/acp.d.ts +0 -6
- package/dist/types/ar/arj.d.ts +0 -5
- package/dist/types/ar/asar.d.ts +0 -7
- package/dist/types/ar/bytes.d.ts +0 -20
- package/dist/types/ar/cab.d.ts +0 -5
- package/dist/types/ar/checksums.d.ts +0 -10
- package/dist/types/ar/codecs/bzip2.d.ts +0 -4
- package/dist/types/ar/codecs/gzip.d.ts +0 -6
- package/dist/types/ar/codecs/lzma.d.ts +0 -6
- package/dist/types/ar/codecs/lzw.d.ts +0 -4
- package/dist/types/ar/codecs/lzx.d.ts +0 -7
- package/dist/types/ar/codecs/xz.d.ts +0 -4
- package/dist/types/ar/codecs/zstd.d.ts +0 -6
- package/dist/types/ar/cpio.d.ts +0 -7
- package/dist/types/ar/deb.d.ts +0 -5
- package/dist/types/ar/entries.d.ts +0 -22
- package/dist/types/ar/error.d.ts +0 -8
- package/dist/types/ar/index.d.ts +0 -11
- package/dist/types/ar/iso.d.ts +0 -5
- package/dist/types/ar/limits.d.ts +0 -33
- package/dist/types/ar/lzh.d.ts +0 -7
- package/dist/types/ar/open.d.ts +0 -45
- package/dist/types/ar/paths.d.ts +0 -18
- package/dist/types/ar/rar/rar4-decoder.d.ts +0 -6
- package/dist/types/ar/rar/rar5-decoder.d.ts +0 -6
- package/dist/types/ar/rar.d.ts +0 -5
- package/dist/types/ar/reader.d.ts +0 -28
- package/dist/types/ar/registry.d.ts +0 -17
- package/dist/types/ar/rpm.d.ts +0 -5
- package/dist/types/ar/sevenzip/decode.d.ts +0 -33
- package/dist/types/ar/sevenzip.d.ts +0 -5
- package/dist/types/ar/source.d.ts +0 -54
- package/dist/types/ar/tar.d.ts +0 -9
- package/dist/types/ar/types.d.ts +0 -99
- package/dist/types/ar/unix-ar.d.ts +0 -7
- package/dist/types/ar/write.d.ts +0 -10
- package/dist/types/ar/zip.d.ts +0 -10
- package/dist/types/async.d.ts +0 -22
- package/dist/types/binary.d.ts +0 -23
- package/dist/types/browsers.d.ts +0 -68
- package/dist/types/chalk.d.ts +0 -125
- package/dist/types/cli.d.ts +0 -134
- package/dist/types/color.d.ts +0 -136
- package/dist/types/dates.d.ts +0 -7
- package/dist/types/dirs.d.ts +0 -325
- package/dist/types/docx/converter.d.ts +0 -46
- package/dist/types/docx/xml.d.ts +0 -26
- package/dist/types/docx.d.ts +0 -11
- package/dist/types/dom/core.d.ts +0 -431
- package/dist/types/dom/parser.d.ts +0 -7
- package/dist/types/dom/selector.d.ts +0 -5
- package/dist/types/dom.d.ts +0 -5
- package/dist/types/env.d.ts +0 -124
- package/dist/types/fetch-retry.d.ts +0 -96
- package/dist/types/file-lock.d.ts +0 -23
- package/dist/types/format.d.ts +0 -37
- package/dist/types/frontmatter.d.ts +0 -46
- package/dist/types/fs-error.d.ts +0 -31
- package/dist/types/glob.d.ts +0 -28
- package/dist/types/headers.d.ts +0 -34
- package/dist/types/index.d.ts +0 -41
- package/dist/types/json-parse.d.ts +0 -83
- package/dist/types/json.d.ts +0 -20
- package/dist/types/logger/rotating-file.d.ts +0 -18
- package/dist/types/logger.d.ts +0 -96
- package/dist/types/loop-phase.d.ts +0 -10
- package/dist/types/lru.d.ts +0 -46
- package/dist/types/marked/core.d.ts +0 -445
- package/dist/types/marked.d.ts +0 -2
- package/dist/types/math-delimiters.d.ts +0 -45
- package/dist/types/mermaid-ascii.d.ts +0 -11
- package/dist/types/mime.d.ts +0 -29
- package/dist/types/module-timer.d.ts +0 -1
- package/dist/types/path-tree.d.ts +0 -76
- package/dist/types/path.d.ts +0 -2
- package/dist/types/peek-file.d.ts +0 -29
- package/dist/types/postmortem.d.ts +0 -174
- package/dist/types/process-name.d.ts +0 -7
- package/dist/types/procmgr.d.ts +0 -74
- package/dist/types/prompt.d.ts +0 -18
- package/dist/types/ptree.d.ts +0 -124
- package/dist/types/readability/readability.d.ts +0 -9
- package/dist/types/readability/readerable.d.ts +0 -10
- package/dist/types/readability/types.d.ts +0 -70
- package/dist/types/readability.d.ts +0 -4
- package/dist/types/ring.d.ts +0 -93
- package/dist/types/runtime-install.d.ts +0 -85
- package/dist/types/sanitize-text.d.ts +0 -29
- package/dist/types/snowflake.d.ts +0 -25
- package/dist/types/sqlite.d.ts +0 -26
- package/dist/types/stderr-guard.d.ts +0 -22
- package/dist/types/stream.d.ts +0 -75
- package/dist/types/tab-spacing.d.ts +0 -24
- package/dist/types/temp.d.ts +0 -17
- package/dist/types/template.d.ts +0 -62
- package/dist/types/timing-buffer.d.ts +0 -22
- package/dist/types/tls-fetch.d.ts +0 -37
- package/dist/types/turndown/gfm.d.ts +0 -11
- package/dist/types/turndown/html.d.ts +0 -5
- package/dist/types/turndown/service.d.ts +0 -21
- package/dist/types/turndown/types.d.ts +0 -70
- package/dist/types/turndown.d.ts +0 -4
- package/dist/types/type-guards.d.ts +0 -5
- package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +0 -41
- package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +0 -89
- package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +0 -12
- package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +0 -66
- package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +0 -48
- package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +0 -43
- package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +0 -56
- package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +0 -65
- package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +0 -27
- package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +0 -17
- package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +0 -34
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +0 -26
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +0 -31
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +0 -11
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +0 -59
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +0 -17
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +0 -30
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +0 -55
- package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +0 -206
- package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +0 -51
- package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +0 -2
- package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/class/types.d.ts +0 -102
- package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/er/types.d.ts +0 -76
- package/dist/types/vendor/mermaid-ascii/index.d.ts +0 -1
- package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +0 -9
- package/dist/types/vendor/mermaid-ascii/parser.d.ts +0 -7
- package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +0 -130
- package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +0 -23
- package/dist/types/vendor/mermaid-ascii/types.d.ts +0 -114
- package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +0 -25
- package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +0 -6
- package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +0 -145
- package/dist/types/version.d.ts +0 -18
- package/dist/types/vterm/buffer.d.ts +0 -99
- package/dist/types/vterm/terminal.d.ts +0 -44
- package/dist/types/vterm.d.ts +0 -8
- package/dist/types/which.d.ts +0 -37
- package/dist/types/worker-host.d.ts +0 -51
- package/dist/types/xml.d.ts +0 -31
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [1.1.
|
|
5
|
+
## [1.1.9] - 2026-09-05
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- USER_AGENT constant restored to zeta/<version> (the v18.1.10 merge re-introduced the upstream omp/ UA on every provider request); brand wording in path-resolver docs corrected; profile validation error message Zeta'd.
|
|
8
8
|
|
|
9
9
|
## [1.1.6] - 2026-08-30
|
|
10
10
|
|
|
@@ -15,12 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
- 同步上游 OMP v18.0.5 / v18.0.6:新增 browsers / json 工具,SHA-2/SHA-3 在 ARM64 上加速。
|
|
17
17
|
|
|
18
|
-
## [1.1.3] - 2026-08-25
|
|
19
|
-
|
|
20
|
-
### Fixed
|
|
21
|
-
|
|
22
|
-
- Republished as 1.1.3 to reset the latest tag after the broken 1.1.2 (no functional change over 1.1.1).
|
|
23
|
-
|
|
24
18
|
## [1.1.2] - 2026-08-25
|
|
25
19
|
|
|
26
20
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
# @linxiraos/pi-utils
|
|
2
2
|
|
|
3
|
-
Shared utilities for [
|
|
3
|
+
Shared utilities for [oh-my-pi](https://github.com/can1357/oh-my-pi) packages. Zero ceremony, Bun-first.
|
|
4
4
|
|
|
5
5
|
## Notable modules
|
|
6
6
|
|
|
7
|
-
| Module
|
|
8
|
-
|
|
|
9
|
-
| `logger`
|
|
10
|
-
| `prompt`
|
|
11
|
-
| `dirs`
|
|
12
|
-
| `stream`
|
|
13
|
-
| `ptree` / `procmgr`
|
|
14
|
-
| `postmortem`
|
|
15
|
-
| `which`
|
|
16
|
-
| `fetch-retry`
|
|
17
|
-
| `fs-error`
|
|
18
|
-
| `env` / `worker-host`
|
|
19
|
-
| `abortable` / `async`
|
|
20
|
-
| `math-delimiters`
|
|
21
|
-
| `peek-file`
|
|
22
|
-
| `frontmatter`, `glob`, `mime`, `temp`, `format`, `color`, `snowflake`, `tab-spacing`, `path-tree`, `sanitize-text` | Smaller single-purpose helpers
|
|
7
|
+
| Module | Purpose |
|
|
8
|
+
| ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `logger` | Centralized logger writing to `~/.zeta/logs/` with rotation (TUI-safe — never stdout) |
|
|
10
|
+
| `prompt` | Handlebars-based prompt templating and formatting helpers |
|
|
11
|
+
| `dirs` | Path helpers for omp config directories (`~/.zeta`, XDG-aware on Linux) |
|
|
12
|
+
| `stream` | `readStream` / `readLines` helpers over `ReadableStream` |
|
|
13
|
+
| `ptree` / `procmgr` | Process trees, `ChildProcess` wrapper, process lifecycle management |
|
|
14
|
+
| `postmortem` | Cleanup callbacks on exit, signals, and fatal exceptions |
|
|
15
|
+
| `which` | `$which()` binary lookup with caching |
|
|
16
|
+
| `fetch-retry` | `fetch` with retry/backoff policies |
|
|
17
|
+
| `fs-error` | Errno guards (`isEnoent` and friends) |
|
|
18
|
+
| `env` / `worker-host` | Environment plumbing and side-effect-free worker-host entry contract (`workerHostEntry`) |
|
|
19
|
+
| `abortable` / `async` | AbortSignal-aware stream/promise helpers |
|
|
20
|
+
| `math-delimiters` | LaTeX span/block delimiter grammar (offsets only) shared by the TUI and collab-web renderers |
|
|
21
|
+
| `peek-file` | Read the first N bytes of a file with pooled buffers |
|
|
22
|
+
| `frontmatter`, `glob`, `mime`, `temp`, `format`, `color`, `snowflake`, `tab-spacing`, `path-tree`, `sanitize-text` | Smaller single-purpose helpers |
|
|
23
23
|
|
|
24
24
|
Import from the root barrel or per-module subpaths (`@linxiraos/pi-utils/<module>`).
|
|
25
25
|
|
|
@@ -33,5 +33,5 @@ Ships TypeScript source directly (no build step); requires Bun ≥ 1.3.14.
|
|
|
33
33
|
|
|
34
34
|
## References
|
|
35
35
|
|
|
36
|
-
- [Monorepo README](https://github.com/
|
|
36
|
+
- [Monorepo README](https://github.com/can1357/oh-my-pi#readme)
|
|
37
37
|
- [CHANGELOG](./CHANGELOG.md)
|
package/package.json
CHANGED
|
@@ -1,65 +1,59 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"./*": {
|
|
60
|
-
"types": "./dist/types/*.d.ts",
|
|
61
|
-
"import": "./src/*.ts"
|
|
62
|
-
},
|
|
63
|
-
"./*.js": "./src/*.ts"
|
|
64
|
-
}
|
|
2
|
+
"engines": {
|
|
3
|
+
"bun": ">=1.3.14"
|
|
4
|
+
},
|
|
5
|
+
"devDependencies": {
|
|
6
|
+
"@types/bun": "^1.3.14"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@linxiraos/pi-natives": "1.1.9"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "bun test --parallel",
|
|
13
|
+
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
14
|
+
"check": "tsgo -p tsconfig.json --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
"./*.js": "./src/*.ts",
|
|
18
|
+
"./*": {
|
|
19
|
+
"import": "./src/*.ts",
|
|
20
|
+
"types": "./src/*.ts"
|
|
21
|
+
},
|
|
22
|
+
"./ar": {
|
|
23
|
+
"import": "./src/ar/index.ts",
|
|
24
|
+
"types": "./src/ar/index.ts"
|
|
25
|
+
},
|
|
26
|
+
".": {
|
|
27
|
+
"import": "./src/index.ts",
|
|
28
|
+
"types": "./src/index.ts"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"types": "./src/index.ts",
|
|
32
|
+
"main": "./src/index.ts",
|
|
33
|
+
"type": "module",
|
|
34
|
+
"files": [
|
|
35
|
+
"src",
|
|
36
|
+
"README.md",
|
|
37
|
+
"CHANGELOG.md"
|
|
38
|
+
],
|
|
39
|
+
"repository": {
|
|
40
|
+
"directory": "packages/utils",
|
|
41
|
+
"url": "git+https://github.com/Linxira-OS/linxira-zeta.git",
|
|
42
|
+
"type": "git"
|
|
43
|
+
},
|
|
44
|
+
"author": "Can Boluk",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/Linxira-OS/linxira-zeta/issues"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://linxira-os.github.io/zeta/",
|
|
50
|
+
"keywords": [
|
|
51
|
+
"utilities",
|
|
52
|
+
"cli",
|
|
53
|
+
"logging",
|
|
54
|
+
"streams"
|
|
55
|
+
],
|
|
56
|
+
"description": "Shared utilities for pi packages",
|
|
57
|
+
"version": "1.1.9",
|
|
58
|
+
"name": "@linxiraos/pi-utils"
|
|
65
59
|
}
|
package/src/acp/connection.ts
CHANGED
|
@@ -67,13 +67,11 @@ export class AgentSideConnection {
|
|
|
67
67
|
#connection: RpcConnection;
|
|
68
68
|
|
|
69
69
|
constructor(toAgent: (connection: AgentSideConnection) => Agent, stream: Stream) {
|
|
70
|
-
let agent: Agent | undefined;
|
|
71
70
|
this.#connection = new RpcConnection(stream, async (method, params, notification) => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return dispatchAgent(target, method, params, notification);
|
|
71
|
+
if (!agent) throw RequestError.internalError(undefined, "Agent is not initialized");
|
|
72
|
+
return dispatchAgent(agent, method, params, notification);
|
|
75
73
|
});
|
|
76
|
-
agent = toAgent(this);
|
|
74
|
+
const agent = toAgent(this);
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
/** Signal aborted when the transport closes. */
|
|
@@ -170,13 +168,11 @@ export class ClientSideConnection {
|
|
|
170
168
|
#connection: RpcConnection;
|
|
171
169
|
|
|
172
170
|
constructor(toClient: (connection: ClientSideConnection) => Client, stream: Stream) {
|
|
173
|
-
let client: Client | undefined;
|
|
174
171
|
this.#connection = new RpcConnection(stream, async (method, params, notification) => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return dispatchClient(target, method, params, notification);
|
|
172
|
+
if (!client) throw RequestError.internalError(undefined, "Client is not initialized");
|
|
173
|
+
return dispatchClient(client, method, params, notification);
|
|
178
174
|
});
|
|
179
|
-
client = toClient(this);
|
|
175
|
+
const client = toClient(this);
|
|
180
176
|
}
|
|
181
177
|
/** Signal aborted when the transport closes. */
|
|
182
178
|
get signal(): AbortSignal {
|
package/src/ar/entries.ts
CHANGED
|
@@ -45,7 +45,7 @@ export function upsertArchiveEntry(
|
|
|
45
45
|
*/
|
|
46
46
|
export function ensureParentDirectories(map: Map<string, ArchiveIndexEntry>, limits: ArchiveLimits): void {
|
|
47
47
|
assertEntryCount(map.size, limits);
|
|
48
|
-
for (const entry of
|
|
48
|
+
for (const entry of Array.from(map.values())) {
|
|
49
49
|
const parts = entry.path.split("/");
|
|
50
50
|
const stop = parts.length - 1;
|
|
51
51
|
for (let index = 1; index <= stop; index++) {
|
package/src/ar/sevenzip.ts
CHANGED
|
@@ -107,6 +107,7 @@ interface FileMetadata {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
function readBoolVector(reader: HeaderReader, count: number): boolean[] {
|
|
110
|
+
// [suppressed] length preallocation
|
|
110
111
|
const result = new Array<boolean>(count);
|
|
111
112
|
let byte = 0;
|
|
112
113
|
let mask = 0;
|
|
@@ -122,12 +123,16 @@ function readBoolVector(reader: HeaderReader, count: number): boolean[] {
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
function readDefinedVector(reader: HeaderReader, count: number): boolean[] {
|
|
125
|
-
if (reader.readByte() !== 0)
|
|
126
|
+
if (reader.readByte() !== 0) {
|
|
127
|
+
// [suppressed] length preallocation
|
|
128
|
+
return new Array<boolean>(count).fill(true);
|
|
129
|
+
}
|
|
126
130
|
return readBoolVector(reader, count);
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
function readDigests(reader: HeaderReader, count: number): Digests {
|
|
130
134
|
const defined = readDefinedVector(reader, count);
|
|
135
|
+
// [suppressed] length preallocation
|
|
131
136
|
const values = new Array<number | undefined>(count);
|
|
132
137
|
for (let index = 0; index < count; index++) if (defined[index]) values[index] = reader.readUInt32();
|
|
133
138
|
return { defined, values };
|
|
@@ -229,7 +234,10 @@ function parsePackInfo(reader: HeaderReader, streams: SevenZipStreams, options:
|
|
|
229
234
|
} else reader.skipSizedProperty();
|
|
230
235
|
}
|
|
231
236
|
if (streams.packSizes.length !== count) throw new ArchiveError("Invalid 7z PackInfo without complete sizes");
|
|
232
|
-
if (streams.packCrcs.length === 0)
|
|
237
|
+
if (streams.packCrcs.length === 0) {
|
|
238
|
+
// [suppressed] length preallocation
|
|
239
|
+
streams.packCrcs = new Array(count).fill(undefined);
|
|
240
|
+
}
|
|
233
241
|
}
|
|
234
242
|
|
|
235
243
|
function parseUnpackInfo(reader: HeaderReader, streams: SevenZipStreams, options: FormatReadOptions): void {
|
|
@@ -254,6 +262,7 @@ function parseUnpackInfo(reader: HeaderReader, streams: SevenZipStreams, options
|
|
|
254
262
|
}
|
|
255
263
|
|
|
256
264
|
function parseSubStreamsInfo(reader: HeaderReader, streams: SevenZipStreams): void {
|
|
265
|
+
// [suppressed] length preallocation
|
|
257
266
|
const counts = new Array<number>(streams.folders.length).fill(1);
|
|
258
267
|
let sizes: number[] | undefined;
|
|
259
268
|
let rawDigests: Digests | undefined;
|
|
@@ -398,6 +407,7 @@ function parseNames(reader: HeaderReader, count: number, external: Uint8Array[],
|
|
|
398
407
|
function parseTimes(reader: HeaderReader, count: number, external: Uint8Array[]): Array<number | undefined> {
|
|
399
408
|
const defined = readDefinedVector(reader, count);
|
|
400
409
|
const values = selectPropertyStream(reader, external);
|
|
410
|
+
// [suppressed] length preallocation
|
|
401
411
|
const result = new Array<number | undefined>(count);
|
|
402
412
|
for (let index = 0; index < count; index++)
|
|
403
413
|
if (defined[index]) {
|
|
@@ -411,6 +421,7 @@ function parseTimes(reader: HeaderReader, count: number, external: Uint8Array[])
|
|
|
411
421
|
function parseAttributes(reader: HeaderReader, count: number, external: Uint8Array[]): Array<number | undefined> {
|
|
412
422
|
const defined = readDefinedVector(reader, count);
|
|
413
423
|
const values = selectPropertyStream(reader, external);
|
|
424
|
+
// [suppressed] length preallocation
|
|
414
425
|
const result = new Array<number | undefined>(count);
|
|
415
426
|
for (let index = 0; index < count; index++) if (defined[index]) result[index] = values.readUInt32();
|
|
416
427
|
return result;
|
|
@@ -420,11 +431,15 @@ function parseFilesInfo(reader: HeaderReader, external: Uint8Array[], options: F
|
|
|
420
431
|
const count = reader.readNumber("file count");
|
|
421
432
|
assertEntryCount(count, options.limits);
|
|
422
433
|
const metadata: FileMetadata = {
|
|
434
|
+
// [suppressed] length preallocation
|
|
423
435
|
names: new Array<string>(count).fill(""),
|
|
436
|
+
// [suppressed] length preallocation
|
|
424
437
|
emptyStreams: new Array<boolean>(count).fill(false),
|
|
425
438
|
emptyFiles: [],
|
|
426
439
|
antiFiles: [],
|
|
440
|
+
// [suppressed] length preallocation
|
|
427
441
|
mtimes: new Array<number | undefined>(count),
|
|
442
|
+
// [suppressed] length preallocation
|
|
428
443
|
attributes: new Array<number | undefined>(count),
|
|
429
444
|
};
|
|
430
445
|
for (;;) {
|
package/src/async.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Wrap a promise with a timeout and optional abort signal.
|
|
3
|
-
* Rejects with the given
|
|
4
|
-
* Cleans up all listeners on settlement.
|
|
3
|
+
* Rejects with the given error or a new error containing the given message if
|
|
4
|
+
* the timeout fires first. Cleans up all listeners on settlement.
|
|
5
5
|
*/
|
|
6
|
-
export function withTimeout<T>(
|
|
6
|
+
export function withTimeout<T>(
|
|
7
|
+
promise: Promise<T>,
|
|
8
|
+
ms: number,
|
|
9
|
+
timeout: string | Error,
|
|
10
|
+
signal?: AbortSignal,
|
|
11
|
+
): Promise<T> {
|
|
7
12
|
if (signal?.aborted) {
|
|
8
13
|
const reason = signal.reason instanceof Error ? signal.reason : new Error("Aborted");
|
|
9
14
|
return Promise.reject(reason);
|
|
@@ -15,7 +20,7 @@ export function withTimeout<T>(promise: Promise<T>, ms: number, message: string,
|
|
|
15
20
|
if (settled) return;
|
|
16
21
|
settled = true;
|
|
17
22
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
18
|
-
reject(new Error(
|
|
23
|
+
reject(typeof timeout === "string" ? new Error(timeout) : timeout);
|
|
19
24
|
}, ms);
|
|
20
25
|
|
|
21
26
|
const onAbort = () => {
|
|
@@ -103,3 +108,19 @@ export class AsyncDrain<T> {
|
|
|
103
108
|
return this.#promise;
|
|
104
109
|
}
|
|
105
110
|
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Runs async operations one at a time in call order. Each `run` starts after
|
|
114
|
+
* the previous operation settles (success or failure) and returns that
|
|
115
|
+
* operation's own promise, so a rejected step never poisons the queue. Used by
|
|
116
|
+
* stateful cursors whose concurrent pulls must not interleave.
|
|
117
|
+
*/
|
|
118
|
+
export class Serial {
|
|
119
|
+
#tail: Promise<unknown> = Promise.resolve();
|
|
120
|
+
|
|
121
|
+
run<T>(op: () => Promise<T>): Promise<T> {
|
|
122
|
+
const result = this.#tail.then(op, op);
|
|
123
|
+
this.#tail = result.catch(() => {});
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
}
|
package/src/browsers.ts
CHANGED
|
@@ -39,6 +39,13 @@ export enum BrowserPlatform {
|
|
|
39
39
|
WIN32 = "win32",
|
|
40
40
|
WIN64 = "win64",
|
|
41
41
|
}
|
|
42
|
+
type ChromeForTestingPlatform = Exclude<BrowserPlatform, BrowserPlatform.LINUX_ARM>;
|
|
43
|
+
|
|
44
|
+
function requireChromeForTestingPlatform(platform: BrowserPlatform): ChromeForTestingPlatform {
|
|
45
|
+
if (platform === BrowserPlatform.LINUX_ARM)
|
|
46
|
+
throw new Error("Chrome for Testing does not provide linux/arm64 builds");
|
|
47
|
+
return platform;
|
|
48
|
+
}
|
|
42
49
|
|
|
43
50
|
const BROWSERS = [
|
|
44
51
|
Browser.CHROME,
|
|
@@ -163,13 +170,13 @@ export function getDownloadUrl(
|
|
|
163
170
|
|
|
164
171
|
/** Compute the executable path in Puppeteer's cache layout. */
|
|
165
172
|
export function computeExecutablePath(options: ComputeExecutablePathOptions): string {
|
|
166
|
-
const
|
|
167
|
-
if (!
|
|
173
|
+
const detectedPlatform = options.platform ?? detectBrowserPlatform();
|
|
174
|
+
if (!detectedPlatform) throw new Error("Cannot determine a browser platform for this host");
|
|
168
175
|
if (options.browser !== Browser.CHROME) throw new Error(`Unsupported browser executable: ${options.browser}`);
|
|
176
|
+
const platform = requireChromeForTestingPlatform(detectedPlatform);
|
|
169
177
|
const installDir = installationDir(options.cacheDir, options.browser, platform, options.buildId);
|
|
170
178
|
switch (platform) {
|
|
171
179
|
case BrowserPlatform.LINUX:
|
|
172
|
-
case BrowserPlatform.LINUX_ARM:
|
|
173
180
|
return path.join(installDir, "chrome-linux64", "chrome");
|
|
174
181
|
case BrowserPlatform.MAC:
|
|
175
182
|
return path.join(
|
|
@@ -292,9 +299,8 @@ async function fetchMetadata<T>(filename: string): Promise<T> {
|
|
|
292
299
|
}
|
|
293
300
|
|
|
294
301
|
function chromeArchivePlatform(platform: BrowserPlatform): string {
|
|
295
|
-
switch (platform) {
|
|
302
|
+
switch (requireChromeForTestingPlatform(platform)) {
|
|
296
303
|
case BrowserPlatform.LINUX:
|
|
297
|
-
case BrowserPlatform.LINUX_ARM:
|
|
298
304
|
return "linux64";
|
|
299
305
|
case BrowserPlatform.MAC:
|
|
300
306
|
return "mac-x64";
|
package/src/dirs.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* variables are set, paths are redirected to XDG-compliant locations under
|
|
9
9
|
* $XDG_*_HOME/zeta/. This requires running `zeta config migrate` first to
|
|
10
10
|
* move data to the new locations. No filesystem existence checks are performed
|
|
11
|
-
* — if the env var is set,
|
|
11
|
+
* — if the env var is set, zeta trusts that the migration has been done.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import * as fs from "node:fs";
|
|
@@ -17,7 +17,7 @@ import * as path from "node:path";
|
|
|
17
17
|
import { engines, version } from "../package.json" with { type: "json" };
|
|
18
18
|
import { isEnoent, isEnotdir } from "./fs-error";
|
|
19
19
|
|
|
20
|
-
/** App name (e.g. "
|
|
20
|
+
/** App name (e.g. "zeta") */
|
|
21
21
|
export const APP_NAME: string = "zeta";
|
|
22
22
|
|
|
23
23
|
/** Config directory name (e.g. ".zeta") */
|
|
@@ -67,7 +67,7 @@ export function normalizeProfileName(profile: string | undefined): string | unde
|
|
|
67
67
|
WINDOWS_RESERVED_BASENAME_RE.test(normalized)
|
|
68
68
|
) {
|
|
69
69
|
throw new Error(
|
|
70
|
-
`Invalid
|
|
70
|
+
`Invalid Zeta profile "${profile}". Profile names must match ${PROFILE_NAME_RE.source}, ` +
|
|
71
71
|
`cannot be "." or "..", cannot end with ".", and cannot be a Windows reserved device name ` +
|
|
72
72
|
`(CON, PRN, AUX, NUL, COM0-9, LPT0-9, or any of those with an extension).`,
|
|
73
73
|
);
|
|
@@ -97,7 +97,7 @@ function getProfileFromEnv(): string | undefined {
|
|
|
97
97
|
* crash a bare `import` of this module with an uncaught stack trace before the
|
|
98
98
|
* CLI's error handling is in scope. The default profile is used instead; the
|
|
99
99
|
* CLI re-validates the env (see `runCli` in coding-agent/src/cli.ts) so the
|
|
100
|
-
* user still gets a clean "Invalid
|
|
100
|
+
* user still gets a clean "Invalid Zeta profile" message.
|
|
101
101
|
*/
|
|
102
102
|
function readProfileFromEnvSafe(): string | undefined {
|
|
103
103
|
try {
|
|
@@ -295,7 +295,7 @@ export function getConfigAgentDirName(): string {
|
|
|
295
295
|
type XdgCategory = "data" | "state" | "cache";
|
|
296
296
|
|
|
297
297
|
/**
|
|
298
|
-
* Resolves and caches all
|
|
298
|
+
* Resolves and caches all zeta directory paths. On Linux, when XDG environment
|
|
299
299
|
* variables are set, paths are redirected under $XDG_*_HOME/zeta/. A new
|
|
300
300
|
* instance is created whenever the agent directory changes, which naturally
|
|
301
301
|
* invalidates all cached paths.
|
|
@@ -949,6 +949,11 @@ export function getSecretPlaceholderKeyPath(): string {
|
|
|
949
949
|
return keyPath;
|
|
950
950
|
}
|
|
951
951
|
|
|
952
|
+
/** Directory holding the per-model tiny-worker sockets and logs (~/.zeta/run/tiny; XDG default: $XDG_STATE_HOME/zeta/run/tiny). */
|
|
953
|
+
export function getTinyWorkerRuntimeDir(): string {
|
|
954
|
+
return dirs.rootSubdir(path.join("run", "tiny"), "state");
|
|
955
|
+
}
|
|
956
|
+
|
|
952
957
|
/** Root directory containing every per-project daemon runtime scope (~/.zeta/run/daemons; XDG default: $XDG_STATE_HOME/zeta/run/daemons). */
|
|
953
958
|
export function getDaemonRuntimeRoot(): string {
|
|
954
959
|
return dirs.rootSubdir(path.join("run", "daemons"), "state");
|
|
@@ -1108,11 +1113,6 @@ export function getInstallId(): string {
|
|
|
1108
1113
|
return next;
|
|
1109
1114
|
}
|
|
1110
1115
|
|
|
1111
|
-
/** Test-only: clear cached install id. Never call from production code. */
|
|
1112
|
-
export function __resetInstallIdCacheForTests(): void {
|
|
1113
|
-
cachedInstallId = null;
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
1116
|
/** Get the project-level tracking directory (<project>/.zeta/tracking). */
|
|
1117
1117
|
export function getProjectTrackingDir(cwd: string = getProjectDir()): string {
|
|
1118
1118
|
return path.join(getProjectAgentDir(cwd), "tracking");
|
|
@@ -1122,3 +1122,8 @@ export function getProjectTrackingDir(cwd: string = getProjectDir()): string {
|
|
|
1122
1122
|
export function getTrackingIndexPath(agentDir?: string): string {
|
|
1123
1123
|
return path.join(agentDir ?? getAgentDir(), "tracking-index.json");
|
|
1124
1124
|
}
|
|
1125
|
+
|
|
1126
|
+
/** Test-only: clear cached install id. Never call from production code. */
|
|
1127
|
+
export function __resetInstallIdCacheForTests(): void {
|
|
1128
|
+
cachedInstallId = null;
|
|
1129
|
+
}
|
package/src/dom/core.ts
CHANGED
|
@@ -105,7 +105,7 @@ export class EventTarget {
|
|
|
105
105
|
dispatchEvent(event: Event): boolean {
|
|
106
106
|
if (!event.target) event.target = this;
|
|
107
107
|
event.currentTarget = this;
|
|
108
|
-
for (const listener of
|
|
108
|
+
for (const listener of Array.from(this.#listeners.get(event.type) ?? [])) {
|
|
109
109
|
if (typeof listener === "function") listener.call(this, event);
|
|
110
110
|
else listener.handleEvent(event);
|
|
111
111
|
if (event.propagationStopped) break;
|
|
@@ -196,7 +196,7 @@ export class Node extends EventTarget {
|
|
|
196
196
|
const node: Node = child;
|
|
197
197
|
if (node === this || node.contains(this)) throw new Error("The new child is an ancestor of this node");
|
|
198
198
|
if (child instanceof DocumentFragment) {
|
|
199
|
-
for (const nested of
|
|
199
|
+
for (const nested of Array.from(child.childNodes)) this.appendChild(nested);
|
|
200
200
|
return child;
|
|
201
201
|
}
|
|
202
202
|
child.parentNode?.removeChild(child);
|
|
@@ -212,7 +212,7 @@ export class Node extends EventTarget {
|
|
|
212
212
|
const index = this.childNodes.indexOf(reference);
|
|
213
213
|
if (index < 0) throw new Error("The reference node is not a child of this node");
|
|
214
214
|
if (child instanceof DocumentFragment) {
|
|
215
|
-
for (const nested of
|
|
215
|
+
for (const nested of Array.from(child.childNodes)) this.insertBefore(nested, reference);
|
|
216
216
|
return child;
|
|
217
217
|
}
|
|
218
218
|
child.parentNode?.removeChild(child);
|
|
@@ -681,7 +681,7 @@ export class Element extends Node {
|
|
|
681
681
|
const parent = this.parentNode;
|
|
682
682
|
if (!parent) return;
|
|
683
683
|
const fragment = parseFragment(value, this.documentForCreation(), this.parentElement?.localName);
|
|
684
|
-
for (const child of
|
|
684
|
+
for (const child of Array.from(fragment.childNodes)) parent.insertBefore(child, this);
|
|
685
685
|
parent.removeChild(this);
|
|
686
686
|
}
|
|
687
687
|
|
package/src/env.ts
CHANGED
|
@@ -36,6 +36,18 @@ export function isMacosMallocStackLoggingEnvName(name: string): boolean {
|
|
|
36
36
|
return name === "MallocStackLogging" || name === "MallocStackLoggingNoCompact";
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* True when running inside a WSL (Windows Subsystem for Linux) distribution.
|
|
41
|
+
*
|
|
42
|
+
* WSL reports `linux` for `process.platform`, so the only reliable signal is
|
|
43
|
+
* the `WSL_DISTRO_NAME`/`WSL_INTEROP` variables the interop layer injects.
|
|
44
|
+
* Callers use this to translate Windows drive paths to their `/mnt/<drive>`
|
|
45
|
+
* mounts and to route clipboard access through `powershell.exe`.
|
|
46
|
+
*/
|
|
47
|
+
export function isWsl(platform: NodeJS.Platform = process.platform, env: NodeJS.ProcessEnv = process.env): boolean {
|
|
48
|
+
return platform === "linux" && Boolean(env.WSL_DISTRO_NAME || env.WSL_INTEROP);
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
export function filterProcessEnv(env: Record<string, string | undefined>): Record<string, string> {
|
|
40
52
|
const result: Record<string, string> = {};
|
|
41
53
|
for (const key in env) {
|
package/src/fetch-retry.ts
CHANGED
|
@@ -12,6 +12,12 @@ const RETRY_DELAY_FIELD_PATTERN = /"retryDelay":\s*"([0-9.]+)(ms|s)"/i;
|
|
|
12
12
|
const TRY_AGAIN_PATTERN = /try again in\s+~?\s*([0-9.]+)\s*(ms|sec|s|minutes?|mins?|m|hours?|hrs?|h)\b/i;
|
|
13
13
|
// "Your limit will reset in 13 minutes" / "reset in 13 minutes" / "will reset in 2h"
|
|
14
14
|
const WILL_RESET_IN_PATTERN = /(?:will\s+)?reset in\s+~?\s*([0-9.]+)\s*(ms|sec|s|minutes?|mins?|m|hours?|hrs?|h)\b/i;
|
|
15
|
+
// "Your limit will reset at 2026-09-01 09:44:51" / "reset at 2026-09-01T09:44:51Z"
|
|
16
|
+
const WILL_RESET_AT_PATTERN =
|
|
17
|
+
/(?:will\s+)?reset at\s+([0-9]{4}-[0-9]{2}-[0-9]{2}[ T][0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?(?:Z|[+-][0-9]{2}:?[0-9]{2})?)/i;
|
|
18
|
+
const CN_RESET_AT_PATTERN = /将在\s*([0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}:[0-9]{2})\s*重置/;
|
|
19
|
+
// "retry-after-ms=98497000"
|
|
20
|
+
const RETRY_AFTER_MS_BODY_PATTERN = /\bretry-after-ms=([0-9]+)\b/i;
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
23
|
* Server-suggested retry delay extraction. Merges the patterns historically used
|
|
@@ -29,6 +35,8 @@ const WILL_RESET_IN_PATTERN = /(?:will\s+)?reset in\s+~?\s*([0-9.]+)\s*(ms|sec|s
|
|
|
29
35
|
* - `Please retry in 250ms` / `Please retry in 12s`
|
|
30
36
|
* - `"retryDelay": "34.074824224s"` (JSON error detail field)
|
|
31
37
|
* - `try again in 250ms` / `try again in 12s` / `try again in 5 min` / `try again in ~158 min`
|
|
38
|
+
* - `retry-after-ms=98497000`
|
|
39
|
+
* - `Your limit will reset at 2026-09-01 09:44:51` / `将在 2026-09-01 09:44:51 重置`
|
|
32
40
|
*
|
|
33
41
|
* Returns `undefined` if no signal is found.
|
|
34
42
|
*/
|
|
@@ -85,11 +93,38 @@ export function extractRetryHint(source: Response | Headers | null | undefined,
|
|
|
85
93
|
if (totalMs > 0) return totalMs;
|
|
86
94
|
}
|
|
87
95
|
}
|
|
96
|
+
for (const pattern of [WILL_RESET_AT_PATTERN, CN_RESET_AT_PATTERN]) {
|
|
97
|
+
const match = pattern.exec(body);
|
|
98
|
+
if (match?.[1]) {
|
|
99
|
+
// Provider timestamps without an explicit offset are interpreted as UTC.
|
|
100
|
+
const normalized = match[1].replace(" ", "T");
|
|
101
|
+
const hasOffset = /(?:Z|[+-][0-9]{2}:?[0-9]{2})$/i.test(normalized);
|
|
102
|
+
const parsed = Date.parse(hasOffset ? normalized : `${normalized}Z`);
|
|
103
|
+
if (!Number.isNaN(parsed) && parsed > Date.now()) {
|
|
104
|
+
return parsed - Date.now();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
88
108
|
// Account-reset hints ("will reset in …") take precedence over short
|
|
89
109
|
// retry hints ("please retry in 5s"): a body carrying both must honour the
|
|
90
110
|
// longer account window, not the shorter generic one. QUOTA_RESET_PATTERN
|
|
91
111
|
// ("reset after …") above already runs first and stays first.
|
|
92
|
-
|
|
112
|
+
const accountResetMatch = WILL_RESET_IN_PATTERN.exec(body);
|
|
113
|
+
if (accountResetMatch?.[1]) {
|
|
114
|
+
const value = Number.parseFloat(accountResetMatch[1]);
|
|
115
|
+
if (Number.isFinite(value) && value > 0) {
|
|
116
|
+
const unitMs = unitToMs(accountResetMatch[2]!);
|
|
117
|
+
if (unitMs !== undefined) return value * unitMs;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const retryAfterMsMatch = RETRY_AFTER_MS_BODY_PATTERN.exec(body);
|
|
122
|
+
if (retryAfterMsMatch?.[1]) {
|
|
123
|
+
const ms = Number(retryAfterMsMatch[1]);
|
|
124
|
+
if (Number.isFinite(ms) && ms > 0) return ms;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const pattern of [PLEASE_RETRY_PATTERN, RETRY_DELAY_FIELD_PATTERN, TRY_AGAIN_PATTERN]) {
|
|
93
128
|
const match = pattern.exec(body);
|
|
94
129
|
if (match?.[1]) {
|
|
95
130
|
const value = Number.parseFloat(match[1]);
|
package/src/format.ts
CHANGED
|
@@ -9,7 +9,7 @@ const DAY = 24 * HOUR;
|
|
|
9
9
|
*/
|
|
10
10
|
export function formatDuration(ms: number): string {
|
|
11
11
|
if (!Number.isFinite(ms) || ms <= 0) return "0ms";
|
|
12
|
-
if (ms < SEC) return `${ms}ms`;
|
|
12
|
+
if (ms < SEC) return `${Math.floor(ms)}ms`;
|
|
13
13
|
if (ms < MIN) return `${(ms / SEC).toFixed(1)}s`;
|
|
14
14
|
if (ms < HOUR) {
|
|
15
15
|
const mins = Math.floor(ms / MIN);
|
package/src/frontmatter.ts
CHANGED
|
@@ -22,6 +22,7 @@ export function normalizeFrontmatterKeys<T>(obj: T): T {
|
|
|
22
22
|
if (obj === null || typeof obj !== "object") return obj;
|
|
23
23
|
if (Array.isArray(obj)) {
|
|
24
24
|
let changed = false;
|
|
25
|
+
// [suppressed] length preallocation
|
|
25
26
|
const out: unknown[] = new Array(obj.length);
|
|
26
27
|
for (let i = 0; i < obj.length; i++) {
|
|
27
28
|
const v = obj[i];
|