@linxiraos/pi-utils 1.0.0
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 +449 -0
- package/README.md +36 -0
- package/dist/types/abortable.d.ts +32 -0
- package/dist/types/acp/connection.d.ts +118 -0
- package/dist/types/acp/protocol.d.ts +526 -0
- package/dist/types/acp/schema.d.ts +41 -0
- package/dist/types/acp/stream.d.ts +8 -0
- package/dist/types/acp/transport.d.ts +81 -0
- package/dist/types/acp.d.ts +6 -0
- package/dist/types/async.d.ts +20 -0
- package/dist/types/binary.d.ts +17 -0
- package/dist/types/browsers.d.ts +68 -0
- package/dist/types/chalk.d.ts +125 -0
- package/dist/types/cli.d.ts +134 -0
- package/dist/types/color.d.ts +102 -0
- package/dist/types/dates.d.ts +7 -0
- package/dist/types/dirs.d.ts +282 -0
- package/dist/types/docx/converter.d.ts +46 -0
- package/dist/types/docx/xml.d.ts +26 -0
- package/dist/types/docx/zip.d.ts +6 -0
- package/dist/types/docx.d.ts +11 -0
- package/dist/types/dom/core.d.ts +431 -0
- package/dist/types/dom/parser.d.ts +7 -0
- package/dist/types/dom/selector.d.ts +5 -0
- package/dist/types/dom.d.ts +5 -0
- package/dist/types/env.d.ts +124 -0
- package/dist/types/fetch-retry.d.ts +96 -0
- package/dist/types/file-lock.d.ts +23 -0
- package/dist/types/format.d.ts +37 -0
- package/dist/types/frontmatter.d.ts +46 -0
- package/dist/types/fs-error.d.ts +31 -0
- package/dist/types/glob.d.ts +28 -0
- package/dist/types/headers.d.ts +34 -0
- package/dist/types/index.d.ts +39 -0
- package/dist/types/json-parse.d.ts +74 -0
- package/dist/types/json.d.ts +14 -0
- package/dist/types/logger/rotating-file.d.ts +18 -0
- package/dist/types/logger.d.ts +96 -0
- package/dist/types/loop-phase.d.ts +10 -0
- package/dist/types/lru.d.ts +46 -0
- package/dist/types/marked/core.d.ts +445 -0
- package/dist/types/marked.d.ts +2 -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 +104 -0
- package/dist/types/process-name.d.ts +7 -0
- package/dist/types/procmgr.d.ts +74 -0
- package/dist/types/prompt.d.ts +18 -0
- package/dist/types/ptree.d.ts +110 -0
- package/dist/types/readability/readability.d.ts +9 -0
- package/dist/types/readability/readerable.d.ts +10 -0
- package/dist/types/readability/types.d.ts +70 -0
- package/dist/types/readability.d.ts +4 -0
- package/dist/types/ring.d.ts +93 -0
- package/dist/types/runtime-install.d.ts +78 -0
- package/dist/types/sanitize-text.d.ts +29 -0
- package/dist/types/snowflake.d.ts +25 -0
- package/dist/types/stderr-guard.d.ts +22 -0
- package/dist/types/stream.d.ts +67 -0
- package/dist/types/tab-spacing.d.ts +24 -0
- package/dist/types/temp.d.ts +17 -0
- package/dist/types/template.d.ts +62 -0
- package/dist/types/timing-buffer.d.ts +22 -0
- package/dist/types/tls-fetch.d.ts +37 -0
- package/dist/types/turndown/gfm.d.ts +11 -0
- package/dist/types/turndown/html.d.ts +5 -0
- package/dist/types/turndown/service.d.ts +21 -0
- package/dist/types/turndown/types.d.ts +70 -0
- package/dist/types/turndown.d.ts +4 -0
- package/dist/types/type-guards.d.ts +5 -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/version.d.ts +18 -0
- package/dist/types/vterm/buffer.d.ts +99 -0
- package/dist/types/vterm/terminal.d.ts +44 -0
- package/dist/types/vterm.d.ts +8 -0
- package/dist/types/which.d.ts +37 -0
- package/dist/types/worker-host.d.ts +51 -0
- package/dist/types/xml.d.ts +31 -0
- package/package.json +59 -0
- package/src/abortable.ts +109 -0
- package/src/acp/connection.ts +344 -0
- package/src/acp/protocol.ts +466 -0
- package/src/acp/schema.ts +160 -0
- package/src/acp/stream.ts +82 -0
- package/src/acp/transport.ts +213 -0
- package/src/acp.ts +6 -0
- package/src/async.ts +91 -0
- package/src/binary.ts +50 -0
- package/src/browsers.ts +501 -0
- package/src/chalk.ts +312 -0
- package/src/cli.ts +529 -0
- package/src/color.ts +302 -0
- package/src/dates.ts +194 -0
- package/src/dirs.ts +1026 -0
- package/src/docx/converter.ts +681 -0
- package/src/docx/xml.ts +166 -0
- package/src/docx/zip.ts +87 -0
- package/src/docx.ts +20 -0
- package/src/dom/core.ts +1254 -0
- package/src/dom/parser.ts +370 -0
- package/src/dom/selector.ts +290 -0
- package/src/dom.ts +33 -0
- package/src/env.ts +400 -0
- package/src/fetch-retry.ts +379 -0
- package/src/file-lock.ts +69 -0
- package/src/format.ts +113 -0
- package/src/frontmatter.ts +205 -0
- package/src/fs-error.ts +56 -0
- package/src/glob.ts +189 -0
- package/src/headers.ts +167 -0
- package/src/index.ts +59 -0
- package/src/json-parse.ts +821 -0
- package/src/json.ts +23 -0
- package/src/logger/rotating-file.ts +149 -0
- package/src/logger.ts +798 -0
- package/src/loop-phase.ts +49 -0
- package/src/lru.ts +185 -0
- package/src/marked/core.ts +1576 -0
- package/src/marked.ts +2 -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 +433 -0
- package/src/process-name.ts +57 -0
- package/src/procmgr.ts +247 -0
- package/src/prompt.ts +545 -0
- package/src/ptree.ts +412 -0
- package/src/readability/readability.ts +533 -0
- package/src/readability/readerable.ts +51 -0
- package/src/readability/types.ts +72 -0
- package/src/readability.ts +11 -0
- package/src/ring.ts +169 -0
- package/src/runtime-install.ts +393 -0
- package/src/sanitize-text.ts +96 -0
- package/src/snowflake.ts +121 -0
- package/src/stderr-guard.ts +155 -0
- package/src/stream.ts +454 -0
- package/src/tab-spacing.ts +336 -0
- package/src/temp.ts +134 -0
- package/src/template.ts +586 -0
- package/src/timing-buffer.ts +47 -0
- package/src/tls-fetch.ts +178 -0
- package/src/turndown/gfm.ts +106 -0
- package/src/turndown/html.ts +257 -0
- package/src/turndown/service.ts +334 -0
- package/src/turndown/types.ts +81 -0
- package/src/turndown.ts +5 -0
- package/src/type-guards.ts +17 -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 +277 -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/version.ts +99 -0
- package/src/vterm/buffer.ts +218 -0
- package/src/vterm/terminal.ts +773 -0
- package/src/vterm.ts +8 -0
- package/src/which.ts +234 -0
- package/src/worker-host.ts +100 -0
- package/src/xml.ts +313 -0
package/src/vterm.ts
ADDED
package/src/which.ts
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// OS-agnostic "which" helper with robust macOS toolchain lookup and flexible cache control.
|
|
2
|
+
//
|
|
3
|
+
// - Falls back to macOS Xcode/CLT toolchain directories if standard `Bun.which()` fails on Darwin.
|
|
4
|
+
// Resolves the active developer directory via $DEVELOPER_DIR / /var/db/xcode_select_link symlink
|
|
5
|
+
// to avoid spawning xcrun subprocesses.
|
|
6
|
+
// - Supports four cache modes (`none`, `fresh`, `ro`, `cached`) for control over discovery cost and determinism.
|
|
7
|
+
// - Computes a stable cache key from command + options to avoid redundant lookups within a process.
|
|
8
|
+
// - Returns path to resolved binary or null if not found.
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as os from "node:os";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
|
|
15
|
+
type CacheKey = string | bigint | number;
|
|
16
|
+
|
|
17
|
+
// Tools shipped by Xcode / Command Line Tools that callers actually look up.
|
|
18
|
+
// Keeps the set small so darwinWhich can fast-reject non-Xcode commands without
|
|
19
|
+
// touching the filesystem. Only needs entries for binaries that live *exclusively*
|
|
20
|
+
// in toolchain dirs (not on a typical $PATH).
|
|
21
|
+
const XCODE_BINS = new Set([
|
|
22
|
+
// Compilers & driver aliases
|
|
23
|
+
"clang",
|
|
24
|
+
"clang++",
|
|
25
|
+
"gcc",
|
|
26
|
+
"g++",
|
|
27
|
+
"cc",
|
|
28
|
+
"c++",
|
|
29
|
+
"cpp",
|
|
30
|
+
"c89",
|
|
31
|
+
"c99",
|
|
32
|
+
"swift",
|
|
33
|
+
"swiftc",
|
|
34
|
+
"swift-frontend",
|
|
35
|
+
// Language servers (LSP)
|
|
36
|
+
"clangd",
|
|
37
|
+
"sourcekit-lsp",
|
|
38
|
+
// Linker & archive tools
|
|
39
|
+
"ld",
|
|
40
|
+
"ld-classic",
|
|
41
|
+
"ar",
|
|
42
|
+
"ranlib",
|
|
43
|
+
"libtool",
|
|
44
|
+
"as",
|
|
45
|
+
"lipo",
|
|
46
|
+
"install_name_tool",
|
|
47
|
+
"codesign_allocate",
|
|
48
|
+
// Build utilities
|
|
49
|
+
"make",
|
|
50
|
+
"gnumake",
|
|
51
|
+
"m4",
|
|
52
|
+
"flex",
|
|
53
|
+
"bison",
|
|
54
|
+
"yacc",
|
|
55
|
+
"lex",
|
|
56
|
+
// VCS (CLT ships git)
|
|
57
|
+
"git",
|
|
58
|
+
"git-receive-pack",
|
|
59
|
+
"git-upload-pack",
|
|
60
|
+
"git-upload-archive",
|
|
61
|
+
"git-shell",
|
|
62
|
+
"scalar",
|
|
63
|
+
// Debugger
|
|
64
|
+
"lldb",
|
|
65
|
+
"lldb-dap",
|
|
66
|
+
// Binary inspection
|
|
67
|
+
"nm",
|
|
68
|
+
"otool",
|
|
69
|
+
"objdump",
|
|
70
|
+
"strings",
|
|
71
|
+
"strip",
|
|
72
|
+
"size",
|
|
73
|
+
"dsymutil",
|
|
74
|
+
"dwarfdump",
|
|
75
|
+
"lipo",
|
|
76
|
+
"vtool",
|
|
77
|
+
// Clang tooling
|
|
78
|
+
"clang-format",
|
|
79
|
+
"swift-format",
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
// Prefixes for versioned binaries (e.g. python3.9, pip3.12, pydoc3.9, 2to3-3.9)
|
|
83
|
+
const XCODE_BIN_PREFIXES = ["python", "pip", "pydoc", "2to3"];
|
|
84
|
+
|
|
85
|
+
function isXcodeBin(command: string): boolean {
|
|
86
|
+
if (XCODE_BINS.has(command)) return true;
|
|
87
|
+
for (const prefix of XCODE_BIN_PREFIXES) {
|
|
88
|
+
if (command.startsWith(prefix)) return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Resolve the active Xcode developer directory once, without spawning any process.
|
|
94
|
+
// Priority: $DEVELOPER_DIR env → /var/db/xcode_select_link symlink → common fallback paths.
|
|
95
|
+
function getDeveloperDirs(): string | null {
|
|
96
|
+
// 1. Explicit env override
|
|
97
|
+
const envDir = process.env.DEVELOPER_DIR;
|
|
98
|
+
if (envDir && fs.existsSync(envDir)) {
|
|
99
|
+
return envDir;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 2. xcode-select stores the active path as a symlink
|
|
103
|
+
try {
|
|
104
|
+
return fs.readlinkSync("/var/db/xcode_select_link");
|
|
105
|
+
} catch {
|
|
106
|
+
// symlink may not exist on minimal installs
|
|
107
|
+
}
|
|
108
|
+
// 3. Common locations
|
|
109
|
+
for (const candidate of ["/Applications/Xcode.app/Contents/Developer", "/Library/Developer/CommandLineTools"]) {
|
|
110
|
+
if (fs.existsSync(candidate)) {
|
|
111
|
+
return candidate;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Build the list of extra toolchain bin directories to check on macOS.
|
|
118
|
+
// Computed lazily once from the resolved developer directory.
|
|
119
|
+
let macosToolPaths: Map<string, string> | undefined;
|
|
120
|
+
function getMacosToolPaths(): Map<string, string> {
|
|
121
|
+
if (macosToolPaths) return macosToolPaths;
|
|
122
|
+
const paths: string[] = [
|
|
123
|
+
// Always check Command Line Tools (may be independent of Xcode)
|
|
124
|
+
"/Library/Developer/CommandLineTools/usr/bin",
|
|
125
|
+
];
|
|
126
|
+
const devDir = getDeveloperDirs();
|
|
127
|
+
if (devDir) {
|
|
128
|
+
paths.push(path.join(devDir, "usr/bin"), path.join(devDir, "Toolchains/XcodeDefault.xctoolchain/usr/bin"));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Deduplicate (e.g. devDir may already be CommandLineTools)
|
|
132
|
+
macosToolPaths = new Map<string, string>();
|
|
133
|
+
for (const dir of Array.from(new Set(paths))) {
|
|
134
|
+
try {
|
|
135
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
136
|
+
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
137
|
+
if (macosToolPaths.has(entry.name)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
macosToolPaths.set(entry.name, path.join(dir, entry.name));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// dir doesn't exist or isn't readable
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return macosToolPaths;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Map: cache key -> resolved binary path or null (not found)
|
|
151
|
+
const toolCache = new Map<CacheKey, string | null>();
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Cache policy for which lookups.
|
|
155
|
+
*/
|
|
156
|
+
export const enum WhichCachePolicy {
|
|
157
|
+
/**
|
|
158
|
+
* Use cached result if available.
|
|
159
|
+
*/
|
|
160
|
+
Cached = 0,
|
|
161
|
+
/**
|
|
162
|
+
* Bypass cache and perform a new lookup.
|
|
163
|
+
*/
|
|
164
|
+
Bypass,
|
|
165
|
+
/**
|
|
166
|
+
* Always update cache.
|
|
167
|
+
*/
|
|
168
|
+
Fresh,
|
|
169
|
+
/**
|
|
170
|
+
* Read-only, serves from cache if present, but doesn't write.
|
|
171
|
+
*/
|
|
172
|
+
ReadOnly,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Extension: additional cache policy for tool path lookup
|
|
176
|
+
export interface WhichOptions extends Bun.WhichOptions {
|
|
177
|
+
/**
|
|
178
|
+
* Cache policy for the lookup.
|
|
179
|
+
* Defaults to `WhichCachePolicy.Fresh`.
|
|
180
|
+
*/
|
|
181
|
+
cache?: WhichCachePolicy;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Darwin-specific "which" shim: consult Xcode/CLT toolchain directories after $PATH.
|
|
185
|
+
// Uses cached directory listings instead of per-command existsSync or xcrun subprocesses.
|
|
186
|
+
function darwinWhich(command: string, options?: Bun.WhichOptions): string | null {
|
|
187
|
+
const regular = Bun.which(command, options);
|
|
188
|
+
if (regular) return regular;
|
|
189
|
+
if (isXcodeBin(command)) {
|
|
190
|
+
return getMacosToolPaths().get(command) ?? null;
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Which function that incorporates Darwin Xcode logic if platform reports as 'darwin'
|
|
196
|
+
export const whichFresh = os.platform() === "darwin" ? darwinWhich : Bun.which;
|
|
197
|
+
|
|
198
|
+
// Derive stable cache key from command and lookup options
|
|
199
|
+
function cacheKey(command: string, options?: Bun.WhichOptions): CacheKey {
|
|
200
|
+
if (!options) return command;
|
|
201
|
+
if (!options.cwd && !options.PATH) return command;
|
|
202
|
+
let h = Bun.hash(command);
|
|
203
|
+
if (options.cwd) h = Bun.hash(options.cwd, h);
|
|
204
|
+
if (options.PATH) h = Bun.hash(options.PATH, h);
|
|
205
|
+
return h;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Locate binary on PATH (with flexible caching).
|
|
210
|
+
*
|
|
211
|
+
* @param command - Binary name to resolve
|
|
212
|
+
* @param options - Bun.WhichOptions plus `cache` control
|
|
213
|
+
* @returns Filesystem path if found, else null
|
|
214
|
+
*/
|
|
215
|
+
export function $which(command: string, options?: WhichOptions): string | null {
|
|
216
|
+
const cachePolicy = options?.cache ?? WhichCachePolicy.Cached;
|
|
217
|
+
const lookupOptions =
|
|
218
|
+
options?.PATH !== undefined || process.env.PATH === undefined ? options : { ...options, PATH: process.env.PATH };
|
|
219
|
+
let key: CacheKey | undefined;
|
|
220
|
+
|
|
221
|
+
if (cachePolicy !== WhichCachePolicy.Bypass) {
|
|
222
|
+
key = cacheKey(command, lookupOptions);
|
|
223
|
+
if (cachePolicy !== WhichCachePolicy.Fresh) {
|
|
224
|
+
const cached = toolCache.get(key);
|
|
225
|
+
if (cached !== undefined) return cached;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const result = whichFresh(command, lookupOptions);
|
|
230
|
+
if (key != null && cachePolicy !== WhichCachePolicy.ReadOnly) {
|
|
231
|
+
toolCache.set(key, result);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { stripWindowsExtendedLengthPathPrefix } from "./path";
|
|
2
|
+
|
|
3
|
+
/** Prefix reserved for argv selectors dispatched by the shared CLI worker host. */
|
|
4
|
+
export const WORKER_HOST_SELECTOR_PREFIX = "__omp_worker_";
|
|
5
|
+
|
|
6
|
+
/** Whether an argv value selects a worker hosted by the shared CLI entrypoint. */
|
|
7
|
+
export function isWorkerHostSelector(value: string | undefined): value is string {
|
|
8
|
+
return value?.startsWith(WORKER_HOST_SELECTOR_PREFIX) ?? false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Main-module path declared by self-dispatching CLI entrypoints — entries
|
|
13
|
+
* whose top-level argv handling routes hidden `__omp_*` worker selectors.
|
|
14
|
+
* Worker spawn sites re-enter this module via `new Worker(entry, { argv })`,
|
|
15
|
+
* so every distribution (source, npm bundle, compiled binary) needs exactly
|
|
16
|
+
* one JavaScript entrypoint. Never set under `bun test`, SDK embedding, or
|
|
17
|
+
* standalone package bins — those hosts load worker modules directly.
|
|
18
|
+
*/
|
|
19
|
+
let workerHostMain: string | null = null;
|
|
20
|
+
|
|
21
|
+
/** Called by CLI entrypoints whose main module dispatches worker argv selectors. */
|
|
22
|
+
export function declareWorkerHostEntry(): void {
|
|
23
|
+
workerHostMain = stripWindowsExtendedLengthPathPrefix(Bun.main);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Main-module path of the self-dispatching CLI host, or null outside it. */
|
|
27
|
+
export function workerHostEntry(): string | null {
|
|
28
|
+
return workerHostMain;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Buffers messages a Bun worker thread receives before its real handler is
|
|
33
|
+
* attached, then hands them off once it is.
|
|
34
|
+
*
|
|
35
|
+
* Bun delivers messages the parent posted before the worker spawned exactly
|
|
36
|
+
* once — when the worker entry module's top-level evaluation completes — to the
|
|
37
|
+
* `message` listeners present at that moment. A worker whose handler attaches
|
|
38
|
+
* via a later `await import(...)` therefore misses that flush. The
|
|
39
|
+
* self-dispatching CLI host imports each worker module dynamically from inside
|
|
40
|
+
* its argv dispatch, so the worker's own `parentPort.on("message")` lands after
|
|
41
|
+
* the flush and the parent's synchronously-posted `init` handshake is dropped —
|
|
42
|
+
* every run then stalls until the init timeout fires and silently falls back to
|
|
43
|
+
* the inline worker (issue: eval cells always taking the full timeout).
|
|
44
|
+
*
|
|
45
|
+
* The host calls {@link installWorkerInbox} synchronously in the entry's sync
|
|
46
|
+
* prefix (before importing the worker module) so a `parentPort` listener exists
|
|
47
|
+
* at flush time; the worker module then {@link consumeWorkerInbox}es it and
|
|
48
|
+
* binds the real handler, replaying anything buffered. Re-dispatching through
|
|
49
|
+
* `parentPort.emit("message", …)` is not an option — Bun's port is an
|
|
50
|
+
* `EventTarget` whose `emit` throws — so the inbox calls the handler directly.
|
|
51
|
+
*/
|
|
52
|
+
export interface WorkerInbox {
|
|
53
|
+
/** Route buffered and subsequent messages to `handler`; returns an unbind fn. */
|
|
54
|
+
bind(handler: (message: unknown) => void): () => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Minimal `parentPort` surface the inbox needs (Node/Bun `MessagePort`). */
|
|
58
|
+
interface MessageListenerPort {
|
|
59
|
+
on(event: "message", listener: (value: unknown) => void): unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let pendingInbox: WorkerInbox | null = null;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Attach a buffering `message` listener on `port` synchronously and stash the
|
|
66
|
+
* resulting inbox for the worker module to {@link consumeWorkerInbox}. MUST be
|
|
67
|
+
* called in the entry module's synchronous prefix — before the worker module is
|
|
68
|
+
* imported — so the listener exists when Bun flushes pre-spawn messages.
|
|
69
|
+
*/
|
|
70
|
+
export function installWorkerInbox(port: MessageListenerPort): WorkerInbox {
|
|
71
|
+
const queue: unknown[] = [];
|
|
72
|
+
let handler: ((message: unknown) => void) | null = null;
|
|
73
|
+
port.on("message", (data: unknown) => {
|
|
74
|
+
if (handler) handler(data);
|
|
75
|
+
else queue.push(data);
|
|
76
|
+
});
|
|
77
|
+
const inbox: WorkerInbox = {
|
|
78
|
+
bind(next) {
|
|
79
|
+
handler = next;
|
|
80
|
+
for (const data of queue) next(data);
|
|
81
|
+
queue.length = 0;
|
|
82
|
+
return () => {
|
|
83
|
+
if (handler === next) handler = null;
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
pendingInbox = inbox;
|
|
88
|
+
return inbox;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Take the inbox installed by {@link installWorkerInbox} for this worker, or
|
|
93
|
+
* `null` when the worker module was loaded directly (no host pre-buffering, so
|
|
94
|
+
* the module's own synchronous top-level listener already wins the flush).
|
|
95
|
+
*/
|
|
96
|
+
export function consumeWorkerInbox(): WorkerInbox | null {
|
|
97
|
+
const inbox = pendingInbox;
|
|
98
|
+
pendingInbox = null;
|
|
99
|
+
return inbox;
|
|
100
|
+
}
|
package/src/xml.ts
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/** Behavior-compatible reimplementation of fast-xml-parser's used surface. */
|
|
2
|
+
|
|
3
|
+
/** Context supplied to an array-selection callback. */
|
|
4
|
+
export type XMLArraySelector = (
|
|
5
|
+
tagName: string,
|
|
6
|
+
jPath: string,
|
|
7
|
+
isLeafNode: boolean,
|
|
8
|
+
isAttribute: boolean | null,
|
|
9
|
+
) => boolean;
|
|
10
|
+
|
|
11
|
+
/** Options supported by {@link XMLParser}. */
|
|
12
|
+
export interface XMLParserOptions {
|
|
13
|
+
/** Omits attributes when true (default). */
|
|
14
|
+
ignoreAttributes?: boolean;
|
|
15
|
+
/** Prefix applied to attribute property names. */
|
|
16
|
+
attributeNamePrefix?: string;
|
|
17
|
+
/** Property used for text alongside children or attributes. */
|
|
18
|
+
textNodeName?: string;
|
|
19
|
+
/** Trims ordinary text and attribute values. */
|
|
20
|
+
trimValues?: boolean;
|
|
21
|
+
/** Coerces numeric and boolean element text. */
|
|
22
|
+
parseTagValue?: boolean;
|
|
23
|
+
/** Coerces numeric and boolean attribute values. */
|
|
24
|
+
parseAttributeValue?: boolean;
|
|
25
|
+
/** Controls entity processing and its expansion ceiling. */
|
|
26
|
+
processEntities?: boolean | { maxTotalExpansions?: number };
|
|
27
|
+
/** Selects element or attribute properties that must always be arrays. */
|
|
28
|
+
isArray?: XMLArraySelector;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type XmlObject = Record<string, unknown>;
|
|
32
|
+
|
|
33
|
+
const STANDARD_ENTITIES: Record<string, string> = { amp: "&", apos: "'", gt: ">", lt: "<", quot: '"' };
|
|
34
|
+
|
|
35
|
+
interface ParsedElement {
|
|
36
|
+
name: string;
|
|
37
|
+
value: unknown;
|
|
38
|
+
leaf: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ParserSettings {
|
|
42
|
+
ignoreAttributes: boolean;
|
|
43
|
+
attributeNamePrefix: string;
|
|
44
|
+
textNodeName: string;
|
|
45
|
+
trimValues: boolean;
|
|
46
|
+
parseTagValue: boolean;
|
|
47
|
+
parseAttributeValue: boolean;
|
|
48
|
+
processEntities: boolean;
|
|
49
|
+
maxTotalExpansions: number;
|
|
50
|
+
isArray?: XMLArraySelector;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Parses XML into the object shape used by the document converters. */
|
|
54
|
+
export class XMLParser {
|
|
55
|
+
readonly #settings: ParserSettings;
|
|
56
|
+
|
|
57
|
+
constructor(options: XMLParserOptions = {}) {
|
|
58
|
+
this.#settings = {
|
|
59
|
+
ignoreAttributes: options.ignoreAttributes ?? true,
|
|
60
|
+
attributeNamePrefix: options.attributeNamePrefix ?? "@_",
|
|
61
|
+
textNodeName: options.textNodeName ?? "#text",
|
|
62
|
+
trimValues: options.trimValues ?? true,
|
|
63
|
+
parseTagValue: options.parseTagValue ?? true,
|
|
64
|
+
parseAttributeValue: options.parseAttributeValue ?? false,
|
|
65
|
+
processEntities: options.processEntities !== false,
|
|
66
|
+
maxTotalExpansions:
|
|
67
|
+
typeof options.processEntities === "object"
|
|
68
|
+
? (options.processEntities.maxTotalExpansions ?? 100_000)
|
|
69
|
+
: 100_000,
|
|
70
|
+
isArray: options.isArray,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Parses one XML document. */
|
|
75
|
+
parse(xml: string): unknown {
|
|
76
|
+
return new XmlReader(xml, this.#settings).parseDocument();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class XmlReader {
|
|
81
|
+
readonly #xml: string;
|
|
82
|
+
readonly #settings: ParserSettings;
|
|
83
|
+
readonly #entities = new Map<string, string>();
|
|
84
|
+
#position = 0;
|
|
85
|
+
#expansions = 0;
|
|
86
|
+
|
|
87
|
+
constructor(xml: string, settings: ParserSettings) {
|
|
88
|
+
this.#xml = xml.charCodeAt(0) === 0xfeff ? xml.slice(1) : xml;
|
|
89
|
+
this.#settings = settings;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
parseDocument(): XmlObject {
|
|
93
|
+
const document: XmlObject = {};
|
|
94
|
+
while (this.#position < this.#xml.length) {
|
|
95
|
+
if (this.#xml.startsWith("<!--", this.#position)) {
|
|
96
|
+
this.#skipThrough("-->");
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (this.#xml.startsWith("<!DOCTYPE", this.#position)) {
|
|
100
|
+
this.#readDoctype();
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (this.#xml.startsWith("<?", this.#position)) {
|
|
104
|
+
this.#readProcessingInstruction(document);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (this.#xml[this.#position] === "<") {
|
|
108
|
+
const element = this.#readElement("");
|
|
109
|
+
this.#addValue(document, element.name, element.name, element.value, element.leaf, null);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
this.#position++;
|
|
113
|
+
}
|
|
114
|
+
return document;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#readElement(parentPath: string): ParsedElement {
|
|
118
|
+
this.#position++;
|
|
119
|
+
const name = this.#readName();
|
|
120
|
+
const path = parentPath ? `${parentPath}.${name}` : name;
|
|
121
|
+
const attributes: [string, string][] = [];
|
|
122
|
+
let selfClosing = false;
|
|
123
|
+
while (this.#position < this.#xml.length) {
|
|
124
|
+
this.#skipWhitespace();
|
|
125
|
+
if (this.#xml.startsWith("/>", this.#position)) {
|
|
126
|
+
this.#position += 2;
|
|
127
|
+
selfClosing = true;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
if (this.#xml[this.#position] === ">") {
|
|
131
|
+
this.#position++;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
const attributeName = this.#readName();
|
|
135
|
+
this.#skipWhitespace();
|
|
136
|
+
let value = "true";
|
|
137
|
+
if (this.#xml[this.#position] === "=") {
|
|
138
|
+
this.#position++;
|
|
139
|
+
this.#skipWhitespace();
|
|
140
|
+
value = this.#readAttributeValue();
|
|
141
|
+
}
|
|
142
|
+
attributes.push([attributeName, value]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const children: XmlObject = {};
|
|
146
|
+
let text = "";
|
|
147
|
+
let hasChildren = false;
|
|
148
|
+
if (!selfClosing) {
|
|
149
|
+
while (this.#position < this.#xml.length) {
|
|
150
|
+
if (this.#xml.startsWith(`</${name}`, this.#position)) {
|
|
151
|
+
this.#position += name.length + 2;
|
|
152
|
+
const close = this.#xml.indexOf(">", this.#position);
|
|
153
|
+
this.#position = close < 0 ? this.#xml.length : close + 1;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
if (this.#xml.startsWith("<![CDATA[", this.#position)) {
|
|
157
|
+
const end = this.#xml.indexOf("]]>", this.#position + 9);
|
|
158
|
+
const raw = this.#xml.slice(this.#position + 9, end < 0 ? this.#xml.length : end);
|
|
159
|
+
const normalized = raw;
|
|
160
|
+
text += this.#settings.parseTagValue ? String(coerceValue(normalized)) : normalized;
|
|
161
|
+
this.#position = end < 0 ? this.#xml.length : end + 3;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (this.#xml.startsWith("<!--", this.#position)) {
|
|
165
|
+
this.#skipThrough("-->");
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (this.#xml.startsWith("<?", this.#position)) {
|
|
169
|
+
this.#skipThrough("?>");
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (this.#xml[this.#position] === "<") {
|
|
173
|
+
const child = this.#readElement(path);
|
|
174
|
+
hasChildren = true;
|
|
175
|
+
this.#addValue(children, child.name, `${path}.${child.name}`, child.value, child.leaf, null);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const next = this.#xml.indexOf("<", this.#position);
|
|
179
|
+
const end = next < 0 ? this.#xml.length : next;
|
|
180
|
+
const normalized = this.#normalizeText(this.#xml.slice(this.#position, end), true);
|
|
181
|
+
text += this.#settings.parseTagValue ? String(coerceValue(normalized)) : normalized;
|
|
182
|
+
this.#position = end;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const hasAttributes = !this.#settings.ignoreAttributes && attributes.length > 0;
|
|
187
|
+
const parsedText = this.#settings.parseTagValue ? coerceValue(text) : text;
|
|
188
|
+
if (!hasChildren && !hasAttributes) return { name, value: parsedText, leaf: true };
|
|
189
|
+
if (text !== "") children[this.#settings.textNodeName] = parsedText;
|
|
190
|
+
if (hasAttributes) {
|
|
191
|
+
for (const [attributeName, raw] of attributes) {
|
|
192
|
+
const key = `${this.#settings.attributeNamePrefix}${attributeName}`;
|
|
193
|
+
const value = this.#settings.parseAttributeValue ? coerceValue(raw) : raw;
|
|
194
|
+
this.#addValue(children, key, `${path}.${attributeName}`, value, true, true);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return { name, value: children, leaf: !hasChildren };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#readAttributeValue(): string {
|
|
201
|
+
const quote = this.#xml[this.#position];
|
|
202
|
+
if (quote === '"' || quote === "'") {
|
|
203
|
+
const start = ++this.#position;
|
|
204
|
+
const end = this.#xml.indexOf(quote, start);
|
|
205
|
+
this.#position = end < 0 ? this.#xml.length : end + 1;
|
|
206
|
+
return this.#normalizeText(this.#xml.slice(start, end < 0 ? this.#xml.length : end), true);
|
|
207
|
+
}
|
|
208
|
+
const start = this.#position;
|
|
209
|
+
while (this.#position < this.#xml.length && !/[\s>]/.test(this.#xml[this.#position])) this.#position++;
|
|
210
|
+
return this.#normalizeText(this.#xml.slice(start, this.#position), true);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#readProcessingInstruction(target: XmlObject): void {
|
|
214
|
+
this.#position += 2;
|
|
215
|
+
const name = this.#readName();
|
|
216
|
+
const bodyStart = this.#position;
|
|
217
|
+
const end = this.#xml.indexOf("?>", bodyStart);
|
|
218
|
+
const body = this.#xml.slice(bodyStart, end < 0 ? this.#xml.length : end);
|
|
219
|
+
this.#position = end < 0 ? this.#xml.length : end + 2;
|
|
220
|
+
const value: XmlObject = {};
|
|
221
|
+
let hasAttribute = false;
|
|
222
|
+
const attributePattern = /([^\s=]+)\s*=\s*(["'])(.*?)\2/g;
|
|
223
|
+
for (const match of body.matchAll(attributePattern)) {
|
|
224
|
+
if (!this.#settings.ignoreAttributes) {
|
|
225
|
+
value[`${this.#settings.attributeNamePrefix}${match[1]}`] = this.#normalizeText(match[3]!, true);
|
|
226
|
+
hasAttribute = true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
target[`?${name}`] = hasAttribute ? value : "";
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#readDoctype(): void {
|
|
233
|
+
const start = this.#position;
|
|
234
|
+
let bracketDepth = 0;
|
|
235
|
+
let quote = "";
|
|
236
|
+
while (this.#position < this.#xml.length) {
|
|
237
|
+
const char = this.#xml[this.#position++];
|
|
238
|
+
if (quote) {
|
|
239
|
+
if (char === quote) quote = "";
|
|
240
|
+
} else if (char === '"' || char === "'") quote = char;
|
|
241
|
+
else if (char === "[") bracketDepth++;
|
|
242
|
+
else if (char === "]") bracketDepth--;
|
|
243
|
+
else if (char === ">" && bracketDepth === 0) break;
|
|
244
|
+
}
|
|
245
|
+
const declaration = this.#xml.slice(start, this.#position);
|
|
246
|
+
const entityPattern = /<!ENTITY\s+([^\s]+)\s+(["'])(.*?)\2\s*>/gs;
|
|
247
|
+
for (const match of declaration.matchAll(entityPattern)) this.#entities.set(match[1]!, match[3]!);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#normalizeText(value: string, entities: boolean): string {
|
|
251
|
+
let normalized = this.#settings.trimValues ? value.trim() : value;
|
|
252
|
+
if (entities && this.#settings.processEntities && normalized.includes("&"))
|
|
253
|
+
normalized = this.#expandEntities(normalized);
|
|
254
|
+
return normalized;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#expandEntities(value: string, stack?: ReadonlySet<string>): string {
|
|
258
|
+
return value.replace(/&([A-Za-z_:][\w.:-]*);/g, (whole, name: string) => {
|
|
259
|
+
const standard = STANDARD_ENTITIES[name];
|
|
260
|
+
if (standard !== undefined) return standard;
|
|
261
|
+
const replacement = this.#entities.get(name);
|
|
262
|
+
if (replacement === undefined || stack?.has(name)) return whole;
|
|
263
|
+
this.#expansions++;
|
|
264
|
+
if (this.#expansions > this.#settings.maxTotalExpansions) throw new Error("Entity expansion limit exceeded");
|
|
265
|
+
const nextStack = new Set(stack);
|
|
266
|
+
nextStack.add(name);
|
|
267
|
+
return this.#expandEntities(replacement, nextStack);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#addValue(
|
|
272
|
+
target: XmlObject,
|
|
273
|
+
name: string,
|
|
274
|
+
path: string,
|
|
275
|
+
value: unknown,
|
|
276
|
+
leaf: boolean,
|
|
277
|
+
attribute: boolean | null,
|
|
278
|
+
): void {
|
|
279
|
+
const present = Object.hasOwn(target, name);
|
|
280
|
+
if (present) {
|
|
281
|
+
const current = target[name];
|
|
282
|
+
if (Array.isArray(current)) current.push(value);
|
|
283
|
+
else target[name] = [current, value];
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const forceArray = this.#settings.isArray?.(name, path, leaf, attribute) ?? false;
|
|
287
|
+
target[name] = forceArray ? [value] : value;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
#readName(): string {
|
|
291
|
+
const start = this.#position;
|
|
292
|
+
while (this.#position < this.#xml.length && !/[\s/>=]/.test(this.#xml[this.#position])) this.#position++;
|
|
293
|
+
return this.#xml.slice(start, this.#position);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#skipWhitespace(): void {
|
|
297
|
+
while (this.#position < this.#xml.length && /\s/.test(this.#xml[this.#position])) this.#position++;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#skipThrough(marker: string): void {
|
|
301
|
+
const end = this.#xml.indexOf(marker, this.#position + marker.length);
|
|
302
|
+
this.#position = end < 0 ? this.#xml.length : end + marker.length;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function coerceValue(value: string): unknown {
|
|
307
|
+
if (value === "true") return true;
|
|
308
|
+
if (value === "false") return false;
|
|
309
|
+
if (value !== "" && /^[+-]?(?:0[xX][\da-fA-F]+|(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)$/.test(value)) {
|
|
310
|
+
return Number(value);
|
|
311
|
+
}
|
|
312
|
+
return value;
|
|
313
|
+
}
|