@hraness/kb 0.17.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/DISCLOSURE +43 -0
- package/LICENSE +21 -0
- package/README.md +545 -0
- package/dist/agent-context.js +35 -0
- package/dist/agent-guide-audit.js +18 -0
- package/dist/attachments.js +19 -0
- package/dist/authoring.js +33 -0
- package/dist/benchmark.js +19 -0
- package/dist/capture.js +49 -0
- package/dist/cli.js +3646 -0
- package/dist/clip/acquire.js +57 -0
- package/dist/clip/args.js +21 -0
- package/dist/clip/bounded-byte-buffer.js +9 -0
- package/dist/clip/browser-profiles.js +17 -0
- package/dist/clip/bundle-reader.js +11 -0
- package/dist/clip/cli.js +27 -0
- package/dist/clip/cookies.js +21 -0
- package/dist/clip/defuddle-worker.js +84 -0
- package/dist/clip/doctor.js +32 -0
- package/dist/clip/jobs.js +784 -0
- package/dist/clip/network-proxy.js +9 -0
- package/dist/clip/network.js +28 -0
- package/dist/clip/persist.js +30 -0
- package/dist/clip/refresh.js +11 -0
- package/dist/clip/terminal.js +11 -0
- package/dist/evaluation-builder.js +12238 -0
- package/dist/evaluation-kb.js +26 -0
- package/dist/evaluation.js +35 -0
- package/dist/git.js +39 -0
- package/dist/graph.js +39 -0
- package/dist/index-01jj6rbv.js +1103 -0
- package/dist/index-06c9ctr6.js +738 -0
- package/dist/index-0kavxzqj.js +2480 -0
- package/dist/index-1gwbassd.js +743 -0
- package/dist/index-1n418kb9.js +827 -0
- package/dist/index-1vrd1rmn.js +56 -0
- package/dist/index-1xxnjn0d.js +171 -0
- package/dist/index-3rm7cz6h.js +373 -0
- package/dist/index-3v2z4f0q.js +328 -0
- package/dist/index-48pz4jpc.js +392 -0
- package/dist/index-4j3tt0c3.js +234 -0
- package/dist/index-5n05se68.js +307 -0
- package/dist/index-5vwpzb5a.js +764 -0
- package/dist/index-6g2pv9d2.js +370 -0
- package/dist/index-7fzc32gf.js +712 -0
- package/dist/index-84x0vjjp.js +432 -0
- package/dist/index-adx6khj5.js +488 -0
- package/dist/index-b88v3vtm.js +805 -0
- package/dist/index-cv6fh7z5.js +462 -0
- package/dist/index-cxfrakt7.js +1067 -0
- package/dist/index-d13v9ckt.js +150 -0
- package/dist/index-dycjbhw2.js +1943 -0
- package/dist/index-dyqwejk5.js +531 -0
- package/dist/index-e5fbsywq.js +658 -0
- package/dist/index-f984hw45.js +927 -0
- package/dist/index-gh719d91.js +63 -0
- package/dist/index-hgve9rh2.js +2046 -0
- package/dist/index-hya40gb2.js +474 -0
- package/dist/index-j4zgmzjr.js +152 -0
- package/dist/index-jsmvyyvf.js +1166 -0
- package/dist/index-kh0pwvdd.js +1974 -0
- package/dist/index-mqx4nd6v.js +119 -0
- package/dist/index-mxxxytys.js +1254 -0
- package/dist/index-n5dd7r0v.js +711 -0
- package/dist/index-npg9z1a4.js +431 -0
- package/dist/index-pj501bh1.js +140 -0
- package/dist/index-qry4vhxk.js +1041 -0
- package/dist/index-s2gw5aw9.js +199 -0
- package/dist/index-vvdj6kca.js +70 -0
- package/dist/index-w2zc0vwa.js +571 -0
- package/dist/index-x3fthpsc.js +585 -0
- package/dist/index-zr53sf63.js +44 -0
- package/dist/index-zxdy5pby.js +2728 -0
- package/dist/index-zzhgcwyt.js +966 -0
- package/dist/index.js +453 -0
- package/dist/navigation.js +13 -0
- package/dist/pdf.js +85 -0
- package/dist/percolate.js +24 -0
- package/dist/portfolio.js +77 -0
- package/dist/query.js +29 -0
- package/dist/repository-memory.js +60 -0
- package/dist/sdk.js +36 -0
- package/dist/search-rules.js +34 -0
- package/dist/search.js +22 -0
- package/dist/semantic.js +39 -0
- package/dist/source-inbox.js +17 -0
- package/dist/untrusted-content.js +32 -0
- package/dist/url-intelligence.js +74 -0
- package/dist/workflow.js +23 -0
- package/dist/workflows/decision-context.js +20 -0
- package/dist/workflows/explain-change.js +8 -0
- package/dist/workflows/index.js +28 -0
- package/dist/workflows/plan-radar.js +10 -0
- package/package.json +409 -0
- package/skills/kb/AGENTS.md +19 -0
- package/skills/kb/SKILL.md +82 -0
- package/skills/kb/agents/openai.yaml +4 -0
- package/skills/kb/references/pdf-review.md +51 -0
- package/skills/kb/references/percolate.md +144 -0
- package/skills/kb/references/plan-structure.md +105 -0
- package/skills/kb/references/plan.md +115 -0
- package/skills/kb/references/query.md +231 -0
- package/skills/kb/references/refresh.md +133 -0
- package/skills/kb/references/save-pdf.md +125 -0
- package/skills/kb/references/save-url.md +178 -0
- package/skills/kb/references/url-authentication.md +75 -0
- package/skills/kb/references/url-platforms.md +25 -0
- package/src/agent-context.ts +1241 -0
- package/src/agent-guide-audit.ts +750 -0
- package/src/attachments.ts +723 -0
- package/src/authoring.ts +1725 -0
- package/src/benchmark.ts +362 -0
- package/src/capture.ts +68 -0
- package/src/cli.ts +3660 -0
- package/src/clip/acquire.ts +1273 -0
- package/src/clip/archive-today.ts +402 -0
- package/src/clip/args.ts +423 -0
- package/src/clip/assets.ts +262 -0
- package/src/clip/bounded-byte-buffer.ts +75 -0
- package/src/clip/browser-profiles.ts +372 -0
- package/src/clip/bundle-reader.ts +608 -0
- package/src/clip/capture.ts +1054 -0
- package/src/clip/cli.ts +181 -0
- package/src/clip/cookies.ts +512 -0
- package/src/clip/doctor.ts +1110 -0
- package/src/clip/extract.ts +868 -0
- package/src/clip/jobs.ts +959 -0
- package/src/clip/lib.ts +861 -0
- package/src/clip/media.ts +1474 -0
- package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
- package/src/clip/metadata-search-tool/Cargo.toml +18 -0
- package/src/clip/metadata-search-tool/runner.ts +685 -0
- package/src/clip/metadata-search-tool/src/main.rs +389 -0
- package/src/clip/metadata-search.ts +687 -0
- package/src/clip/network-proxy.ts +627 -0
- package/src/clip/network.ts +950 -0
- package/src/clip/package-root.ts +54 -0
- package/src/clip/persist.ts +1472 -0
- package/src/clip/platforms.ts +1824 -0
- package/src/clip/refresh.ts +197 -0
- package/src/clip/structured.ts +432 -0
- package/src/clip/terminal.ts +197 -0
- package/src/clip/url-intelligence.ts +581 -0
- package/src/clip/url-metadata-backfill.ts +616 -0
- package/src/clip/url-metadata-cli.ts +251 -0
- package/src/clip/url-metadata.ts +1397 -0
- package/src/evaluation-analysis-v2.ts +1440 -0
- package/src/evaluation-builder.ts +2539 -0
- package/src/evaluation-corpus-authoring.ts +2052 -0
- package/src/evaluation-evidence.ts +1147 -0
- package/src/evaluation-execution-v2.ts +426 -0
- package/src/evaluation-implementation.ts +131 -0
- package/src/evaluation-kb-closure.ts +1430 -0
- package/src/evaluation-kb-runner-v2.ts +735 -0
- package/src/evaluation-kb-v2.ts +2567 -0
- package/src/evaluation-kb.ts +1018 -0
- package/src/evaluation-measurement-v2.ts +1462 -0
- package/src/evaluation-packing-v2.ts +452 -0
- package/src/evaluation-redaction.ts +28 -0
- package/src/evaluation-v2.ts +3857 -0
- package/src/evaluation.ts +1220 -0
- package/src/git.ts +1276 -0
- package/src/graph.ts +1487 -0
- package/src/index.ts +20 -0
- package/src/init.ts +123 -0
- package/src/navigation.ts +277 -0
- package/src/note-lock.ts +529 -0
- package/src/pdf/args.ts +202 -0
- package/src/pdf/capture.ts +298 -0
- package/src/pdf/cli.ts +159 -0
- package/src/pdf/extract.ts +303 -0
- package/src/pdf/layout.ts +618 -0
- package/src/pdf/markdown.ts +154 -0
- package/src/pdf/model.ts +234 -0
- package/src/pdf/ocr.ts +190 -0
- package/src/pdf/persist.ts +314 -0
- package/src/pdf/source.ts +135 -0
- package/src/pdf/tools.ts +146 -0
- package/src/pdf.ts +92 -0
- package/src/percolate.ts +877 -0
- package/src/portfolio-audit.ts +574 -0
- package/src/portfolio-identity.ts +162 -0
- package/src/portfolio-registry.ts +473 -0
- package/src/portfolio.ts +765 -0
- package/src/query.ts +646 -0
- package/src/repository-memory.ts +1150 -0
- package/src/sdk.ts +1452 -0
- package/src/search-rules.ts +712 -0
- package/src/search.ts +710 -0
- package/src/semantic-runtime.ts +1139 -0
- package/src/semantic.ts +2480 -0
- package/src/source-inbox.ts +232 -0
- package/src/untrusted-content.ts +401 -0
- package/src/url-intelligence.ts +104 -0
- package/src/vault.ts +636 -0
- package/src/workflow.ts +653 -0
- package/src/workflows/decision-context.ts +81 -0
- package/src/workflows/explain-change.ts +73 -0
- package/src/workflows/index.ts +3 -0
- package/src/workflows/plan-radar.ts +101 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "kb-url-metadata-search"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2024"
|
|
5
|
+
publish = false
|
|
6
|
+
|
|
7
|
+
[dependencies]
|
|
8
|
+
metadata-search-engine-rs = { git = "https://github.com/MikeLuu99/searxng-rust", rev = "f40a00ea67a857ee996e1caba1ebab3ee7a14a47" }
|
|
9
|
+
libc = "=0.2.189"
|
|
10
|
+
reqwest = { version = "=0.12.28", features = ["brotli", "cookies", "gzip"] }
|
|
11
|
+
serde = { version = "1", features = ["derive"] }
|
|
12
|
+
serde_json = "1"
|
|
13
|
+
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
|
14
|
+
|
|
15
|
+
[profile.release]
|
|
16
|
+
codegen-units = 1
|
|
17
|
+
lto = "thin"
|
|
18
|
+
strip = true
|
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
constants,
|
|
5
|
+
fchmodSync,
|
|
6
|
+
fstatSync,
|
|
7
|
+
fsyncSync,
|
|
8
|
+
lstatSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
mkdtempSync,
|
|
11
|
+
openSync,
|
|
12
|
+
readSync,
|
|
13
|
+
renameSync,
|
|
14
|
+
rmSync,
|
|
15
|
+
type BigIntStats,
|
|
16
|
+
writeSync,
|
|
17
|
+
} from "node:fs";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
20
|
+
|
|
21
|
+
export type MetadataSearchToolAction = "build" | "check";
|
|
22
|
+
|
|
23
|
+
const TEMPORARY_DIRECTORY_PREFIX = "hraness-kb-metadata-search-tool-";
|
|
24
|
+
const MAX_EXECUTABLE_BYTES = 64 * 1024 * 1024;
|
|
25
|
+
const COPY_BUFFER_BYTES = 64 * 1024;
|
|
26
|
+
|
|
27
|
+
type DirectoryBinding = {
|
|
28
|
+
readonly path: string;
|
|
29
|
+
readonly descriptor: number;
|
|
30
|
+
readonly identity: BigIntStats;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type EnsuredDirectory = {
|
|
34
|
+
readonly binding: DirectoryBinding;
|
|
35
|
+
readonly created: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type MetadataSearchDirectorySyncPhase =
|
|
39
|
+
| "tool-after-target"
|
|
40
|
+
| "target-after-release"
|
|
41
|
+
| "tool-before-commit"
|
|
42
|
+
| "release-before-commit"
|
|
43
|
+
| "release-after-commit"
|
|
44
|
+
| "tool-after-commit"
|
|
45
|
+
| "tool-after-backup-removal"
|
|
46
|
+
| "release-after-rollback"
|
|
47
|
+
| "tool-after-rollback";
|
|
48
|
+
|
|
49
|
+
export type MetadataSearchToolRunnerDependencies = {
|
|
50
|
+
readonly toolDirectory?: string;
|
|
51
|
+
readonly platform?: NodeJS.Platform;
|
|
52
|
+
readonly createTemporaryDirectory?: () => string;
|
|
53
|
+
readonly removeTemporaryDirectory?: (
|
|
54
|
+
path: string,
|
|
55
|
+
identity: BigIntStats,
|
|
56
|
+
) => void;
|
|
57
|
+
readonly runCargo?: (
|
|
58
|
+
command: readonly string[],
|
|
59
|
+
options: { readonly cwd: string; readonly environment: NodeJS.ProcessEnv },
|
|
60
|
+
) => number;
|
|
61
|
+
readonly beforeInstall?: (input: {
|
|
62
|
+
readonly sourcePath: string;
|
|
63
|
+
readonly targetDirectory: string;
|
|
64
|
+
readonly releaseDirectory: string;
|
|
65
|
+
readonly destinationPath: string;
|
|
66
|
+
}) => void;
|
|
67
|
+
readonly beforeCommit?: (input: {
|
|
68
|
+
readonly sourcePath: string;
|
|
69
|
+
readonly targetDirectory: string;
|
|
70
|
+
readonly releaseDirectory: string;
|
|
71
|
+
readonly destinationPath: string;
|
|
72
|
+
}) => void;
|
|
73
|
+
readonly syncDirectory?: (
|
|
74
|
+
descriptor: number,
|
|
75
|
+
phase: MetadataSearchDirectorySyncPhase,
|
|
76
|
+
) => void;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function noFollowFlag(): number {
|
|
80
|
+
return typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function directoryFlag(): number {
|
|
84
|
+
return typeof constants.O_DIRECTORY === "number" ? constants.O_DIRECTORY : 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function errorCode(error: unknown): string | undefined {
|
|
88
|
+
return typeof error === "object"
|
|
89
|
+
&& error !== null
|
|
90
|
+
&& "code" in error
|
|
91
|
+
&& typeof error.code === "string"
|
|
92
|
+
? error.code
|
|
93
|
+
: undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function maybeLstat(path: string): BigIntStats | null {
|
|
97
|
+
try {
|
|
98
|
+
return lstatSync(path, { bigint: true });
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (errorCode(error) === "ENOENT") return null;
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function sameDirectoryIdentity(left: BigIntStats, right: BigIntStats): boolean {
|
|
106
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function sameFileIdentity(left: BigIntStats, right: BigIntStats): boolean {
|
|
110
|
+
return sameDirectoryIdentity(left, right)
|
|
111
|
+
&& left.size === right.size
|
|
112
|
+
&& left.mtimeNs === right.mtimeNs
|
|
113
|
+
&& left.ctimeNs === right.ctimeNs;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function sameInstalledFile(left: BigIntStats, right: BigIntStats): boolean {
|
|
117
|
+
return sameDirectoryIdentity(left, right)
|
|
118
|
+
&& left.size === right.size
|
|
119
|
+
&& left.mtimeNs === right.mtimeNs;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function assertOwnedByCurrentUser(stat: BigIntStats, label: string): void {
|
|
123
|
+
if (process.platform === "win32" || typeof process.getuid !== "function") return;
|
|
124
|
+
if (stat.uid !== BigInt(process.getuid())) {
|
|
125
|
+
throw new Error(`${label} must be owned by the current user`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function assertDirectory(stat: BigIntStats | null, label: string): asserts stat is BigIntStats {
|
|
130
|
+
if (stat === null || stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
131
|
+
throw new Error(`${label} must be a real directory`);
|
|
132
|
+
}
|
|
133
|
+
assertOwnedByCurrentUser(stat, label);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function openBoundDirectory(path: string, label: string): DirectoryBinding {
|
|
137
|
+
const before = maybeLstat(path);
|
|
138
|
+
assertDirectory(before, label);
|
|
139
|
+
const descriptor = openSync(
|
|
140
|
+
path,
|
|
141
|
+
constants.O_RDONLY | directoryFlag() | noFollowFlag(),
|
|
142
|
+
);
|
|
143
|
+
try {
|
|
144
|
+
const opened = fstatSync(descriptor, { bigint: true });
|
|
145
|
+
if (!opened.isDirectory() || !sameDirectoryIdentity(before, opened)) {
|
|
146
|
+
throw new Error(`${label} changed while it was opened`);
|
|
147
|
+
}
|
|
148
|
+
return { path, descriptor, identity: opened };
|
|
149
|
+
} catch (error) {
|
|
150
|
+
closeSync(descriptor);
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function assertDirectoryBinding(binding: DirectoryBinding, label: string): void {
|
|
156
|
+
const byPath = maybeLstat(binding.path);
|
|
157
|
+
assertDirectory(byPath, label);
|
|
158
|
+
const byDescriptor = fstatSync(binding.descriptor, { bigint: true });
|
|
159
|
+
if (
|
|
160
|
+
!sameDirectoryIdentity(binding.identity, byPath)
|
|
161
|
+
|| !sameDirectoryIdentity(binding.identity, byDescriptor)
|
|
162
|
+
) {
|
|
163
|
+
throw new Error(`${label} changed during installation`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function ensurePrivateDirectory(path: string, label: string): EnsuredDirectory {
|
|
168
|
+
let created = false;
|
|
169
|
+
try {
|
|
170
|
+
mkdirSync(path, { mode: 0o700 });
|
|
171
|
+
created = true;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (errorCode(error) !== "EEXIST") throw error;
|
|
174
|
+
}
|
|
175
|
+
const before = maybeLstat(path);
|
|
176
|
+
assertDirectory(before, label);
|
|
177
|
+
const binding = openBoundDirectory(path, label);
|
|
178
|
+
try {
|
|
179
|
+
if (process.platform !== "win32") {
|
|
180
|
+
fchmodSync(binding.descriptor, 0o700);
|
|
181
|
+
}
|
|
182
|
+
const identity = fstatSync(binding.descriptor, { bigint: true });
|
|
183
|
+
return { binding: { ...binding, identity }, created };
|
|
184
|
+
} catch (error) {
|
|
185
|
+
closeSync(binding.descriptor);
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function assertExecutableSource(
|
|
191
|
+
stat: BigIntStats,
|
|
192
|
+
platform: NodeJS.Platform,
|
|
193
|
+
): void {
|
|
194
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
195
|
+
throw new Error("Cargo release output must be a regular file");
|
|
196
|
+
}
|
|
197
|
+
if (stat.nlink !== 1n) {
|
|
198
|
+
throw new Error("Cargo release output must not be hard-linked");
|
|
199
|
+
}
|
|
200
|
+
if (stat.size < 1n || stat.size > BigInt(MAX_EXECUTABLE_BYTES)) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Cargo release output must contain 1-${MAX_EXECUTABLE_BYTES} bytes`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
if (platform !== "win32" && (stat.mode & 0o111n) === 0n) {
|
|
206
|
+
throw new Error("Cargo release output must be executable");
|
|
207
|
+
}
|
|
208
|
+
assertOwnedByCurrentUser(stat, "Cargo release output");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function assertExistingDestination(
|
|
212
|
+
stat: BigIntStats,
|
|
213
|
+
platform: NodeJS.Platform,
|
|
214
|
+
): void {
|
|
215
|
+
assertExecutableSource(stat, platform);
|
|
216
|
+
if (platform !== "win32" && (stat.mode & 0o777n) !== 0o700n) {
|
|
217
|
+
throw new Error("installed metadata-search executable has unsafe permissions");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function writeAll(descriptor: number, buffer: Buffer, length: number): void {
|
|
222
|
+
let offset = 0;
|
|
223
|
+
while (offset < length) {
|
|
224
|
+
const written = writeSync(descriptor, buffer, offset, length - offset);
|
|
225
|
+
if (written < 1) throw new Error("could not write the staged executable");
|
|
226
|
+
offset += written;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function copyValidatedExecutable(
|
|
231
|
+
sourcePath: string,
|
|
232
|
+
stagingPath: string,
|
|
233
|
+
platform: NodeJS.Platform,
|
|
234
|
+
): BigIntStats {
|
|
235
|
+
const sourceDescriptor = openSync(
|
|
236
|
+
sourcePath,
|
|
237
|
+
constants.O_RDONLY | noFollowFlag(),
|
|
238
|
+
);
|
|
239
|
+
let stagingDescriptor: number | null = null;
|
|
240
|
+
let staged: BigIntStats | null = null;
|
|
241
|
+
let complete = false;
|
|
242
|
+
try {
|
|
243
|
+
const sourceBefore = fstatSync(sourceDescriptor, { bigint: true });
|
|
244
|
+
assertExecutableSource(sourceBefore, platform);
|
|
245
|
+
stagingDescriptor = openSync(
|
|
246
|
+
stagingPath,
|
|
247
|
+
constants.O_CREAT
|
|
248
|
+
| constants.O_EXCL
|
|
249
|
+
| constants.O_WRONLY
|
|
250
|
+
| noFollowFlag(),
|
|
251
|
+
0o700,
|
|
252
|
+
);
|
|
253
|
+
if (platform !== "win32") fchmodSync(stagingDescriptor, 0o700);
|
|
254
|
+
|
|
255
|
+
const buffer = Buffer.allocUnsafe(COPY_BUFFER_BYTES);
|
|
256
|
+
let copiedBytes = 0;
|
|
257
|
+
const expectedBytes = Number(sourceBefore.size);
|
|
258
|
+
while (copiedBytes < expectedBytes) {
|
|
259
|
+
const bytesRead = readSync(
|
|
260
|
+
sourceDescriptor,
|
|
261
|
+
buffer,
|
|
262
|
+
0,
|
|
263
|
+
Math.min(buffer.byteLength, expectedBytes - copiedBytes),
|
|
264
|
+
null,
|
|
265
|
+
);
|
|
266
|
+
if (bytesRead < 1) {
|
|
267
|
+
throw new Error("Cargo release output changed while it was copied");
|
|
268
|
+
}
|
|
269
|
+
writeAll(stagingDescriptor, buffer, bytesRead);
|
|
270
|
+
copiedBytes += bytesRead;
|
|
271
|
+
}
|
|
272
|
+
if (readSync(sourceDescriptor, buffer, 0, 1, null) !== 0) {
|
|
273
|
+
throw new Error("Cargo release output grew while it was copied");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const sourceAfter = fstatSync(sourceDescriptor, { bigint: true });
|
|
277
|
+
if (!sameFileIdentity(sourceBefore, sourceAfter)) {
|
|
278
|
+
throw new Error("Cargo release output changed while it was copied");
|
|
279
|
+
}
|
|
280
|
+
const sourceByPath = maybeLstat(sourcePath);
|
|
281
|
+
if (sourceByPath === null || !sameFileIdentity(sourceBefore, sourceByPath)) {
|
|
282
|
+
throw new Error("Cargo release output path changed while it was copied");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fsyncSync(stagingDescriptor);
|
|
286
|
+
staged = fstatSync(stagingDescriptor, { bigint: true });
|
|
287
|
+
if (!staged.isFile() || staged.nlink !== 1n || staged.size !== sourceBefore.size) {
|
|
288
|
+
throw new Error("staged metadata-search executable failed validation");
|
|
289
|
+
}
|
|
290
|
+
complete = true;
|
|
291
|
+
return staged;
|
|
292
|
+
} finally {
|
|
293
|
+
if (stagingDescriptor !== null) {
|
|
294
|
+
if (!complete) staged = fstatSync(stagingDescriptor, { bigint: true });
|
|
295
|
+
closeSync(stagingDescriptor);
|
|
296
|
+
}
|
|
297
|
+
closeSync(sourceDescriptor);
|
|
298
|
+
if (!complete) {
|
|
299
|
+
removeIdentityBoundFile(stagingPath, staged, "incomplete staged executable");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function assertDestinationUnchanged(
|
|
305
|
+
destinationPath: string,
|
|
306
|
+
expected: BigIntStats | null,
|
|
307
|
+
platform: NodeJS.Platform,
|
|
308
|
+
): void {
|
|
309
|
+
const current = maybeLstat(destinationPath);
|
|
310
|
+
if (expected === null) {
|
|
311
|
+
if (current !== null) {
|
|
312
|
+
throw new Error("metadata-search executable destination appeared during installation");
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (current === null || !sameFileIdentity(expected, current)) {
|
|
317
|
+
throw new Error("metadata-search executable destination changed during installation");
|
|
318
|
+
}
|
|
319
|
+
assertExistingDestination(current, platform);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function removeIdentityBoundFile(
|
|
323
|
+
path: string,
|
|
324
|
+
expected: BigIntStats | null,
|
|
325
|
+
label: string,
|
|
326
|
+
): void {
|
|
327
|
+
const current = maybeLstat(path);
|
|
328
|
+
if (current === null) return;
|
|
329
|
+
if (
|
|
330
|
+
expected === null
|
|
331
|
+
|| current.isDirectory()
|
|
332
|
+
|| !sameFileIdentity(expected, current)
|
|
333
|
+
) {
|
|
334
|
+
throw new Error(`refusing to remove a replaced ${label}`);
|
|
335
|
+
}
|
|
336
|
+
rmSync(path, { force: true });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function metadataSearchExecutableName(
|
|
340
|
+
platform: NodeJS.Platform = process.platform,
|
|
341
|
+
): string {
|
|
342
|
+
return platform === "win32"
|
|
343
|
+
? "kb-url-metadata-search.exe"
|
|
344
|
+
: "kb-url-metadata-search";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function metadataSearchToolCommand(
|
|
348
|
+
action: MetadataSearchToolAction,
|
|
349
|
+
targetDirectory: string,
|
|
350
|
+
toolDirectory = import.meta.dir,
|
|
351
|
+
): readonly string[] {
|
|
352
|
+
if (!isAbsolute(targetDirectory)) {
|
|
353
|
+
throw new Error("metadata-search tool target directory must be absolute");
|
|
354
|
+
}
|
|
355
|
+
if (!isAbsolute(toolDirectory)) {
|
|
356
|
+
throw new Error("metadata-search tool directory must be absolute");
|
|
357
|
+
}
|
|
358
|
+
return Object.freeze([
|
|
359
|
+
"cargo",
|
|
360
|
+
action,
|
|
361
|
+
...(action === "check" ? ["--all-targets"] : ["--release"]),
|
|
362
|
+
"--locked",
|
|
363
|
+
"--manifest-path",
|
|
364
|
+
join(toolDirectory, "Cargo.toml"),
|
|
365
|
+
"--target-dir",
|
|
366
|
+
targetDirectory,
|
|
367
|
+
]);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function installMetadataSearchExecutable(input: {
|
|
371
|
+
readonly sourcePath: string;
|
|
372
|
+
readonly toolDirectory: string;
|
|
373
|
+
readonly platform?: NodeJS.Platform;
|
|
374
|
+
readonly beforeInstall?: MetadataSearchToolRunnerDependencies["beforeInstall"];
|
|
375
|
+
readonly beforeCommit?: MetadataSearchToolRunnerDependencies["beforeCommit"];
|
|
376
|
+
readonly syncDirectory?: MetadataSearchToolRunnerDependencies["syncDirectory"];
|
|
377
|
+
}): string {
|
|
378
|
+
const toolDirectory = resolve(input.toolDirectory);
|
|
379
|
+
const platform = input.platform ?? process.platform;
|
|
380
|
+
const executableName = metadataSearchExecutableName(platform);
|
|
381
|
+
const sourcePath = resolve(input.sourcePath);
|
|
382
|
+
const targetDirectory = join(toolDirectory, "target");
|
|
383
|
+
const releaseDirectory = join(targetDirectory, "release");
|
|
384
|
+
const destinationPath = join(releaseDirectory, executableName);
|
|
385
|
+
const stagingName = `.${executableName}.${process.pid}.${randomUUID()}.tmp`;
|
|
386
|
+
const backupName = `.${executableName}.${process.pid}.${randomUUID()}.backup`;
|
|
387
|
+
const stagingPath = join(toolDirectory, stagingName);
|
|
388
|
+
const backupPath = join(toolDirectory, backupName);
|
|
389
|
+
const syncDirectory = input.syncDirectory
|
|
390
|
+
?? ((descriptor: number) => fsyncSync(descriptor));
|
|
391
|
+
|
|
392
|
+
const toolBinding = openBoundDirectory(toolDirectory, "metadata-search tool directory");
|
|
393
|
+
let targetBinding: DirectoryBinding | null = null;
|
|
394
|
+
let releaseBinding: DirectoryBinding | null = null;
|
|
395
|
+
let stagedIdentity: BigIntStats | null = null;
|
|
396
|
+
let backupIdentity: BigIntStats | null = null;
|
|
397
|
+
let committed = false;
|
|
398
|
+
let backupDestroyed = false;
|
|
399
|
+
let installFailure: { readonly error: unknown } | null = null;
|
|
400
|
+
let installedPath: string | null = null;
|
|
401
|
+
const cleanupErrors: unknown[] = [];
|
|
402
|
+
try {
|
|
403
|
+
const target = ensurePrivateDirectory(
|
|
404
|
+
targetDirectory,
|
|
405
|
+
"metadata-search target directory",
|
|
406
|
+
);
|
|
407
|
+
targetBinding = target.binding;
|
|
408
|
+
assertDirectoryBinding(toolBinding, "metadata-search tool directory");
|
|
409
|
+
syncDirectory(toolBinding.descriptor, "tool-after-target");
|
|
410
|
+
const release = ensurePrivateDirectory(
|
|
411
|
+
releaseDirectory,
|
|
412
|
+
"metadata-search release directory",
|
|
413
|
+
);
|
|
414
|
+
releaseBinding = release.binding;
|
|
415
|
+
assertDirectoryBinding(targetBinding, "metadata-search target directory");
|
|
416
|
+
syncDirectory(targetBinding.descriptor, "target-after-release");
|
|
417
|
+
|
|
418
|
+
const previousDestination = maybeLstat(destinationPath);
|
|
419
|
+
if (previousDestination !== null) {
|
|
420
|
+
assertExistingDestination(previousDestination, platform);
|
|
421
|
+
backupIdentity = copyValidatedExecutable(
|
|
422
|
+
destinationPath,
|
|
423
|
+
backupPath,
|
|
424
|
+
platform,
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
stagedIdentity = copyValidatedExecutable(sourcePath, stagingPath, platform);
|
|
429
|
+
input.beforeInstall?.({
|
|
430
|
+
sourcePath,
|
|
431
|
+
targetDirectory,
|
|
432
|
+
releaseDirectory,
|
|
433
|
+
destinationPath,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
assertDirectoryBinding(toolBinding, "metadata-search tool directory");
|
|
437
|
+
assertDirectoryBinding(targetBinding, "metadata-search target directory");
|
|
438
|
+
assertDirectoryBinding(releaseBinding, "metadata-search release directory");
|
|
439
|
+
assertDestinationUnchanged(destinationPath, previousDestination, platform);
|
|
440
|
+
const stagedByPath = maybeLstat(stagingPath);
|
|
441
|
+
if (stagedByPath === null || !sameFileIdentity(stagedIdentity, stagedByPath)) {
|
|
442
|
+
throw new Error("staged metadata-search executable changed before installation");
|
|
443
|
+
}
|
|
444
|
+
if (backupIdentity !== null) {
|
|
445
|
+
const backup = maybeLstat(backupPath);
|
|
446
|
+
if (backup === null || !sameFileIdentity(backupIdentity, backup)) {
|
|
447
|
+
throw new Error("metadata-search executable backup changed before installation");
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
input.beforeCommit?.({
|
|
452
|
+
sourcePath,
|
|
453
|
+
targetDirectory,
|
|
454
|
+
releaseDirectory,
|
|
455
|
+
destinationPath,
|
|
456
|
+
});
|
|
457
|
+
assertDirectoryBinding(toolBinding, "metadata-search tool directory");
|
|
458
|
+
assertDirectoryBinding(targetBinding, "metadata-search target directory");
|
|
459
|
+
assertDirectoryBinding(releaseBinding, "metadata-search release directory");
|
|
460
|
+
assertDestinationUnchanged(destinationPath, previousDestination, platform);
|
|
461
|
+
const commitStaging = maybeLstat(stagingPath);
|
|
462
|
+
if (commitStaging === null || !sameFileIdentity(stagedIdentity, commitStaging)) {
|
|
463
|
+
throw new Error("staged metadata-search executable changed before commit");
|
|
464
|
+
}
|
|
465
|
+
if (backupIdentity !== null) {
|
|
466
|
+
const commitBackup = maybeLstat(backupPath);
|
|
467
|
+
if (commitBackup === null || !sameFileIdentity(backupIdentity, commitBackup)) {
|
|
468
|
+
throw new Error("metadata-search executable backup changed before commit");
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
syncDirectory(toolBinding.descriptor, "tool-before-commit");
|
|
472
|
+
syncDirectory(releaseBinding.descriptor, "release-before-commit");
|
|
473
|
+
renameSync(stagingPath, destinationPath);
|
|
474
|
+
committed = true;
|
|
475
|
+
assertDirectoryBinding(toolBinding, "metadata-search tool directory");
|
|
476
|
+
assertDirectoryBinding(targetBinding, "metadata-search target directory");
|
|
477
|
+
assertDirectoryBinding(releaseBinding, "metadata-search release directory");
|
|
478
|
+
const installed = maybeLstat(destinationPath);
|
|
479
|
+
if (installed === null || !sameInstalledFile(stagedIdentity, installed)) {
|
|
480
|
+
throw new Error("installed metadata-search executable failed identity validation");
|
|
481
|
+
}
|
|
482
|
+
assertExistingDestination(installed, platform);
|
|
483
|
+
syncDirectory(releaseBinding.descriptor, "release-after-commit");
|
|
484
|
+
syncDirectory(toolBinding.descriptor, "tool-after-commit");
|
|
485
|
+
if (backupIdentity !== null) {
|
|
486
|
+
const backup = maybeLstat(backupPath);
|
|
487
|
+
if (backup === null || !sameFileIdentity(backupIdentity, backup)) {
|
|
488
|
+
throw new Error("metadata-search executable backup changed during installation");
|
|
489
|
+
}
|
|
490
|
+
rmSync(backupPath, { force: true });
|
|
491
|
+
backupDestroyed = true;
|
|
492
|
+
syncDirectory(toolBinding.descriptor, "tool-after-backup-removal");
|
|
493
|
+
backupIdentity = null;
|
|
494
|
+
}
|
|
495
|
+
installedPath = destinationPath;
|
|
496
|
+
} catch (error) {
|
|
497
|
+
let reportedError = error;
|
|
498
|
+
if (
|
|
499
|
+
committed
|
|
500
|
+
&& !backupDestroyed
|
|
501
|
+
&& releaseBinding !== null
|
|
502
|
+
&& stagedIdentity !== null
|
|
503
|
+
) {
|
|
504
|
+
try {
|
|
505
|
+
const installed = maybeLstat(destinationPath);
|
|
506
|
+
if (installed === null || !sameInstalledFile(stagedIdentity, installed)) {
|
|
507
|
+
throw new Error("refusing to replace an unrecognized failed install");
|
|
508
|
+
}
|
|
509
|
+
if (backupIdentity === null) {
|
|
510
|
+
rmSync(destinationPath, { force: true });
|
|
511
|
+
} else {
|
|
512
|
+
const backup = maybeLstat(backupPath);
|
|
513
|
+
if (backup === null || !sameFileIdentity(backupIdentity, backup)) {
|
|
514
|
+
throw new Error("refusing to restore an unrecognized executable backup");
|
|
515
|
+
}
|
|
516
|
+
renameSync(backupPath, destinationPath);
|
|
517
|
+
backupIdentity = null;
|
|
518
|
+
}
|
|
519
|
+
syncDirectory(releaseBinding.descriptor, "release-after-rollback");
|
|
520
|
+
syncDirectory(toolBinding.descriptor, "tool-after-rollback");
|
|
521
|
+
} catch (rollbackError) {
|
|
522
|
+
reportedError = new AggregateError(
|
|
523
|
+
[error, rollbackError],
|
|
524
|
+
"metadata-search executable installation and rollback both failed",
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
installFailure = { error: reportedError };
|
|
529
|
+
} finally {
|
|
530
|
+
const attemptCleanup = (action: () => void): void => {
|
|
531
|
+
try {
|
|
532
|
+
action();
|
|
533
|
+
} catch (error) {
|
|
534
|
+
cleanupErrors.push(error);
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
attemptCleanup(() => removeIdentityBoundFile(
|
|
538
|
+
stagingPath,
|
|
539
|
+
stagedIdentity,
|
|
540
|
+
"staging executable",
|
|
541
|
+
));
|
|
542
|
+
attemptCleanup(() => removeIdentityBoundFile(
|
|
543
|
+
backupPath,
|
|
544
|
+
backupIdentity,
|
|
545
|
+
"executable backup",
|
|
546
|
+
));
|
|
547
|
+
if (releaseBinding !== null) {
|
|
548
|
+
const releaseDescriptor = releaseBinding.descriptor;
|
|
549
|
+
attemptCleanup(() => closeSync(releaseDescriptor));
|
|
550
|
+
}
|
|
551
|
+
if (targetBinding !== null) {
|
|
552
|
+
const targetDescriptor = targetBinding.descriptor;
|
|
553
|
+
attemptCleanup(() => closeSync(targetDescriptor));
|
|
554
|
+
}
|
|
555
|
+
attemptCleanup(() => closeSync(toolBinding.descriptor));
|
|
556
|
+
}
|
|
557
|
+
const errors = [
|
|
558
|
+
...(installFailure === null ? [] : [installFailure.error]),
|
|
559
|
+
...cleanupErrors,
|
|
560
|
+
];
|
|
561
|
+
if (errors.length === 1) throw errors[0];
|
|
562
|
+
if (errors.length > 1) {
|
|
563
|
+
const details = errors.map((error) => error instanceof Error
|
|
564
|
+
? error.message
|
|
565
|
+
: "unknown error").join("; ");
|
|
566
|
+
throw new AggregateError(
|
|
567
|
+
errors,
|
|
568
|
+
`metadata-search executable installation or cleanup failed: ${details}`,
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
if (installedPath === null) {
|
|
572
|
+
throw new Error("metadata-search executable installation produced no outcome");
|
|
573
|
+
}
|
|
574
|
+
return installedPath;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function defaultTemporaryDirectory(): string {
|
|
578
|
+
return mkdtempSync(join(tmpdir(), TEMPORARY_DIRECTORY_PREFIX));
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function assertTemporaryDirectoryIdentity(
|
|
582
|
+
path: string,
|
|
583
|
+
expected: BigIntStats,
|
|
584
|
+
): void {
|
|
585
|
+
const current = maybeLstat(path);
|
|
586
|
+
assertDirectory(current, "Cargo target directory");
|
|
587
|
+
if (!sameDirectoryIdentity(expected, current)) {
|
|
588
|
+
throw new Error("Cargo target directory changed before cleanup");
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function removeOwnedTemporaryDirectory(
|
|
593
|
+
path: string,
|
|
594
|
+
identity: BigIntStats,
|
|
595
|
+
): void {
|
|
596
|
+
const resolvedPath = resolve(path);
|
|
597
|
+
const resolvedTemporaryRoot = resolve(tmpdir());
|
|
598
|
+
if (
|
|
599
|
+
dirname(resolvedPath) !== resolvedTemporaryRoot
|
|
600
|
+
|| !basename(resolvedPath).startsWith(TEMPORARY_DIRECTORY_PREFIX)
|
|
601
|
+
) {
|
|
602
|
+
throw new Error("refusing to remove an unexpected Cargo target directory");
|
|
603
|
+
}
|
|
604
|
+
assertTemporaryDirectoryIdentity(resolvedPath, identity);
|
|
605
|
+
rmSync(resolvedPath, { recursive: true, force: true });
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function defaultCargoRunner(
|
|
609
|
+
command: readonly string[],
|
|
610
|
+
options: { readonly cwd: string; readonly environment: NodeJS.ProcessEnv },
|
|
611
|
+
): number {
|
|
612
|
+
const result = Bun.spawnSync({
|
|
613
|
+
cmd: [...command],
|
|
614
|
+
cwd: options.cwd,
|
|
615
|
+
env: options.environment,
|
|
616
|
+
stdin: "inherit",
|
|
617
|
+
stdout: "inherit",
|
|
618
|
+
stderr: "inherit",
|
|
619
|
+
});
|
|
620
|
+
return result.exitCode;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
export function runMetadataSearchTool(
|
|
624
|
+
action: MetadataSearchToolAction,
|
|
625
|
+
dependencies: MetadataSearchToolRunnerDependencies = {},
|
|
626
|
+
): number {
|
|
627
|
+
const toolDirectory = resolve(dependencies.toolDirectory ?? import.meta.dir);
|
|
628
|
+
const platform = dependencies.platform ?? process.platform;
|
|
629
|
+
const createTemporaryDirectory = dependencies.createTemporaryDirectory
|
|
630
|
+
?? defaultTemporaryDirectory;
|
|
631
|
+
const removeTemporaryDirectory = dependencies.removeTemporaryDirectory
|
|
632
|
+
?? removeOwnedTemporaryDirectory;
|
|
633
|
+
const runCargo = dependencies.runCargo ?? defaultCargoRunner;
|
|
634
|
+
const createdTargetDirectory = createTemporaryDirectory();
|
|
635
|
+
if (!isAbsolute(createdTargetDirectory)) {
|
|
636
|
+
throw new Error("metadata-search tool temporary directory must be absolute");
|
|
637
|
+
}
|
|
638
|
+
const targetDirectory = resolve(createdTargetDirectory);
|
|
639
|
+
const targetIdentity = maybeLstat(targetDirectory);
|
|
640
|
+
assertDirectory(targetIdentity, "Cargo target directory");
|
|
641
|
+
|
|
642
|
+
try {
|
|
643
|
+
const command = metadataSearchToolCommand(action, targetDirectory, toolDirectory);
|
|
644
|
+
const exitCode = runCargo(command, {
|
|
645
|
+
cwd: toolDirectory,
|
|
646
|
+
environment: { ...process.env, CARGO_INCREMENTAL: "0" },
|
|
647
|
+
});
|
|
648
|
+
if (exitCode !== 0 || action === "check") return exitCode;
|
|
649
|
+
|
|
650
|
+
installMetadataSearchExecutable({
|
|
651
|
+
sourcePath: join(
|
|
652
|
+
targetDirectory,
|
|
653
|
+
"release",
|
|
654
|
+
metadataSearchExecutableName(platform),
|
|
655
|
+
),
|
|
656
|
+
toolDirectory,
|
|
657
|
+
platform,
|
|
658
|
+
beforeInstall: dependencies.beforeInstall,
|
|
659
|
+
beforeCommit: dependencies.beforeCommit,
|
|
660
|
+
syncDirectory: dependencies.syncDirectory,
|
|
661
|
+
});
|
|
662
|
+
return 0;
|
|
663
|
+
} finally {
|
|
664
|
+
assertTemporaryDirectoryIdentity(targetDirectory, targetIdentity);
|
|
665
|
+
removeTemporaryDirectory(targetDirectory, targetIdentity);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (import.meta.main) {
|
|
670
|
+
const action = Bun.argv[2];
|
|
671
|
+
if (
|
|
672
|
+
(action !== "check" && action !== "build")
|
|
673
|
+
|| Bun.argv.length !== 3
|
|
674
|
+
) {
|
|
675
|
+
process.stderr.write("usage: runner.ts check|build\n");
|
|
676
|
+
process.exit(2);
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
process.exit(runMetadataSearchTool(action));
|
|
680
|
+
} catch (error) {
|
|
681
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
682
|
+
process.stderr.write(`metadata-search tool ${action} failed: ${message}\n`);
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
685
|
+
}
|