@oh-my-pi-zen/pi-utils 16.3.6-zen.1
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 +245 -0
- package/README.md +36 -0
- package/dist/types/abortable.d.ts +32 -0
- package/dist/types/async.d.ts +6 -0
- package/dist/types/binary.d.ts +17 -0
- package/dist/types/cli.d.ts +117 -0
- package/dist/types/color.d.ts +102 -0
- package/dist/types/dirs.d.ts +260 -0
- package/dist/types/env.d.ts +74 -0
- package/dist/types/fetch-retry.d.ts +94 -0
- package/dist/types/format.d.ts +37 -0
- package/dist/types/frontmatter.d.ts +25 -0
- package/dist/types/fs-error.d.ts +31 -0
- package/dist/types/glob.d.ts +28 -0
- package/dist/types/index.d.ts +35 -0
- package/dist/types/json-parse.d.ts +48 -0
- package/dist/types/json.d.ts +4 -0
- package/dist/types/logger.d.ts +83 -0
- package/dist/types/loop-phase.d.ts +10 -0
- package/dist/types/mermaid-ascii.d.ts +11 -0
- package/dist/types/mime.d.ts +29 -0
- package/dist/types/module-timer.d.ts +1 -0
- package/dist/types/path-tree.d.ts +76 -0
- package/dist/types/path.d.ts +2 -0
- package/dist/types/peek-file.d.ts +29 -0
- package/dist/types/postmortem.d.ts +37 -0
- package/dist/types/procmgr.d.ts +29 -0
- package/dist/types/prompt.d.ts +18 -0
- package/dist/types/ptree.d.ts +108 -0
- package/dist/types/ring.d.ts +93 -0
- package/dist/types/runtime-install.d.ts +68 -0
- package/dist/types/sanitize-text.d.ts +29 -0
- package/dist/types/snowflake.d.ts +25 -0
- package/dist/types/stream.d.ts +68 -0
- package/dist/types/tab-spacing.d.ts +24 -0
- package/dist/types/temp.d.ts +17 -0
- package/dist/types/timing-buffer.d.ts +22 -0
- package/dist/types/tls-fetch.d.ts +37 -0
- package/dist/types/type-guards.d.ts +3 -0
- package/dist/types/vendor/mermaid-ascii/ascii/ansi.d.ts +41 -0
- package/dist/types/vendor/mermaid-ascii/ascii/canvas.d.ts +89 -0
- package/dist/types/vendor/mermaid-ascii/ascii/class-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/converter.d.ts +12 -0
- package/dist/types/vendor/mermaid-ascii/ascii/draw.d.ts +66 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-bundling.d.ts +48 -0
- package/dist/types/vendor/mermaid-ascii/ascii/edge-routing.d.ts +43 -0
- package/dist/types/vendor/mermaid-ascii/ascii/er-diagram.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/grid.d.ts +56 -0
- package/dist/types/vendor/mermaid-ascii/ascii/index.d.ts +65 -0
- package/dist/types/vendor/mermaid-ascii/ascii/multiline-utils.d.ts +27 -0
- package/dist/types/vendor/mermaid-ascii/ascii/pathfinder.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/sequence.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/circle.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/corners.d.ts +34 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/diamond.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/hexagon.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/index.d.ts +26 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rectangle.d.ts +31 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/rounded.d.ts +11 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/special.d.ts +59 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/stadium.d.ts +17 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/state.d.ts +30 -0
- package/dist/types/vendor/mermaid-ascii/ascii/shapes/types.d.ts +55 -0
- package/dist/types/vendor/mermaid-ascii/ascii/types.d.ts +206 -0
- package/dist/types/vendor/mermaid-ascii/ascii/validate.d.ts +51 -0
- package/dist/types/vendor/mermaid-ascii/ascii/xychart.d.ts +2 -0
- package/dist/types/vendor/mermaid-ascii/class/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/class/types.d.ts +102 -0
- package/dist/types/vendor/mermaid-ascii/er/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/er/types.d.ts +76 -0
- package/dist/types/vendor/mermaid-ascii/index.d.ts +1 -0
- package/dist/types/vendor/mermaid-ascii/multiline-utils.d.ts +9 -0
- package/dist/types/vendor/mermaid-ascii/parser.d.ts +7 -0
- package/dist/types/vendor/mermaid-ascii/sequence/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/sequence/types.d.ts +130 -0
- package/dist/types/vendor/mermaid-ascii/text-metrics.d.ts +21 -0
- package/dist/types/vendor/mermaid-ascii/types.d.ts +114 -0
- package/dist/types/vendor/mermaid-ascii/xychart/colors.d.ts +25 -0
- package/dist/types/vendor/mermaid-ascii/xychart/parser.d.ts +6 -0
- package/dist/types/vendor/mermaid-ascii/xychart/types.d.ts +145 -0
- package/dist/types/which.d.ts +37 -0
- package/dist/types/worker-host.d.ts +47 -0
- package/package.json +62 -0
- package/src/abortable.ts +109 -0
- package/src/async.ts +50 -0
- package/src/binary.ts +50 -0
- package/src/cli.ts +457 -0
- package/src/color.ts +302 -0
- package/src/dirs.ts +925 -0
- package/src/env.ts +226 -0
- package/src/fetch-retry.ts +355 -0
- package/src/format.ts +113 -0
- package/src/frontmatter.ts +162 -0
- package/src/fs-error.ts +56 -0
- package/src/glob.ts +189 -0
- package/src/index.ts +55 -0
- package/src/json-parse.ts +612 -0
- package/src/json.ts +10 -0
- package/src/logger.ts +672 -0
- package/src/loop-phase.ts +49 -0
- package/src/mermaid-ascii.ts +31 -0
- package/src/mime.ts +159 -0
- package/src/module-timer.ts +148 -0
- package/src/path-tree.ts +147 -0
- package/src/path.ts +28 -0
- package/src/peek-file.ts +188 -0
- package/src/postmortem.ts +222 -0
- package/src/procmgr.ts +195 -0
- package/src/prompt.ts +562 -0
- package/src/ptree.ts +395 -0
- package/src/ring.ts +169 -0
- package/src/runtime-install.ts +372 -0
- package/src/sanitize-text.ts +96 -0
- package/src/snowflake.ts +121 -0
- package/src/stream.ts +432 -0
- package/src/tab-spacing.ts +336 -0
- package/src/temp.ts +134 -0
- package/src/timing-buffer.ts +47 -0
- package/src/tls-fetch.ts +178 -0
- package/src/type-guards.ts +11 -0
- package/src/vendor/mermaid-ascii/NOTICE +33 -0
- package/src/vendor/mermaid-ascii/ascii/ansi.ts +409 -0
- package/src/vendor/mermaid-ascii/ascii/canvas.ts +476 -0
- package/src/vendor/mermaid-ascii/ascii/class-diagram.ts +699 -0
- package/src/vendor/mermaid-ascii/ascii/converter.ts +271 -0
- package/src/vendor/mermaid-ascii/ascii/draw.ts +1382 -0
- package/src/vendor/mermaid-ascii/ascii/edge-bundling.ts +328 -0
- package/src/vendor/mermaid-ascii/ascii/edge-routing.ts +297 -0
- package/src/vendor/mermaid-ascii/ascii/er-diagram.ts +441 -0
- package/src/vendor/mermaid-ascii/ascii/grid.ts +578 -0
- package/src/vendor/mermaid-ascii/ascii/index.ts +187 -0
- package/src/vendor/mermaid-ascii/ascii/multiline-utils.ts +78 -0
- package/src/vendor/mermaid-ascii/ascii/pathfinder.ts +215 -0
- package/src/vendor/mermaid-ascii/ascii/sequence.ts +460 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/circle.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/corners.ts +127 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/diamond.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/hexagon.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/index.ts +101 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rectangle.ts +175 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/rounded.ts +27 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/special.ts +296 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/stadium.ts +114 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/state.ts +192 -0
- package/src/vendor/mermaid-ascii/ascii/shapes/types.ts +73 -0
- package/src/vendor/mermaid-ascii/ascii/types.ts +273 -0
- package/src/vendor/mermaid-ascii/ascii/validate.ts +120 -0
- package/src/vendor/mermaid-ascii/ascii/xychart.ts +875 -0
- package/src/vendor/mermaid-ascii/class/parser.ts +290 -0
- package/src/vendor/mermaid-ascii/class/types.ts +121 -0
- package/src/vendor/mermaid-ascii/er/parser.ts +181 -0
- package/src/vendor/mermaid-ascii/er/types.ts +91 -0
- package/src/vendor/mermaid-ascii/index.ts +14 -0
- package/src/vendor/mermaid-ascii/multiline-utils.ts +30 -0
- package/src/vendor/mermaid-ascii/parser.ts +645 -0
- package/src/vendor/mermaid-ascii/sequence/parser.ts +207 -0
- package/src/vendor/mermaid-ascii/sequence/types.ts +146 -0
- package/src/vendor/mermaid-ascii/text-metrics.ts +71 -0
- package/src/vendor/mermaid-ascii/types.ts +164 -0
- package/src/vendor/mermaid-ascii/xychart/colors.ts +140 -0
- package/src/vendor/mermaid-ascii/xychart/parser.ts +115 -0
- package/src/vendor/mermaid-ascii/xychart/types.ts +150 -0
- package/src/which.ts +232 -0
- package/src/worker-host.ts +92 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache policy for which lookups.
|
|
3
|
+
*/
|
|
4
|
+
export declare const enum WhichCachePolicy {
|
|
5
|
+
/**
|
|
6
|
+
* Use cached result if available.
|
|
7
|
+
*/
|
|
8
|
+
Cached = 0,
|
|
9
|
+
/**
|
|
10
|
+
* Bypass cache and perform a new lookup.
|
|
11
|
+
*/
|
|
12
|
+
Bypass = 1,
|
|
13
|
+
/**
|
|
14
|
+
* Always update cache.
|
|
15
|
+
*/
|
|
16
|
+
Fresh = 2,
|
|
17
|
+
/**
|
|
18
|
+
* Read-only, serves from cache if present, but doesn't write.
|
|
19
|
+
*/
|
|
20
|
+
ReadOnly = 3
|
|
21
|
+
}
|
|
22
|
+
export interface WhichOptions extends Bun.WhichOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Cache policy for the lookup.
|
|
25
|
+
* Defaults to `WhichCachePolicy.Fresh`.
|
|
26
|
+
*/
|
|
27
|
+
cache?: WhichCachePolicy;
|
|
28
|
+
}
|
|
29
|
+
export declare const whichFresh: typeof Bun.which;
|
|
30
|
+
/**
|
|
31
|
+
* Locate binary on PATH (with flexible caching).
|
|
32
|
+
*
|
|
33
|
+
* @param command - Binary name to resolve
|
|
34
|
+
* @param options - Bun.WhichOptions plus `cache` control
|
|
35
|
+
* @returns Filesystem path if found, else null
|
|
36
|
+
*/
|
|
37
|
+
export declare function $which(command: string, options?: WhichOptions): string | null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Called by CLI entrypoints whose main module dispatches worker argv selectors. */
|
|
2
|
+
export declare function declareWorkerHostEntry(): void;
|
|
3
|
+
/** Main-module path of the self-dispatching CLI host, or null outside it. */
|
|
4
|
+
export declare function workerHostEntry(): string | null;
|
|
5
|
+
/**
|
|
6
|
+
* Buffers messages a Bun worker thread receives before its real handler is
|
|
7
|
+
* attached, then hands them off once it is.
|
|
8
|
+
*
|
|
9
|
+
* Bun delivers messages the parent posted before the worker spawned exactly
|
|
10
|
+
* once — when the worker entry module's top-level evaluation completes — to the
|
|
11
|
+
* `message` listeners present at that moment. A worker whose handler attaches
|
|
12
|
+
* via a later `await import(...)` therefore misses that flush. The
|
|
13
|
+
* self-dispatching CLI host imports each worker module dynamically from inside
|
|
14
|
+
* its argv dispatch, so the worker's own `parentPort.on("message")` lands after
|
|
15
|
+
* the flush and the parent's synchronously-posted `init` handshake is dropped —
|
|
16
|
+
* every run then stalls until the init timeout fires and silently falls back to
|
|
17
|
+
* the inline worker (issue: eval cells always taking the full timeout).
|
|
18
|
+
*
|
|
19
|
+
* The host calls {@link installWorkerInbox} synchronously in the entry's sync
|
|
20
|
+
* prefix (before importing the worker module) so a `parentPort` listener exists
|
|
21
|
+
* at flush time; the worker module then {@link consumeWorkerInbox}es it and
|
|
22
|
+
* binds the real handler, replaying anything buffered. Re-dispatching through
|
|
23
|
+
* `parentPort.emit("message", …)` is not an option — Bun's port is an
|
|
24
|
+
* `EventTarget` whose `emit` throws — so the inbox calls the handler directly.
|
|
25
|
+
*/
|
|
26
|
+
export interface WorkerInbox {
|
|
27
|
+
/** Route buffered and subsequent messages to `handler`; returns an unbind fn. */
|
|
28
|
+
bind(handler: (message: unknown) => void): () => void;
|
|
29
|
+
}
|
|
30
|
+
/** Minimal `parentPort` surface the inbox needs (Node/Bun `MessagePort`). */
|
|
31
|
+
interface MessageListenerPort {
|
|
32
|
+
on(event: "message", listener: (value: unknown) => void): unknown;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Attach a buffering `message` listener on `port` synchronously and stash the
|
|
36
|
+
* resulting inbox for the worker module to {@link consumeWorkerInbox}. MUST be
|
|
37
|
+
* called in the entry module's synchronous prefix — before the worker module is
|
|
38
|
+
* imported — so the listener exists when Bun flushes pre-spawn messages.
|
|
39
|
+
*/
|
|
40
|
+
export declare function installWorkerInbox(port: MessageListenerPort): WorkerInbox;
|
|
41
|
+
/**
|
|
42
|
+
* Take the inbox installed by {@link installWorkerInbox} for this worker, or
|
|
43
|
+
* `null` when the worker module was loaded directly (no host pre-buffering, so
|
|
44
|
+
* the module's own synchronous top-level listener already wins the flush).
|
|
45
|
+
*/
|
|
46
|
+
export declare function consumeWorkerInbox(): WorkerInbox | null;
|
|
47
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "module",
|
|
3
|
+
"name": "@oh-my-pi-zen/pi-utils",
|
|
4
|
+
"version": "16.3.6-zen.1",
|
|
5
|
+
"description": "Shared utilities for pi packages",
|
|
6
|
+
"homepage": "https://omp.sh",
|
|
7
|
+
"author": "Can Boluk",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/cagedbird043/oh-my-pi-zen.git",
|
|
12
|
+
"directory": "packages/utils"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/cagedbird043/oh-my-pi-zen/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"utilities",
|
|
19
|
+
"cli",
|
|
20
|
+
"logging",
|
|
21
|
+
"streams"
|
|
22
|
+
],
|
|
23
|
+
"main": "./src/index.ts",
|
|
24
|
+
"types": "./dist/types/index.d.ts",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"check": "biome check . && bun run check:types",
|
|
27
|
+
"check:types": "tsgo -p tsconfig.json --noEmit",
|
|
28
|
+
"lint": "biome lint .",
|
|
29
|
+
"test": "bun test --parallel",
|
|
30
|
+
"fix": "biome check --write --unsafe .",
|
|
31
|
+
"fmt": "biome format --write ."
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@oh-my-pi-zen/pi-natives": "16.3.6-zen.1",
|
|
35
|
+
"handlebars": "^4.7.9",
|
|
36
|
+
"winston": "^3.19.0",
|
|
37
|
+
"winston-daily-rotate-file": "^5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/bun": "^1.3.14"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"bun": ">=1.3.14"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"src",
|
|
47
|
+
"README.md",
|
|
48
|
+
"CHANGELOG.md",
|
|
49
|
+
"dist/types"
|
|
50
|
+
],
|
|
51
|
+
"exports": {
|
|
52
|
+
".": {
|
|
53
|
+
"types": "./dist/types/index.d.ts",
|
|
54
|
+
"import": "./src/index.ts"
|
|
55
|
+
},
|
|
56
|
+
"./*": {
|
|
57
|
+
"types": "./dist/types/*.d.ts",
|
|
58
|
+
"import": "./src/*.ts"
|
|
59
|
+
},
|
|
60
|
+
"./*.js": "./src/*.ts"
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/abortable.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
|
|
3
|
+
export class AbortError extends Error {
|
|
4
|
+
constructor(signal: AbortSignal) {
|
|
5
|
+
assert(signal.aborted, "Abort signal must be aborted");
|
|
6
|
+
|
|
7
|
+
const message = signal.reason instanceof Error ? signal.reason.message : "Cancelled";
|
|
8
|
+
super(`Aborted: ${message}`, { cause: signal.reason });
|
|
9
|
+
this.name = "AbortError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Abortable async iteration over a {@link ReadableStream}. Reads the source
|
|
15
|
+
* reader directly and yields each chunk, so the consumer's `for await` drives a
|
|
16
|
+
* single read loop with no intermediate stream or per-chunk enqueue.
|
|
17
|
+
*
|
|
18
|
+
* Unlike `stream.pipeThrough(..., { signal })`, this explicitly cancels the
|
|
19
|
+
* source reader on abort or early `break`, propagating HTTP-client disconnects
|
|
20
|
+
* and watchdog timeouts to the backend request instead of only stopping the
|
|
21
|
+
* local consumer. On abort it throws {@link AbortError}; the lock is released
|
|
22
|
+
* on completion, abort, throw, or early exit. The source is cancelled only on
|
|
23
|
+
* abort or early exit — never on natural EOF.
|
|
24
|
+
*/
|
|
25
|
+
export async function* abortableSource<T>(stream: ReadableStream<T>, signal?: AbortSignal): AsyncGenerator<T> {
|
|
26
|
+
if (signal?.aborted) throw new AbortError(signal);
|
|
27
|
+
const reader = stream.getReader();
|
|
28
|
+
let onAbort: (() => void) | undefined;
|
|
29
|
+
if (signal) {
|
|
30
|
+
onAbort = () => {
|
|
31
|
+
void reader.cancel(signal.reason).catch(() => {});
|
|
32
|
+
};
|
|
33
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
34
|
+
}
|
|
35
|
+
let completed = false;
|
|
36
|
+
try {
|
|
37
|
+
for (;;) {
|
|
38
|
+
const result = await reader.read();
|
|
39
|
+
if (signal?.aborted) throw new AbortError(signal);
|
|
40
|
+
if (result.done) {
|
|
41
|
+
completed = true;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
yield result.value;
|
|
45
|
+
}
|
|
46
|
+
} finally {
|
|
47
|
+
if (signal && onAbort) signal.removeEventListener("abort", onAbort);
|
|
48
|
+
// Propagate early-exit (`break`/`return`) and abort to the backend; skip
|
|
49
|
+
// on natural EOF where the stream already closed itself.
|
|
50
|
+
if (!completed) {
|
|
51
|
+
try {
|
|
52
|
+
await reader.cancel();
|
|
53
|
+
} catch {}
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
reader.releaseLock();
|
|
57
|
+
} catch {}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Runs a promise-returning function (`pr`). If the given AbortSignal is aborted before or during
|
|
63
|
+
* execution, the promise is rejected with a standard error.
|
|
64
|
+
*
|
|
65
|
+
* @param signal - Optional AbortSignal to cancel the operation
|
|
66
|
+
* @param pr - Function returning a promise to run
|
|
67
|
+
* @returns Promise resolving as `pr` would, or rejecting on abort
|
|
68
|
+
*/
|
|
69
|
+
export function untilAborted<T>(
|
|
70
|
+
signal: AbortSignal | undefined | null,
|
|
71
|
+
pr: Promise<T> | (() => Promise<T>),
|
|
72
|
+
): Promise<T> {
|
|
73
|
+
if (!signal) return typeof pr === "function" ? pr() : pr;
|
|
74
|
+
if (signal.aborted) return Promise.reject(new AbortError(signal));
|
|
75
|
+
|
|
76
|
+
const { promise, resolve, reject } = Promise.withResolvers<T>();
|
|
77
|
+
const onAbort = () => reject(new AbortError(signal));
|
|
78
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
79
|
+
|
|
80
|
+
void (async () => {
|
|
81
|
+
try {
|
|
82
|
+
resolve(await (typeof pr === "function" ? pr() : pr));
|
|
83
|
+
} catch (err) {
|
|
84
|
+
reject(err);
|
|
85
|
+
} finally {
|
|
86
|
+
signal.removeEventListener("abort", onAbort);
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
|
|
90
|
+
return promise;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Memoizes a function with no arguments, calling it once and caching the result.
|
|
95
|
+
*
|
|
96
|
+
* @param fn - Function to be called once
|
|
97
|
+
* @returns A function that returns the cached result of `fn`
|
|
98
|
+
*/
|
|
99
|
+
export function once<T>(fn: () => T): () => T {
|
|
100
|
+
let store = undefined as { value: T } | undefined;
|
|
101
|
+
return () => {
|
|
102
|
+
if (store) {
|
|
103
|
+
return store.value;
|
|
104
|
+
}
|
|
105
|
+
const value = fn();
|
|
106
|
+
store = { value };
|
|
107
|
+
return value;
|
|
108
|
+
};
|
|
109
|
+
}
|
package/src/async.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap a promise with a timeout and optional abort signal.
|
|
3
|
+
* Rejects with the given message if the timeout fires first.
|
|
4
|
+
* Cleans up all listeners on settlement.
|
|
5
|
+
*/
|
|
6
|
+
export function withTimeout<T>(promise: Promise<T>, ms: number, message: string, signal?: AbortSignal): Promise<T> {
|
|
7
|
+
if (signal?.aborted) {
|
|
8
|
+
const reason = signal.reason instanceof Error ? signal.reason : new Error("Aborted");
|
|
9
|
+
return Promise.reject(reason);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { promise: wrapped, resolve, reject } = Promise.withResolvers<T>();
|
|
13
|
+
let settled = false;
|
|
14
|
+
const timeoutId = setTimeout(() => {
|
|
15
|
+
if (settled) return;
|
|
16
|
+
settled = true;
|
|
17
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
18
|
+
reject(new Error(message));
|
|
19
|
+
}, ms);
|
|
20
|
+
|
|
21
|
+
const onAbort = () => {
|
|
22
|
+
if (settled) return;
|
|
23
|
+
settled = true;
|
|
24
|
+
clearTimeout(timeoutId);
|
|
25
|
+
reject(signal?.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (signal) {
|
|
29
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
promise.then(
|
|
33
|
+
value => {
|
|
34
|
+
if (settled) return;
|
|
35
|
+
settled = true;
|
|
36
|
+
clearTimeout(timeoutId);
|
|
37
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
38
|
+
resolve(value);
|
|
39
|
+
},
|
|
40
|
+
err => {
|
|
41
|
+
if (settled) return;
|
|
42
|
+
settled = true;
|
|
43
|
+
clearTimeout(timeoutId);
|
|
44
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
45
|
+
reject(err);
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return wrapped;
|
|
50
|
+
}
|
package/src/binary.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-based binary/text classification for files that are about to be
|
|
3
|
+
* decoded as UTF-8 text and shown to a model or user.
|
|
4
|
+
*
|
|
5
|
+
* The read tool and `@file` auto-read both materialize file bytes as UTF-8
|
|
6
|
+
* strings. For a binary file (font, object, archive, packed blob) that decode
|
|
7
|
+
* is lossy: NUL bytes and invalid sequences survive as control characters and
|
|
8
|
+
* U+FFFD replacements, which corrupt terminal rendering and waste the context
|
|
9
|
+
* window with mojibake. Sniff the header first and refuse instead.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* if (await isProbablyBinary(path)) return "[binary file omitted]";
|
|
13
|
+
* const text = await Bun.file(path).text();
|
|
14
|
+
*/
|
|
15
|
+
import { peekFile, peekFileSync } from "./peek-file";
|
|
16
|
+
|
|
17
|
+
/** Header window sniffed for the binary heuristic; mirrors git's 8000-byte scan. */
|
|
18
|
+
const BINARY_SNIFF_BYTES = 8192;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Classify an in-memory byte header as binary (non-UTF-8-text).
|
|
22
|
+
*
|
|
23
|
+
* Binary when the header contains a NUL byte (true binary, plus UTF-16/UTF-32
|
|
24
|
+
* text whose ASCII range is NUL-padded) or when it is not valid UTF-8. The
|
|
25
|
+
* decode runs in streaming mode so a multibyte sequence truncated at the header
|
|
26
|
+
* boundary is tolerated, while any genuinely invalid byte still fails — matching
|
|
27
|
+
* the strict `fatal` decode the `local://`/`ssh://` read paths already use.
|
|
28
|
+
*/
|
|
29
|
+
export function isProbablyBinaryHeader(header: Uint8Array): boolean {
|
|
30
|
+
if (header.indexOf(0) !== -1) return true;
|
|
31
|
+
try {
|
|
32
|
+
new TextDecoder("utf-8", { fatal: true }).decode(header, { stream: true });
|
|
33
|
+
return false;
|
|
34
|
+
} catch {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sniff the first {@link BINARY_SNIFF_BYTES} of `filePath` and report whether it
|
|
41
|
+
* is binary (non-UTF-8-text). See {@link isProbablyBinaryHeader} for the rule.
|
|
42
|
+
*/
|
|
43
|
+
export function isProbablyBinary(filePath: string, maxBytes = BINARY_SNIFF_BYTES): Promise<boolean> {
|
|
44
|
+
return peekFile(filePath, maxBytes, isProbablyBinaryHeader);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Synchronous {@link isProbablyBinary}. */
|
|
48
|
+
export function isProbablyBinarySync(filePath: string, maxBytes = BINARY_SNIFF_BYTES): boolean {
|
|
49
|
+
return peekFileSync(filePath, maxBytes, isProbablyBinaryHeader);
|
|
50
|
+
}
|