@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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { YAML } from "bun";
|
|
2
|
+
import { truncate } from "./format";
|
|
3
|
+
import * as logger from "./logger";
|
|
4
|
+
|
|
5
|
+
function stripHtmlComments(content: string): string {
|
|
6
|
+
return content.replace(/<!--[\s\S]*?-->/g, "");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Convert kebab-case to camelCase (e.g. "thinking-level" -> "thinkingLevel") */
|
|
10
|
+
function kebabToCamel(key: string): string {
|
|
11
|
+
if (!key.includes("-")) return key;
|
|
12
|
+
return key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Recursively normalize object keys from kebab-case to camelCase — the
|
|
17
|
+
* representation convention for frontmatter consumed inside this codebase.
|
|
18
|
+
* Exported for loaders that parse with `rawKeys: true` to validate exact
|
|
19
|
+
* spec-defined keys, then normalize for storage.
|
|
20
|
+
*/
|
|
21
|
+
export function normalizeFrontmatterKeys<T>(obj: T): T {
|
|
22
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
23
|
+
if (Array.isArray(obj)) {
|
|
24
|
+
let changed = false;
|
|
25
|
+
const out: unknown[] = new Array(obj.length);
|
|
26
|
+
for (let i = 0; i < obj.length; i++) {
|
|
27
|
+
const v = obj[i];
|
|
28
|
+
const nv = normalizeFrontmatterKeys(v);
|
|
29
|
+
out[i] = nv;
|
|
30
|
+
if (nv !== v) changed = true;
|
|
31
|
+
}
|
|
32
|
+
return (changed ? (out as unknown) : obj) as T;
|
|
33
|
+
}
|
|
34
|
+
let changed = false;
|
|
35
|
+
const result: Record<string, unknown> = {};
|
|
36
|
+
for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
|
|
37
|
+
const nk = key.includes("-") ? kebabToCamel(key) : key;
|
|
38
|
+
const nv = normalizeFrontmatterKeys(value);
|
|
39
|
+
result[nk] = nv;
|
|
40
|
+
if (nk !== key || nv !== value) changed = true;
|
|
41
|
+
}
|
|
42
|
+
return (changed ? result : obj) as T;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const PLAIN_SCALAR_KEY_VALUE = /^(\s*[A-Za-z_][\w-]*:\s+)(\S.*?)(\s*)$/;
|
|
46
|
+
const FLOW_OR_EXPLICIT_VALUE_START = new Set(['"', "'", "[", "{", "|", ">", "!", "&", "*", "#"]);
|
|
47
|
+
|
|
48
|
+
function quoteAmbiguousPlainScalars(metadata: string): string | undefined {
|
|
49
|
+
let changed = false;
|
|
50
|
+
const lines = metadata.split("\n").map(line => {
|
|
51
|
+
const match = line.match(PLAIN_SCALAR_KEY_VALUE);
|
|
52
|
+
if (!match) return line;
|
|
53
|
+
const [, prefix, rawValue, suffix] = match;
|
|
54
|
+
const value = rawValue.trimEnd();
|
|
55
|
+
if (!value.includes(": ")) return line;
|
|
56
|
+
if (FLOW_OR_EXPLICIT_VALUE_START.has(value[0])) return line;
|
|
57
|
+
changed = true;
|
|
58
|
+
return `${prefix}${JSON.stringify(value)}${suffix}`;
|
|
59
|
+
});
|
|
60
|
+
return changed ? lines.join("\n") : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseYamlRecord(metadata: string, repairTabs: boolean): Record<string, unknown> | null {
|
|
64
|
+
const loaded = YAML.parse(repairTabs ? metadata.replaceAll("\t", " ") : metadata);
|
|
65
|
+
if (loaded === null || loaded === undefined) return null;
|
|
66
|
+
if (typeof loaded !== "object" || Array.isArray(loaded)) return null;
|
|
67
|
+
return loaded as Record<string, unknown>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class FrontmatterError extends Error {
|
|
71
|
+
constructor(
|
|
72
|
+
error: Error,
|
|
73
|
+
readonly source?: unknown,
|
|
74
|
+
) {
|
|
75
|
+
super(`Failed to parse YAML frontmatter (${source}): ${error.message}`, { cause: error });
|
|
76
|
+
this.name = "FrontmatterError";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
override toString(): string {
|
|
80
|
+
// Format the error with stack and detail, including the error message, stack, and source if present
|
|
81
|
+
const details: string[] = [this.message];
|
|
82
|
+
if (this.source !== undefined) {
|
|
83
|
+
details.push(`Source: ${JSON.stringify(this.source)}`);
|
|
84
|
+
}
|
|
85
|
+
if (this.cause && typeof this.cause === "object" && "stack" in this.cause && this.cause.stack) {
|
|
86
|
+
details.push(`Stack:\n${this.cause.stack}`);
|
|
87
|
+
} else if (this.stack) {
|
|
88
|
+
details.push(`Stack:\n${this.stack}`);
|
|
89
|
+
}
|
|
90
|
+
return details.join("\n\n");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface FrontmatterOptions {
|
|
95
|
+
/** Source of the content (alias: source) */
|
|
96
|
+
location?: unknown;
|
|
97
|
+
/** Source of the content (alias for location) */
|
|
98
|
+
source?: unknown;
|
|
99
|
+
/** Fallback frontmatter values */
|
|
100
|
+
fallback?: Record<string, unknown>;
|
|
101
|
+
/** Normalize the content */
|
|
102
|
+
normalize?: boolean;
|
|
103
|
+
/** Level of error handling */
|
|
104
|
+
level?: "off" | "warn" | "fatal";
|
|
105
|
+
/**
|
|
106
|
+
* Attempt lenient recovery of near-miss input before failing: quote
|
|
107
|
+
* ambiguous plain scalars, replace tabs with spaces, and strip leading HTML
|
|
108
|
+
* comments ahead of the opening delimiter. Default `true`. Spec-conformant
|
|
109
|
+
* loaders set `false` so malformed input is rejected instead of silently
|
|
110
|
+
* repaired (CRLF newline normalization still applies).
|
|
111
|
+
*/
|
|
112
|
+
repair?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Preserve frontmatter keys verbatim instead of normalizing kebab-case to
|
|
115
|
+
* camelCase. Default `false`. Strict spec loaders use this so a standard
|
|
116
|
+
* key (e.g. `allowed-tools`) is never aliased with its camelCase form.
|
|
117
|
+
*/
|
|
118
|
+
rawKeys?: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Parse YAML frontmatter from markdown content
|
|
123
|
+
* Returns { frontmatter, body } where body has frontmatter stripped
|
|
124
|
+
*/
|
|
125
|
+
export function parseFrontmatter(
|
|
126
|
+
content: string,
|
|
127
|
+
options?: FrontmatterOptions,
|
|
128
|
+
): { frontmatter: Record<string, unknown>; body: string } {
|
|
129
|
+
const {
|
|
130
|
+
location,
|
|
131
|
+
source,
|
|
132
|
+
fallback,
|
|
133
|
+
normalize = true,
|
|
134
|
+
level = "warn",
|
|
135
|
+
repair = true,
|
|
136
|
+
rawKeys = false,
|
|
137
|
+
} = options ?? {};
|
|
138
|
+
const finalizeKeys = (fm: Record<string, unknown>): Record<string, unknown> =>
|
|
139
|
+
rawKeys ? fm : normalizeFrontmatterKeys(fm);
|
|
140
|
+
const loc = location ?? source;
|
|
141
|
+
const frontmatter: Record<string, unknown> = { ...fallback };
|
|
142
|
+
|
|
143
|
+
const newlineNormalized = normalize ? content.replace(/\r\n?/g, "\n") : content;
|
|
144
|
+
const normalized = normalize && repair ? stripHtmlComments(newlineNormalized) : newlineNormalized;
|
|
145
|
+
if (!normalized.startsWith("---")) {
|
|
146
|
+
return { frontmatter, body: normalized };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const endIndex = normalized.indexOf("\n---", 3);
|
|
150
|
+
if (endIndex === -1) {
|
|
151
|
+
return { frontmatter, body: normalized };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const metadata = normalized.slice(4, endIndex);
|
|
155
|
+
const body = normalized.slice(endIndex + 4).trim();
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const loaded = parseYamlRecord(metadata, repair);
|
|
159
|
+
return { frontmatter: finalizeKeys({ ...frontmatter, ...loaded }), body };
|
|
160
|
+
} catch (error) {
|
|
161
|
+
const quotedMetadata = repair ? quoteAmbiguousPlainScalars(metadata) : undefined;
|
|
162
|
+
if (quotedMetadata) {
|
|
163
|
+
try {
|
|
164
|
+
const loaded = parseYamlRecord(quotedMetadata, true);
|
|
165
|
+
return { frontmatter: finalizeKeys({ ...frontmatter, ...loaded }), body };
|
|
166
|
+
} catch {
|
|
167
|
+
// Fall through to the existing warning + simple key/value fallback.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const err = new FrontmatterError(
|
|
172
|
+
error instanceof Error ? error : new Error(`YAML: ${error}`),
|
|
173
|
+
loc ?? `Inline '${truncate(content, 64)}'`,
|
|
174
|
+
);
|
|
175
|
+
if (level === "warn" || level === "fatal") {
|
|
176
|
+
logger.warn("Failed to parse YAML frontmatter", { err: err.toString() });
|
|
177
|
+
}
|
|
178
|
+
if (level === "fatal") {
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Simple key: value fallback. Reparse each value on its own so one
|
|
183
|
+
// malformed line (e.g. `scope: "text","thinking"`) can't leave sibling
|
|
184
|
+
// values wrapped in literal quotes; values that don't parse as YAML fall
|
|
185
|
+
// back to the raw trimmed string (issue #4796).
|
|
186
|
+
for (const line of metadata.split("\n")) {
|
|
187
|
+
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
188
|
+
if (!match) continue;
|
|
189
|
+
const raw = match[2].trim();
|
|
190
|
+
let value: unknown = raw;
|
|
191
|
+
if (raw.length > 0) {
|
|
192
|
+
try {
|
|
193
|
+
const parsed = YAML.parse(raw);
|
|
194
|
+
if (parsed !== null && typeof parsed !== "object") value = parsed;
|
|
195
|
+
else if (Array.isArray(parsed)) value = parsed;
|
|
196
|
+
} catch {
|
|
197
|
+
// keep the raw string
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
frontmatter[match[1]] = value;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { frontmatter: finalizeKeys(frontmatter), body };
|
|
204
|
+
}
|
|
205
|
+
}
|
package/src/fs-error.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe filesystem error handling utilities.
|
|
3
|
+
*
|
|
4
|
+
* Use these to check error codes without string matching on messages:
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { isEnoent, isFsError } from "@linxiraos/pi-utils";
|
|
9
|
+
*
|
|
10
|
+
* try {
|
|
11
|
+
* return await Bun.file(path).text();
|
|
12
|
+
* } catch (err) {
|
|
13
|
+
* if (isEnoent(err)) return null;
|
|
14
|
+
* throw err;
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface FsError extends Error {
|
|
20
|
+
code: string;
|
|
21
|
+
errno?: number;
|
|
22
|
+
syscall?: string;
|
|
23
|
+
path?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isFsError(err: unknown): err is FsError {
|
|
27
|
+
return err instanceof Error && "code" in err && typeof (err as FsError).code === "string";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isEnoent(err: unknown): err is FsError {
|
|
31
|
+
return isFsError(err) && err.code === "ENOENT";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isEacces(err: unknown): err is FsError {
|
|
35
|
+
return isFsError(err) && err.code === "EACCES";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isEisdir(err: unknown): err is FsError {
|
|
39
|
+
return isFsError(err) && err.code === "EISDIR";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isEnotdir(err: unknown): err is FsError {
|
|
43
|
+
return isFsError(err) && err.code === "ENOTDIR";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function isEexist(err: unknown): err is FsError {
|
|
47
|
+
return isFsError(err) && err.code === "EEXIST";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function isEnotempty(err: unknown): err is FsError {
|
|
51
|
+
return isFsError(err) && err.code === "ENOTEMPTY";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function hasFsCode(err: unknown, code: string): err is FsError {
|
|
55
|
+
return isFsError(err) && err.code === code;
|
|
56
|
+
}
|
package/src/glob.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { Glob } from "bun";
|
|
3
|
+
import { getProjectDir } from "./dirs";
|
|
4
|
+
|
|
5
|
+
export interface GlobPathsOptions {
|
|
6
|
+
/** Base directory for glob patterns. Defaults to getProjectDir(). */
|
|
7
|
+
cwd?: string;
|
|
8
|
+
/** Glob exclusion patterns. */
|
|
9
|
+
exclude?: string[];
|
|
10
|
+
/** Abort signal to cancel the glob. */
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
/** Timeout in milliseconds for the glob operation. */
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
/** Include dotfiles when true. */
|
|
15
|
+
dot?: boolean;
|
|
16
|
+
/** Only return files (skip directories). Default: true. */
|
|
17
|
+
onlyFiles?: boolean;
|
|
18
|
+
/** Respect .gitignore files when true. Walks up directory tree to find all applicable .gitignore files. */
|
|
19
|
+
gitignore?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Patterns always excluded (.git is never useful in glob results). */
|
|
23
|
+
const ALWAYS_IGNORED = ["**/.git", "**/.git/**"];
|
|
24
|
+
|
|
25
|
+
/** node_modules exclusion patterns (skipped if pattern explicitly references node_modules). */
|
|
26
|
+
const NODE_MODULES_IGNORED = ["**/node_modules", "**/node_modules/**"];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse a single .gitignore file and return glob-compatible exclude patterns.
|
|
30
|
+
* @param content - Raw content of the .gitignore file
|
|
31
|
+
* @param gitignoreDir - Absolute path to the directory containing the .gitignore
|
|
32
|
+
* @param baseDir - Absolute path to the glob's cwd (for relativizing rooted patterns)
|
|
33
|
+
*/
|
|
34
|
+
function parseGitignorePatterns(content: string, gitignoreDir: string, baseDir: string): string[] {
|
|
35
|
+
const patterns: string[] = [];
|
|
36
|
+
|
|
37
|
+
for (const rawLine of content.split("\n")) {
|
|
38
|
+
const line = rawLine.trim();
|
|
39
|
+
// Skip empty lines and comments
|
|
40
|
+
if (!line || line.startsWith("#")) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
// Skip negation patterns (unsupported for simple exclude)
|
|
44
|
+
if (line.startsWith("!")) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let pattern = line;
|
|
49
|
+
|
|
50
|
+
// Handle trailing slash (directory-only match)
|
|
51
|
+
// For glob exclude, we treat it as matching the dir and its contents
|
|
52
|
+
const isDirectoryOnly = pattern.endsWith("/");
|
|
53
|
+
if (isDirectoryOnly) {
|
|
54
|
+
pattern = pattern.slice(0, -1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle rooted patterns (start with /)
|
|
58
|
+
if (pattern.startsWith("/")) {
|
|
59
|
+
// Rooted pattern: relative to the .gitignore location
|
|
60
|
+
const absolutePattern = path.join(gitignoreDir, pattern.slice(1));
|
|
61
|
+
const relativeToBase = path.relative(baseDir, absolutePattern);
|
|
62
|
+
if (relativeToBase.startsWith("..")) {
|
|
63
|
+
// Pattern is outside the search directory, skip
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
pattern = relativeToBase.replace(/\\/g, "/");
|
|
67
|
+
if (isDirectoryOnly) {
|
|
68
|
+
patterns.push(pattern);
|
|
69
|
+
patterns.push(`${pattern}/**`);
|
|
70
|
+
} else {
|
|
71
|
+
patterns.push(pattern);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
// Unrooted pattern: match anywhere in the tree
|
|
75
|
+
if (pattern.includes("/")) {
|
|
76
|
+
// Contains slash: match from any directory level
|
|
77
|
+
patterns.push(`**/${pattern}`);
|
|
78
|
+
if (isDirectoryOnly) {
|
|
79
|
+
patterns.push(`**/${pattern}/**`);
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
// No slash: match file/dir name anywhere
|
|
83
|
+
patterns.push(`**/${pattern}`);
|
|
84
|
+
if (isDirectoryOnly) {
|
|
85
|
+
patterns.push(`**/${pattern}/**`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return patterns;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Load .gitignore patterns from a directory and its parents.
|
|
96
|
+
* Walks up the directory tree to find all applicable .gitignore files.
|
|
97
|
+
* Returns glob-compatible exclude patterns.
|
|
98
|
+
*/
|
|
99
|
+
export async function loadGitignorePatterns(baseDir: string): Promise<string[]> {
|
|
100
|
+
const patterns: string[] = [];
|
|
101
|
+
const absoluteBase = path.resolve(baseDir);
|
|
102
|
+
|
|
103
|
+
let current = absoluteBase;
|
|
104
|
+
const maxDepth = 50; // Prevent infinite loops
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < maxDepth; i++) {
|
|
107
|
+
const gitignorePath = path.join(current, ".gitignore");
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const content = await Bun.file(gitignorePath).text();
|
|
111
|
+
const filePatterns = parseGitignorePatterns(content, current, absoluteBase);
|
|
112
|
+
patterns.push(...filePatterns);
|
|
113
|
+
} catch {
|
|
114
|
+
// .gitignore doesn't exist or can't be read, continue
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const parent = path.dirname(current);
|
|
118
|
+
if (parent === current) {
|
|
119
|
+
// Reached filesystem root
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
current = parent;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return patterns;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolve filesystem paths matching glob patterns with optional exclude filters.
|
|
130
|
+
* Returns paths relative to the provided cwd (or getProjectDir()).
|
|
131
|
+
* Errors and abort/timeouts are surfaced to the caller.
|
|
132
|
+
*/
|
|
133
|
+
export async function globPaths(patterns: string | string[], options: GlobPathsOptions = {}): Promise<string[]> {
|
|
134
|
+
const { cwd, exclude, signal, timeoutMs, dot, onlyFiles = true, gitignore } = options;
|
|
135
|
+
|
|
136
|
+
// Build exclude list: always exclude .git, exclude node_modules unless pattern references it
|
|
137
|
+
const patternArray = Array.isArray(patterns) ? patterns : [patterns];
|
|
138
|
+
const mentionsNodeModules = patternArray.some(p => p.includes("node_modules"));
|
|
139
|
+
|
|
140
|
+
const baseExclude = mentionsNodeModules ? [...ALWAYS_IGNORED] : [...ALWAYS_IGNORED, ...NODE_MODULES_IGNORED];
|
|
141
|
+
let effectiveExclude = exclude ? [...baseExclude, ...exclude] : baseExclude;
|
|
142
|
+
|
|
143
|
+
if (gitignore) {
|
|
144
|
+
const gitignorePatterns = await loadGitignorePatterns(cwd ?? getProjectDir());
|
|
145
|
+
effectiveExclude = [...effectiveExclude, ...gitignorePatterns];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const base = cwd ?? getProjectDir();
|
|
149
|
+
const allResults: string[] = [];
|
|
150
|
+
|
|
151
|
+
// Combine timeout and abort signals
|
|
152
|
+
const timeoutSignal = timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined;
|
|
153
|
+
const combinedSignal =
|
|
154
|
+
signal && timeoutSignal ? AbortSignal.any([signal, timeoutSignal]) : (signal ?? timeoutSignal);
|
|
155
|
+
|
|
156
|
+
for (const pattern of patternArray) {
|
|
157
|
+
const glob = new Glob(pattern);
|
|
158
|
+
const scanOptions = {
|
|
159
|
+
cwd: base,
|
|
160
|
+
dot,
|
|
161
|
+
onlyFiles,
|
|
162
|
+
throwErrorOnBrokenSymlink: false,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
for await (const entry of glob.scan(scanOptions)) {
|
|
166
|
+
if (combinedSignal?.aborted) {
|
|
167
|
+
const reason = combinedSignal.reason;
|
|
168
|
+
if (reason instanceof Error) throw reason;
|
|
169
|
+
throw new DOMException("Aborted", "AbortError");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check exclusion patterns
|
|
173
|
+
const normalized = entry.replace(/\\/g, "/");
|
|
174
|
+
let excluded = false;
|
|
175
|
+
for (const excludePattern of effectiveExclude) {
|
|
176
|
+
const excludeGlob = new Glob(excludePattern);
|
|
177
|
+
if (excludeGlob.match(normalized)) {
|
|
178
|
+
excluded = true;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!excluded) {
|
|
183
|
+
allResults.push(normalized);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return allResults;
|
|
189
|
+
}
|
package/src/headers.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/** Behavior-compatible reimplementation of header-generator's used surface. */
|
|
2
|
+
|
|
3
|
+
/** A browser family supported by the curated header profiles. */
|
|
4
|
+
export type BrowserName = "chrome" | "firefox" | "safari";
|
|
5
|
+
|
|
6
|
+
/** A desktop operating system supported by the curated header profiles. */
|
|
7
|
+
export type OperatingSystem = "windows" | "macos" | "linux";
|
|
8
|
+
|
|
9
|
+
/** Constructor and per-call constraints for header generation. */
|
|
10
|
+
export interface HeaderGeneratorOptions {
|
|
11
|
+
/** Browser families eligible for a draw. */
|
|
12
|
+
browsers: BrowserName[];
|
|
13
|
+
/** Browser selection query; the supported `last 3 versions` query uses the curated versions. */
|
|
14
|
+
browserListQuery: string;
|
|
15
|
+
/** Desktop operating systems eligible for a draw. */
|
|
16
|
+
operatingSystems: OperatingSystem[];
|
|
17
|
+
/** Device classes eligible for a draw. */
|
|
18
|
+
devices: "desktop"[];
|
|
19
|
+
/** Ordered locales for the Accept-Language value. */
|
|
20
|
+
locales: string[];
|
|
21
|
+
/** HTTP protocol generation mode. */
|
|
22
|
+
httpVersion: "1" | "2";
|
|
23
|
+
/** Whether impossible constraints throw instead of relaxing to a coherent profile. */
|
|
24
|
+
strict: boolean;
|
|
25
|
+
/** Random source returning a value in the range from zero (inclusive) to one (exclusive). */
|
|
26
|
+
rng: () => number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A generated HTTP request header map. */
|
|
30
|
+
export type Headers = Record<string, string>;
|
|
31
|
+
|
|
32
|
+
type ResolvedOptions = Omit<HeaderGeneratorOptions, "rng">;
|
|
33
|
+
|
|
34
|
+
type BrowserProfile = {
|
|
35
|
+
browser: BrowserName;
|
|
36
|
+
operatingSystem: OperatingSystem;
|
|
37
|
+
version: number;
|
|
38
|
+
userAgent: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const DEFAULT_OPTIONS: ResolvedOptions = {
|
|
42
|
+
browsers: ["chrome", "firefox", "safari"],
|
|
43
|
+
browserListQuery: "",
|
|
44
|
+
operatingSystems: ["windows", "macos", "linux"],
|
|
45
|
+
devices: ["desktop"],
|
|
46
|
+
locales: ["en-US", "en"],
|
|
47
|
+
httpVersion: "2",
|
|
48
|
+
strict: false,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const VERSIONS: Readonly<Record<BrowserName, readonly number[]>> = {
|
|
52
|
+
chrome: [149, 150, 151],
|
|
53
|
+
firefox: [147, 148, 149],
|
|
54
|
+
safari: [26, 26.1, 26.2],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function pick<T>(values: readonly T[], rng: () => number): T {
|
|
58
|
+
const random = rng();
|
|
59
|
+
const index = Math.min(values.length - 1, Math.max(0, Math.floor(random * values.length)));
|
|
60
|
+
const value = values[index];
|
|
61
|
+
if (value === undefined) throw new Error("Cannot choose from an empty header profile list");
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function formatLocales(locales: readonly string[]): string {
|
|
66
|
+
return locales
|
|
67
|
+
.slice(0, 10)
|
|
68
|
+
.map((locale, index) => {
|
|
69
|
+
if (index === 0) return locale;
|
|
70
|
+
const quality = Math.max(0.1, 1 - index / 10).toFixed(1);
|
|
71
|
+
return `${locale};q=${quality}`;
|
|
72
|
+
})
|
|
73
|
+
.join(",");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function makeUserAgent(browser: BrowserName, operatingSystem: OperatingSystem, version: number): string {
|
|
77
|
+
if (browser === "safari") {
|
|
78
|
+
return `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/${version.toFixed(1)} Safari/605.1.15`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const platform =
|
|
82
|
+
operatingSystem === "windows"
|
|
83
|
+
? "Windows NT 10.0; Win64; x64"
|
|
84
|
+
: operatingSystem === "macos"
|
|
85
|
+
? "Macintosh; Intel Mac OS X 10_15_7"
|
|
86
|
+
: "X11; Linux x86_64";
|
|
87
|
+
if (browser === "firefox") {
|
|
88
|
+
return `Mozilla/5.0 (${platform}; rv:${version}.0) Gecko/20100101 Firefox/${version}.0`;
|
|
89
|
+
}
|
|
90
|
+
return `Mozilla/5.0 (${platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${version}.0.0.0 Safari/537.36`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function makeProfile(options: ResolvedOptions, rng: () => number): BrowserProfile {
|
|
94
|
+
const candidates: Array<{ browser: BrowserName; operatingSystem: OperatingSystem }> = [];
|
|
95
|
+
for (const browser of options.browsers) {
|
|
96
|
+
for (const operatingSystem of options.operatingSystems) {
|
|
97
|
+
if (browser === "safari" && operatingSystem !== "macos") continue;
|
|
98
|
+
candidates.push({ browser, operatingSystem });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (candidates.length === 0) {
|
|
103
|
+
if (options.strict) throw new Error("No coherent browser profile matches the requested options");
|
|
104
|
+
candidates.push({ browser: "chrome", operatingSystem: "windows" });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const candidate = pick(candidates, rng);
|
|
108
|
+
const version = pick(VERSIONS[candidate.browser], rng);
|
|
109
|
+
return {
|
|
110
|
+
...candidate,
|
|
111
|
+
version,
|
|
112
|
+
userAgent: makeUserAgent(candidate.browser, candidate.operatingSystem, version),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Generates coherent modern desktop browser navigation headers. */
|
|
117
|
+
export class HeaderGenerator {
|
|
118
|
+
#options: ResolvedOptions;
|
|
119
|
+
#rng: () => number;
|
|
120
|
+
|
|
121
|
+
/** Creates a generator with reusable constraints and an optionally injectable RNG. */
|
|
122
|
+
constructor(options: Partial<HeaderGeneratorOptions> = {}) {
|
|
123
|
+
this.#rng = options.rng ?? Math.random;
|
|
124
|
+
this.#options = { ...DEFAULT_OPTIONS, ...options };
|
|
125
|
+
if (this.#options.devices.some(device => device !== "desktop") && this.#options.strict) {
|
|
126
|
+
throw new Error("Only desktop browser profiles are available");
|
|
127
|
+
}
|
|
128
|
+
if (this.#options.locales.length === 0 && this.#options.strict) {
|
|
129
|
+
throw new Error("At least one locale is required");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Generates one header set, applying per-call constraints and request overrides. */
|
|
134
|
+
getHeaders(options: Partial<HeaderGeneratorOptions> = {}, overrides: Headers = {}): Headers {
|
|
135
|
+
const resolved = { ...this.#options, ...options };
|
|
136
|
+
const rng = options.rng ?? this.#rng;
|
|
137
|
+
const profile = makeProfile(resolved, rng);
|
|
138
|
+
const headers: Headers = {
|
|
139
|
+
accept:
|
|
140
|
+
profile.browser === "chrome"
|
|
141
|
+
? "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
|
|
142
|
+
: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
143
|
+
"user-agent": profile.userAgent,
|
|
144
|
+
"accept-encoding": profile.browser === "safari" ? "gzip, deflate, br" : "gzip, deflate, br, zstd",
|
|
145
|
+
"accept-language": formatLocales(resolved.locales.length > 0 ? resolved.locales : DEFAULT_OPTIONS.locales),
|
|
146
|
+
"upgrade-insecure-requests": "1",
|
|
147
|
+
"sec-fetch-dest": "document",
|
|
148
|
+
"sec-fetch-mode": "navigate",
|
|
149
|
+
"sec-fetch-site": "none",
|
|
150
|
+
"sec-fetch-user": "?1",
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
if (profile.browser === "chrome") {
|
|
154
|
+
headers["sec-ch-ua"] =
|
|
155
|
+
`"Google Chrome";v="${profile.version}", "Chromium";v="${profile.version}", "Not_A Brand";v="24"`;
|
|
156
|
+
headers["sec-ch-ua-mobile"] = "?0";
|
|
157
|
+
headers["sec-ch-ua-platform"] =
|
|
158
|
+
profile.operatingSystem === "windows"
|
|
159
|
+
? '"Windows"'
|
|
160
|
+
: profile.operatingSystem === "macos"
|
|
161
|
+
? '"macOS"'
|
|
162
|
+
: '"Linux"';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { ...headers, ...overrides };
|
|
166
|
+
}
|
|
167
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export { once, untilAborted } from "./abortable";
|
|
2
|
+
export * from "./async";
|
|
3
|
+
export * from "./binary";
|
|
4
|
+
export * from "./color";
|
|
5
|
+
export * from "./dirs";
|
|
6
|
+
export * from "./env";
|
|
7
|
+
export * from "./fetch-retry";
|
|
8
|
+
export * from "./file-lock";
|
|
9
|
+
export * from "./format";
|
|
10
|
+
export * from "./frontmatter";
|
|
11
|
+
export * from "./fs-error";
|
|
12
|
+
export * from "./glob";
|
|
13
|
+
export * from "./json";
|
|
14
|
+
export * from "./json-parse";
|
|
15
|
+
export * as logger from "./logger";
|
|
16
|
+
export * from "./loop-phase";
|
|
17
|
+
export * from "./mermaid-ascii";
|
|
18
|
+
export * from "./mime";
|
|
19
|
+
export * from "./path";
|
|
20
|
+
export * from "./path-tree";
|
|
21
|
+
export * from "./peek-file";
|
|
22
|
+
export * as postmortem from "./postmortem";
|
|
23
|
+
export * from "./process-name";
|
|
24
|
+
export * as procmgr from "./procmgr";
|
|
25
|
+
export * as prompt from "./prompt";
|
|
26
|
+
export * as ptree from "./ptree";
|
|
27
|
+
export { AbortError, ChildProcess, Exception, NonZeroExitError } from "./ptree";
|
|
28
|
+
export * from "./runtime-install";
|
|
29
|
+
export * from "./sanitize-text";
|
|
30
|
+
export * from "./snowflake";
|
|
31
|
+
export * from "./stderr-guard";
|
|
32
|
+
export * from "./stream";
|
|
33
|
+
export * from "./tab-spacing";
|
|
34
|
+
export * from "./temp";
|
|
35
|
+
export * from "./tls-fetch";
|
|
36
|
+
export * from "./type-guards";
|
|
37
|
+
export * from "./version";
|
|
38
|
+
export * from "./which";
|
|
39
|
+
|
|
40
|
+
function isPlainObject(val: object): val is Record<string, unknown> {
|
|
41
|
+
return Object.getPrototypeOf(val) === Object.prototype || Array.isArray(val);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function structuredCloneJSON<T>(value: T): T {
|
|
45
|
+
// primitives|null|undefined, copy
|
|
46
|
+
if (!value || typeof value !== "object") {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// deep clone
|
|
51
|
+
if (isPlainObject(value)) {
|
|
52
|
+
try {
|
|
53
|
+
return structuredClone(value);
|
|
54
|
+
} catch {
|
|
55
|
+
// might still fail due to nested structures
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
59
|
+
}
|