vde-open 0.1.4 → 0.1.6
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/README.md +57 -55
- package/dist/cli.js +2 -2
- package/dist/daemon.js +2 -2
- package/dist/{main-DlLPjYZZ.js → main-C3SHFgNa.js} +489 -12
- package/dist/print-CR5-GDjQ.js +1779 -0
- package/dist/render-BJnuEfHt.js +6235 -0
- package/dist/{render-BNZz0CjQ.js → rendering-rules-DiE2jI7I.js} +39 -6243
- package/dist/{search-index-__laOomT.js → search-index-B7DqaWh6.js} +10 -0
- package/dist/web/assets/index-BsWikuUv.js +97 -0
- package/dist/web/index.html +1 -1
- package/dist/workers/parse-worker.js +10 -4
- package/dist/workers/search-worker.js +1 -1
- package/docs/agent-usage.md +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BoCe8U41.js +0 -97
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { r as __toESM, t as __commonJSMin } from "./rolldown-runtime-DC62tzP2.js";
|
|
2
|
-
import { $ as literal, A as successEnvelope, B as readParamsSchema, C as VdeError, D as ExitCode, E as isVdeError, F as htmlModeChangeParamsSchema, G as sha256Schema, H as reorderParamsSchema, I as htmlModeSchema, J as watchRuleSchema, K as sourceKindSchema, L as linkOpenParamsSchema, M as closeParamsSchema, N as documentFormatSchema, P as documentIdSchema, Q as boolean, R as listParamsSchema, S as validateAnswers, U as revisionSchema, V as renderGrantParamsSchema, W as searchParamsSchema, X as _enum, Y as LIMITS, Z as array, _ as feedbackWaitParamsSchema, a as uniqueTokens, at as canonicalJson, b as submissionSchema, c as feedbackAckParamsSchema, d as feedbackDraftParamsSchema, et as number, f as feedbackForgetParamsSchema, g as feedbackSubmitParamsSchema, h as feedbackStatusSchema, i as codePointLength, it as ZodError, j as assetRecordSchema, k as errorEnvelope, l as feedbackCancelParamsSchema, m as feedbackListParamsSchema, n as batchesOf, nt as strictObject, o as answersSchema, p as feedbackIdParamsSchema, q as sourceStateSchema, r as partsOf, rt as string, s as cancellationSchema, tt as record, u as feedbackCreateParamsSchema, v as questionnaireSchema, w as exitCodeForError, x as utf8Length, y as requestIdSchema, z as openParamsSchema } from "./search-index-
|
|
3
|
-
import {
|
|
2
|
+
import { $ as literal, A as successEnvelope, B as readParamsSchema, C as VdeError, D as ExitCode, E as isVdeError, F as htmlModeChangeParamsSchema, G as sha256Schema, H as reorderParamsSchema, I as htmlModeSchema, J as watchRuleSchema, K as sourceKindSchema, L as linkOpenParamsSchema, M as closeParamsSchema, N as documentFormatSchema, P as documentIdSchema, Q as boolean, R as listParamsSchema, S as validateAnswers, U as revisionSchema, V as renderGrantParamsSchema, W as searchParamsSchema, X as _enum, Y as LIMITS, Z as array, _ as feedbackWaitParamsSchema, a as uniqueTokens, at as canonicalJson, b as submissionSchema, c as feedbackAckParamsSchema, d as feedbackDraftParamsSchema, et as number, f as feedbackForgetParamsSchema, g as feedbackSubmitParamsSchema, h as feedbackStatusSchema, i as codePointLength, it as ZodError, j as assetRecordSchema, k as errorEnvelope, l as feedbackCancelParamsSchema, m as feedbackListParamsSchema, n as batchesOf, nt as strictObject, o as answersSchema, p as feedbackIdParamsSchema, q as sourceStateSchema, r as partsOf, rt as string, s as cancellationSchema, tt as record, u as feedbackCreateParamsSchema, v as questionnaireSchema, w as exitCodeForError, x as utf8Length, y as requestIdSchema, z as openParamsSchema } from "./search-index-B7DqaWh6.js";
|
|
3
|
+
import { C as roleAllowed, _ as dirnameOfLogicalPath, a as ParseLimitError, g as classifyReference, h as classifyLink, m as assetTypeOf, o as analyzeDocument, v as encodeLogicalPath, x as isValidLogicalPath, y as hasHiddenSegment } from "./rendering-rules-DiE2jI7I.js";
|
|
4
|
+
import { n as scanReferences, r as extractTitle } from "./render-BJnuEfHt.js";
|
|
4
5
|
import { n as measureHeap, t as collectGarbage } from "./heap-C3qHm7SE.js";
|
|
5
|
-
import { constants, existsSync, stat, unwatchFile, watch, watchFile } from "node:fs";
|
|
6
|
-
import { appendFile, chmod, link, lstat, mkdir, open, readFile, readdir, readlink, realpath, rename, rm, rmdir, stat as stat$1, writeFile } from "node:fs/promises";
|
|
6
|
+
import { accessSync, constants, existsSync, stat, statSync, unwatchFile, watch, watchFile } from "node:fs";
|
|
7
|
+
import { appendFile, chmod, link, lstat, mkdir, mkdtemp, open, readFile, readdir, readlink, realpath, rename, rm, rmdir, stat as stat$1, writeFile } from "node:fs/promises";
|
|
7
8
|
import * as sp from "node:path";
|
|
8
|
-
import { basename, dirname, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
9
|
+
import { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
9
10
|
import { EventEmitter } from "node:events";
|
|
10
|
-
import { execFile } from "node:child_process";
|
|
11
|
+
import { execFile, spawn } from "node:child_process";
|
|
11
12
|
import { promisify } from "node:util";
|
|
12
13
|
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
13
14
|
import * as nativeFs from "fs";
|
|
14
|
-
import { readdir as readdir$1, readdirSync, realpath as realpath$1, realpathSync, stat as stat$2, statSync } from "fs";
|
|
15
|
+
import { readdir as readdir$1, readdirSync, realpath as realpath$1, realpathSync, stat as stat$2, statSync as statSync$1 } from "fs";
|
|
15
16
|
import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, normalize, posix as posix$1, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
|
|
16
17
|
import { fileURLToPath } from "url";
|
|
17
18
|
import { createRequire } from "module";
|
|
18
|
-
import {
|
|
19
|
+
import { tmpdir, type } from "node:os";
|
|
20
|
+
import { fileURLToPath as fileURLToPath$1, pathToFileURL } from "node:url";
|
|
19
21
|
import { Worker } from "node:worker_threads";
|
|
20
22
|
import { performance } from "node:perf_hooks";
|
|
21
23
|
import { getHeapStatistics } from "node:v8";
|
|
@@ -23,7 +25,6 @@ import { STATUS_CODES, ServerResponse, createServer } from "node:http";
|
|
|
23
25
|
import { Http2ServerRequest, constants as constants$1 } from "node:http2";
|
|
24
26
|
import { Readable } from "node:stream";
|
|
25
27
|
import { createServer as createServer$1 } from "node:net";
|
|
26
|
-
import { type } from "node:os";
|
|
27
28
|
//#region ../../packages/shared/src/bridge.ts
|
|
28
29
|
const BRIDGE_HELLO = "vde-bridge-hello";
|
|
29
30
|
const BRIDGE_PORT = "vde-bridge-port";
|
|
@@ -154,7 +155,7 @@ function parseStrictJson(text) {
|
|
|
154
155
|
}
|
|
155
156
|
//#endregion
|
|
156
157
|
//#region package.json
|
|
157
|
-
var version = "0.1.
|
|
158
|
+
var version = "0.1.6";
|
|
158
159
|
//#endregion
|
|
159
160
|
//#region src/documents/cursor.ts
|
|
160
161
|
function invalidCursor(reason) {
|
|
@@ -3354,7 +3355,7 @@ function getOptions(options) {
|
|
|
3354
3355
|
realpath: opts.fs.realpath || realpath$1,
|
|
3355
3356
|
realpathSync: opts.fs.realpathSync || realpathSync,
|
|
3356
3357
|
stat: opts.fs.stat || stat$2,
|
|
3357
|
-
statSync: opts.fs.statSync || statSync
|
|
3358
|
+
statSync: opts.fs.statSync || statSync$1
|
|
3358
3359
|
});
|
|
3359
3360
|
if (opts.debug) log("globbing with options:", opts);
|
|
3360
3361
|
return opts;
|
|
@@ -5445,6 +5446,454 @@ var DocumentService = class {
|
|
|
5445
5446
|
}
|
|
5446
5447
|
};
|
|
5447
5448
|
//#endregion
|
|
5449
|
+
//#region src/export/browser.ts
|
|
5450
|
+
function executableFile(path) {
|
|
5451
|
+
try {
|
|
5452
|
+
accessSync(path, constants.X_OK);
|
|
5453
|
+
return statSync(path).isFile();
|
|
5454
|
+
} catch {
|
|
5455
|
+
return false;
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
const MAC_APPS = [
|
|
5459
|
+
"Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
5460
|
+
"Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
5461
|
+
"Chromium.app/Contents/MacOS/Chromium"
|
|
5462
|
+
];
|
|
5463
|
+
const LINUX_COMMANDS = [
|
|
5464
|
+
"google-chrome",
|
|
5465
|
+
"google-chrome-stable",
|
|
5466
|
+
"chromium",
|
|
5467
|
+
"chromium-browser",
|
|
5468
|
+
"microsoft-edge",
|
|
5469
|
+
"microsoft-edge-stable"
|
|
5470
|
+
];
|
|
5471
|
+
const WINDOWS_APPS = ["Google\\Chrome\\Application\\chrome.exe", "Microsoft\\Edge\\Application\\msedge.exe"];
|
|
5472
|
+
function candidates(lookup) {
|
|
5473
|
+
const { env, platform, homeDir } = lookup;
|
|
5474
|
+
if (platform === "darwin") return ["/Applications", join(homeDir, "Applications")].flatMap((dir) => MAC_APPS.map((app) => join(dir, app)));
|
|
5475
|
+
if (platform === "win32") {
|
|
5476
|
+
const roots = [
|
|
5477
|
+
env["ProgramFiles"],
|
|
5478
|
+
env["ProgramFiles(x86)"],
|
|
5479
|
+
env["LOCALAPPDATA"]
|
|
5480
|
+
].filter((root) => root !== void 0 && root !== "");
|
|
5481
|
+
return WINDOWS_APPS.flatMap((app) => roots.map((root) => `${root}\\${app}`));
|
|
5482
|
+
}
|
|
5483
|
+
const dirs = (env["PATH"] ?? "").split(delimiter).filter((dir) => isAbsolute(dir));
|
|
5484
|
+
return LINUX_COMMANDS.flatMap((command) => dirs.map((dir) => join(dir, command)));
|
|
5485
|
+
}
|
|
5486
|
+
function findBrowser(lookup) {
|
|
5487
|
+
const isExecutable = lookup.isExecutable ?? executableFile;
|
|
5488
|
+
const explicit = lookup.env["VDE_OPEN_BROWSER"];
|
|
5489
|
+
if (explicit !== void 0 && explicit !== "") {
|
|
5490
|
+
if (isAbsolute(explicit) && isExecutable(explicit)) return explicit;
|
|
5491
|
+
throw new VdeError("E_BROWSER_NOT_FOUND", "VDE_OPEN_BROWSER is not the absolute path of an executable file.", { source: "VDE_OPEN_BROWSER" });
|
|
5492
|
+
}
|
|
5493
|
+
const found = candidates(lookup).find((path) => isExecutable(path));
|
|
5494
|
+
if (found !== void 0) return found;
|
|
5495
|
+
throw new VdeError("E_BROWSER_NOT_FOUND", "Exporting a PDF needs Google Chrome or Microsoft Edge. Set VDE_OPEN_BROWSER to use another Chromium-based browser.", { source: "search" });
|
|
5496
|
+
}
|
|
5497
|
+
//#endregion
|
|
5498
|
+
//#region src/export/cdp.ts
|
|
5499
|
+
const MAX_MESSAGE_BYTES = 16777216;
|
|
5500
|
+
var CdpError = class extends Error {
|
|
5501
|
+
method;
|
|
5502
|
+
constructor(method, message) {
|
|
5503
|
+
super(`${method}: ${message}`);
|
|
5504
|
+
this.name = "CdpError";
|
|
5505
|
+
this.method = method;
|
|
5506
|
+
}
|
|
5507
|
+
};
|
|
5508
|
+
function connectCdp(toBrowser, fromBrowser) {
|
|
5509
|
+
let nextId = 1;
|
|
5510
|
+
let failure = null;
|
|
5511
|
+
const pending = /* @__PURE__ */ new Map();
|
|
5512
|
+
const waiters = [];
|
|
5513
|
+
let chunks = [];
|
|
5514
|
+
let buffered = 0;
|
|
5515
|
+
const close = (error) => {
|
|
5516
|
+
if (failure !== null) return;
|
|
5517
|
+
failure = error;
|
|
5518
|
+
for (const waiter of pending.values()) waiter.reject(error);
|
|
5519
|
+
pending.clear();
|
|
5520
|
+
for (const waiter of waiters.splice(0)) waiter.reject(error);
|
|
5521
|
+
};
|
|
5522
|
+
const dispatch = (message) => {
|
|
5523
|
+
if (message.id !== void 0) {
|
|
5524
|
+
const waiter = pending.get(message.id);
|
|
5525
|
+
if (!waiter) return;
|
|
5526
|
+
pending.delete(message.id);
|
|
5527
|
+
if (message.error) waiter.reject(new CdpError(waiter.method, message.error.message ?? "failed"));
|
|
5528
|
+
else waiter.resolve(message.result);
|
|
5529
|
+
return;
|
|
5530
|
+
}
|
|
5531
|
+
const index = waiters.findIndex((waiter) => waiter.method === message.method && waiter.sessionId === message.sessionId && waiter.accept(message.params));
|
|
5532
|
+
if (index === -1) return;
|
|
5533
|
+
const [waiter] = waiters.splice(index, 1);
|
|
5534
|
+
waiter?.resolve(message.params);
|
|
5535
|
+
};
|
|
5536
|
+
fromBrowser.on("data", (chunk) => {
|
|
5537
|
+
if (failure !== null) return;
|
|
5538
|
+
let start = 0;
|
|
5539
|
+
let end = chunk.indexOf(0, start);
|
|
5540
|
+
try {
|
|
5541
|
+
while (end !== -1) {
|
|
5542
|
+
chunks.push(chunk.subarray(start, end));
|
|
5543
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
5544
|
+
chunks = [];
|
|
5545
|
+
buffered = 0;
|
|
5546
|
+
dispatch(JSON.parse(text));
|
|
5547
|
+
start = end + 1;
|
|
5548
|
+
end = chunk.indexOf(0, start);
|
|
5549
|
+
}
|
|
5550
|
+
if (start < chunk.length) {
|
|
5551
|
+
chunks.push(chunk.subarray(start));
|
|
5552
|
+
buffered += chunk.length - start;
|
|
5553
|
+
if (buffered > MAX_MESSAGE_BYTES) throw new Error("message too large");
|
|
5554
|
+
}
|
|
5555
|
+
} catch {
|
|
5556
|
+
chunks = [];
|
|
5557
|
+
close(new CdpError("protocol", "the browser sent a message that cannot be read"));
|
|
5558
|
+
}
|
|
5559
|
+
});
|
|
5560
|
+
fromBrowser.on("error", () => void 0);
|
|
5561
|
+
toBrowser.on("error", () => void 0);
|
|
5562
|
+
return {
|
|
5563
|
+
send(method, params = {}, sessionId) {
|
|
5564
|
+
if (failure !== null) return Promise.reject(failure);
|
|
5565
|
+
const id = nextId;
|
|
5566
|
+
nextId += 1;
|
|
5567
|
+
return new Promise((resolve, reject) => {
|
|
5568
|
+
pending.set(id, {
|
|
5569
|
+
method,
|
|
5570
|
+
resolve,
|
|
5571
|
+
reject
|
|
5572
|
+
});
|
|
5573
|
+
toBrowser.write(`${JSON.stringify({
|
|
5574
|
+
id,
|
|
5575
|
+
method,
|
|
5576
|
+
params,
|
|
5577
|
+
...sessionId === void 0 ? {} : { sessionId }
|
|
5578
|
+
})}\0`);
|
|
5579
|
+
});
|
|
5580
|
+
},
|
|
5581
|
+
waitFor(method, sessionId, accept) {
|
|
5582
|
+
if (failure !== null) return Promise.reject(failure);
|
|
5583
|
+
return new Promise((resolve, reject) => {
|
|
5584
|
+
waiters.push({
|
|
5585
|
+
method,
|
|
5586
|
+
sessionId,
|
|
5587
|
+
accept,
|
|
5588
|
+
resolve,
|
|
5589
|
+
reject
|
|
5590
|
+
});
|
|
5591
|
+
});
|
|
5592
|
+
},
|
|
5593
|
+
close
|
|
5594
|
+
};
|
|
5595
|
+
}
|
|
5596
|
+
//#endregion
|
|
5597
|
+
//#region src/export/pdf-service.ts
|
|
5598
|
+
const BROWSER_FLAGS = [
|
|
5599
|
+
"--headless",
|
|
5600
|
+
"--remote-debugging-pipe",
|
|
5601
|
+
"--no-first-run",
|
|
5602
|
+
"--no-default-browser-check",
|
|
5603
|
+
"--disable-background-networking",
|
|
5604
|
+
"--disable-component-update",
|
|
5605
|
+
"--disable-default-apps",
|
|
5606
|
+
"--disable-extensions",
|
|
5607
|
+
"--disable-sync",
|
|
5608
|
+
"--password-store=basic",
|
|
5609
|
+
"--use-mock-keychain"
|
|
5610
|
+
];
|
|
5611
|
+
const MIN_BROWSER_VERSION = 131;
|
|
5612
|
+
const EXIT_GRACE_MS = 5e3;
|
|
5613
|
+
const MAX_WAITING = 4;
|
|
5614
|
+
const READ_CHUNK_BYTES = 1048576;
|
|
5615
|
+
const MAX_PAGE_BYTES = 268435456;
|
|
5616
|
+
const TEMP_PREFIX$1 = "vde-open-pdf-";
|
|
5617
|
+
const STALE_TEMP_MS = 36e5;
|
|
5618
|
+
const PDF_HEAD = Buffer.from("%PDF-");
|
|
5619
|
+
const PDF_TAIL = Buffer.from("%%EOF");
|
|
5620
|
+
function exportFailed(reason, details = {}) {
|
|
5621
|
+
return new VdeError("E_EXPORT_FAILED", "The browser could not print the PDF.", {
|
|
5622
|
+
reason,
|
|
5623
|
+
...details
|
|
5624
|
+
});
|
|
5625
|
+
}
|
|
5626
|
+
const stopping = () => new VdeError("E_DAEMON_STOPPING", "The daemon is stopping.");
|
|
5627
|
+
const cancelled = () => new VdeError("E_INTERRUPTED", "The PDF export was cancelled.");
|
|
5628
|
+
function settledWithin(promise, ms) {
|
|
5629
|
+
return new Promise((resolve) => {
|
|
5630
|
+
const timer = setTimeout(() => resolve(false), ms);
|
|
5631
|
+
promise.then(() => {
|
|
5632
|
+
clearTimeout(timer);
|
|
5633
|
+
resolve(true);
|
|
5634
|
+
});
|
|
5635
|
+
});
|
|
5636
|
+
}
|
|
5637
|
+
async function printPage(cdp, url) {
|
|
5638
|
+
const { product } = await cdp.send("Browser.getVersion");
|
|
5639
|
+
if (Number(/\/(\d+)\./.exec(product)?.[1] ?? "0") < MIN_BROWSER_VERSION) throw new VdeError("E_BROWSER_NOT_FOUND", `Exporting a PDF needs Google Chrome or Microsoft Edge ${String(MIN_BROWSER_VERSION)} or later.`, {
|
|
5640
|
+
source: "version",
|
|
5641
|
+
version: product
|
|
5642
|
+
});
|
|
5643
|
+
const { targetId } = await cdp.send("Target.createTarget", { url: "about:blank" });
|
|
5644
|
+
const { sessionId } = await cdp.send("Target.attachToTarget", {
|
|
5645
|
+
targetId,
|
|
5646
|
+
flatten: true
|
|
5647
|
+
});
|
|
5648
|
+
await cdp.send("Page.enable", {}, sessionId);
|
|
5649
|
+
await cdp.send("Page.setLifecycleEventsEnabled", { enabled: true }, sessionId);
|
|
5650
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5651
|
+
let loaderId = null;
|
|
5652
|
+
const loaded = cdp.waitFor("Page.lifecycleEvent", sessionId, (event) => {
|
|
5653
|
+
if (event.name !== "load") return false;
|
|
5654
|
+
if (loaderId === null) seen.add(event.loaderId);
|
|
5655
|
+
return event.loaderId === loaderId;
|
|
5656
|
+
});
|
|
5657
|
+
loaded.catch(() => void 0);
|
|
5658
|
+
const navigation = await cdp.send("Page.navigate", { url }, sessionId);
|
|
5659
|
+
if (navigation.errorText !== void 0 || navigation.loaderId === void 0) throw exportFailed("load");
|
|
5660
|
+
loaderId = navigation.loaderId;
|
|
5661
|
+
if (!seen.has(loaderId)) await loaded;
|
|
5662
|
+
const { stream } = await cdp.send("Page.printToPDF", {
|
|
5663
|
+
printBackground: true,
|
|
5664
|
+
preferCSSPageSize: true,
|
|
5665
|
+
displayHeaderFooter: false,
|
|
5666
|
+
generateDocumentOutline: true,
|
|
5667
|
+
generateTaggedPDF: true,
|
|
5668
|
+
transferMode: "ReturnAsStream"
|
|
5669
|
+
}, sessionId);
|
|
5670
|
+
const parts = [];
|
|
5671
|
+
for (;;) {
|
|
5672
|
+
const chunk = await cdp.send("IO.read", {
|
|
5673
|
+
handle: stream,
|
|
5674
|
+
size: READ_CHUNK_BYTES
|
|
5675
|
+
}, sessionId);
|
|
5676
|
+
parts.push(Buffer.from(chunk.data, chunk.base64Encoded === true ? "base64" : "latin1"));
|
|
5677
|
+
if (chunk.eof) break;
|
|
5678
|
+
}
|
|
5679
|
+
await cdp.send("IO.close", { handle: stream }, sessionId);
|
|
5680
|
+
return {
|
|
5681
|
+
product,
|
|
5682
|
+
pdf: Buffer.concat(parts)
|
|
5683
|
+
};
|
|
5684
|
+
}
|
|
5685
|
+
function isPdf(pdf) {
|
|
5686
|
+
let end = pdf.length;
|
|
5687
|
+
while (end > 0 && (pdf[end - 1] === 10 || pdf[end - 1] === 13 || pdf[end - 1] === 32)) end -= 1;
|
|
5688
|
+
return pdf.subarray(0, PDF_HEAD.length).equals(PDF_HEAD) && pdf.subarray(end - PDF_TAIL.length, end).equals(PDF_TAIL);
|
|
5689
|
+
}
|
|
5690
|
+
function createPdfService(options) {
|
|
5691
|
+
const { store, documents, printer } = options;
|
|
5692
|
+
const platform = options.platform ?? process.platform;
|
|
5693
|
+
const timeoutMs = options.timeoutMs ?? LIMITS.pdfExportTimeoutMs;
|
|
5694
|
+
const tempRoot = options.tempRoot ?? tmpdir();
|
|
5695
|
+
const maxPageBytes = options.maxPageBytes ?? MAX_PAGE_BYTES;
|
|
5696
|
+
let closed = false;
|
|
5697
|
+
let stopRunning = null;
|
|
5698
|
+
let queue = Promise.resolve();
|
|
5699
|
+
let waiting = 0;
|
|
5700
|
+
const kill = (child) => {
|
|
5701
|
+
if (child.pid === void 0) return;
|
|
5702
|
+
if (platform === "win32") {
|
|
5703
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
5704
|
+
spawn("taskkill", [
|
|
5705
|
+
"/PID",
|
|
5706
|
+
String(child.pid),
|
|
5707
|
+
"/T",
|
|
5708
|
+
"/F"
|
|
5709
|
+
], {
|
|
5710
|
+
stdio: "ignore",
|
|
5711
|
+
windowsHide: true
|
|
5712
|
+
}).once("error", () => child.kill());
|
|
5713
|
+
return;
|
|
5714
|
+
}
|
|
5715
|
+
try {
|
|
5716
|
+
process.kill(-child.pid, "SIGKILL");
|
|
5717
|
+
} catch {}
|
|
5718
|
+
};
|
|
5719
|
+
const writePage = async (path, html, slot, images) => {
|
|
5720
|
+
const file = await open(path, "wx", 384);
|
|
5721
|
+
let written = 0;
|
|
5722
|
+
const write = async (text) => {
|
|
5723
|
+
written += Buffer.byteLength(text);
|
|
5724
|
+
if (written > maxPageBytes) throw new VdeError("E_LIMIT_EXCEEDED", "The document is too large to print.", {
|
|
5725
|
+
reason: "page-size",
|
|
5726
|
+
limit: maxPageBytes
|
|
5727
|
+
});
|
|
5728
|
+
await file.write(text);
|
|
5729
|
+
};
|
|
5730
|
+
try {
|
|
5731
|
+
let last = 0;
|
|
5732
|
+
for (const match of html.matchAll(new RegExp(`${slot}-(\\d+)`, "g"))) {
|
|
5733
|
+
await write(html.slice(last, match.index));
|
|
5734
|
+
const image = images[Number(match[1])];
|
|
5735
|
+
if (image !== void 0) {
|
|
5736
|
+
await write(`data:${image.mime};base64,`);
|
|
5737
|
+
await write((await store.readBlob(image.sha256)).toString("base64"));
|
|
5738
|
+
}
|
|
5739
|
+
last = match.index + match[0].length;
|
|
5740
|
+
}
|
|
5741
|
+
await write(html.slice(last));
|
|
5742
|
+
} finally {
|
|
5743
|
+
await file.close();
|
|
5744
|
+
}
|
|
5745
|
+
};
|
|
5746
|
+
const uid = platform === "win32" ? null : process.getuid?.() ?? null;
|
|
5747
|
+
const sweepStale = async () => {
|
|
5748
|
+
const now = Date.now();
|
|
5749
|
+
for (const name of await readdir(tempRoot).catch(() => [])) {
|
|
5750
|
+
if (!name.startsWith(TEMP_PREFIX$1)) continue;
|
|
5751
|
+
const path = join(tempRoot, name);
|
|
5752
|
+
const info = await lstat(path).catch(() => null);
|
|
5753
|
+
if (info === null || !info.isDirectory() || now - info.mtimeMs < STALE_TEMP_MS) continue;
|
|
5754
|
+
if (uid !== null && info.uid !== uid) continue;
|
|
5755
|
+
await rm(path, {
|
|
5756
|
+
recursive: true,
|
|
5757
|
+
force: true
|
|
5758
|
+
}).catch(() => void 0);
|
|
5759
|
+
}
|
|
5760
|
+
};
|
|
5761
|
+
sweepStale();
|
|
5762
|
+
const print = async (browser, directory, url, signal) => {
|
|
5763
|
+
if (closed) throw stopping();
|
|
5764
|
+
if (signal?.aborted === true) throw cancelled();
|
|
5765
|
+
const child = spawn(browser, [
|
|
5766
|
+
...BROWSER_FLAGS,
|
|
5767
|
+
`--user-data-dir=${join(directory, "profile")}`,
|
|
5768
|
+
"about:blank"
|
|
5769
|
+
], {
|
|
5770
|
+
stdio: [
|
|
5771
|
+
"ignore",
|
|
5772
|
+
"ignore",
|
|
5773
|
+
"ignore",
|
|
5774
|
+
"pipe",
|
|
5775
|
+
"pipe"
|
|
5776
|
+
],
|
|
5777
|
+
detached: platform !== "win32",
|
|
5778
|
+
windowsHide: true
|
|
5779
|
+
});
|
|
5780
|
+
const cdp = connectCdp(child.stdio[3], child.stdio[4]);
|
|
5781
|
+
let reason = null;
|
|
5782
|
+
const stop = (why) => {
|
|
5783
|
+
reason ??= why;
|
|
5784
|
+
cdp.close(why);
|
|
5785
|
+
kill(child);
|
|
5786
|
+
};
|
|
5787
|
+
const exited = new Promise((resolve) => {
|
|
5788
|
+
child.once("exit", () => {
|
|
5789
|
+
cdp.close(reason ?? exportFailed("exit"));
|
|
5790
|
+
resolve();
|
|
5791
|
+
});
|
|
5792
|
+
child.once("error", () => {
|
|
5793
|
+
stop(exportFailed("spawn"));
|
|
5794
|
+
resolve();
|
|
5795
|
+
});
|
|
5796
|
+
});
|
|
5797
|
+
const timer = setTimeout(() => stop(exportFailed("timeout", { timeoutMs })), timeoutMs);
|
|
5798
|
+
const onAbort = () => stop(cancelled());
|
|
5799
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5800
|
+
stopRunning = stop;
|
|
5801
|
+
let printed = null;
|
|
5802
|
+
try {
|
|
5803
|
+
printed = await printPage(cdp, url);
|
|
5804
|
+
await cdp.send("Browser.close").catch(() => void 0);
|
|
5805
|
+
return printed;
|
|
5806
|
+
} catch (error) {
|
|
5807
|
+
if (reason !== null) throw reason;
|
|
5808
|
+
if (error instanceof VdeError) throw error;
|
|
5809
|
+
throw exportFailed("protocol");
|
|
5810
|
+
} finally {
|
|
5811
|
+
clearTimeout(timer);
|
|
5812
|
+
signal?.removeEventListener("abort", onAbort);
|
|
5813
|
+
stopRunning = null;
|
|
5814
|
+
if (printed === null) kill(child);
|
|
5815
|
+
if (!await settledWithin(exited, EXIT_GRACE_MS)) {
|
|
5816
|
+
kill(child);
|
|
5817
|
+
await settledWithin(exited, EXIT_GRACE_MS);
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
};
|
|
5821
|
+
const run = async (documentId, revision, signal) => {
|
|
5822
|
+
const started = Date.now();
|
|
5823
|
+
const { record, entry } = documents.describeRevision(documentId, revision);
|
|
5824
|
+
if (entry.format !== "markdown") throw new VdeError("E_UNSUPPORTED_FORMAT", "Only Markdown documents can be exported as PDF.", { format: entry.format });
|
|
5825
|
+
const source = (await store.readBlob(entry.sourceSha256)).toString("utf8");
|
|
5826
|
+
const images = entry.assets.filter((asset) => asset.role === "image" || asset.role === "svg");
|
|
5827
|
+
const slot = randomBytes(16).toString("hex");
|
|
5828
|
+
const html = await printer.print({
|
|
5829
|
+
source,
|
|
5830
|
+
title: record.title,
|
|
5831
|
+
documentLogicalPath: entry.documentLogicalPath,
|
|
5832
|
+
images: images.map((asset) => asset.logicalPath),
|
|
5833
|
+
imageSlot: slot
|
|
5834
|
+
});
|
|
5835
|
+
const browser = options.findBrowser();
|
|
5836
|
+
const directory = await mkdtemp(join(tempRoot, TEMP_PREFIX$1));
|
|
5837
|
+
try {
|
|
5838
|
+
const page = join(directory, "document.html");
|
|
5839
|
+
await writePage(page, html, slot, images);
|
|
5840
|
+
const { product, pdf } = await print(browser, directory, pathToFileURL(page).href, signal);
|
|
5841
|
+
if (!isPdf(pdf)) throw exportFailed("output");
|
|
5842
|
+
options.onEvent?.("pdf.exported", {
|
|
5843
|
+
bytes: pdf.length,
|
|
5844
|
+
elapsedMs: Date.now() - started,
|
|
5845
|
+
browser: product.split("/", 1)[0] ?? ""
|
|
5846
|
+
});
|
|
5847
|
+
return pdf;
|
|
5848
|
+
} finally {
|
|
5849
|
+
await rm(directory, {
|
|
5850
|
+
recursive: true,
|
|
5851
|
+
force: true,
|
|
5852
|
+
maxRetries: 10,
|
|
5853
|
+
retryDelay: 100
|
|
5854
|
+
}).catch((error) => {
|
|
5855
|
+
options.onEvent?.("pdf.cleanup_failed", { code: error.code ?? "unknown" });
|
|
5856
|
+
});
|
|
5857
|
+
}
|
|
5858
|
+
};
|
|
5859
|
+
return {
|
|
5860
|
+
exportMarkdown(documentId, revision, signal) {
|
|
5861
|
+
if (closed) return Promise.reject(stopping());
|
|
5862
|
+
if (waiting >= MAX_WAITING) return Promise.reject(new VdeError("E_LIMIT_EXCEEDED", "Too many PDF exports are waiting.", {
|
|
5863
|
+
reason: "waiting",
|
|
5864
|
+
limit: MAX_WAITING
|
|
5865
|
+
}));
|
|
5866
|
+
let counted = true;
|
|
5867
|
+
const uncount = () => {
|
|
5868
|
+
if (!counted) return;
|
|
5869
|
+
counted = false;
|
|
5870
|
+
waiting -= 1;
|
|
5871
|
+
};
|
|
5872
|
+
waiting += 1;
|
|
5873
|
+
signal?.addEventListener("abort", uncount, { once: true });
|
|
5874
|
+
const result = queue.then(() => {
|
|
5875
|
+
signal?.removeEventListener("abort", uncount);
|
|
5876
|
+
uncount();
|
|
5877
|
+
if (closed) throw stopping();
|
|
5878
|
+
if (signal?.aborted === true) throw cancelled();
|
|
5879
|
+
return run(documentId, revision, signal);
|
|
5880
|
+
});
|
|
5881
|
+
queue = result.catch((error) => {
|
|
5882
|
+
if (error instanceof VdeError && error.code !== "E_INTERRUPTED") options.onEvent?.("pdf.failed", {
|
|
5883
|
+
code: error.code,
|
|
5884
|
+
reason: String(error.details["reason"] ?? error.details["source"] ?? "")
|
|
5885
|
+
});
|
|
5886
|
+
});
|
|
5887
|
+
return result;
|
|
5888
|
+
},
|
|
5889
|
+
async close() {
|
|
5890
|
+
closed = true;
|
|
5891
|
+
stopRunning?.(stopping());
|
|
5892
|
+
await queue;
|
|
5893
|
+
}
|
|
5894
|
+
};
|
|
5895
|
+
}
|
|
5896
|
+
//#endregion
|
|
5448
5897
|
//#region src/feedback/service.ts
|
|
5449
5898
|
function sha256$1(text) {
|
|
5450
5899
|
return createHash("sha256").update(text).digest("hex");
|
|
@@ -10806,6 +11255,14 @@ async function startManagementServer(deps, options = {}) {
|
|
|
10806
11255
|
})));
|
|
10807
11256
|
api.post("/documents/:id/focus", async (c) => ok(c, "documents.focus", await deps.documents.focus({ documentId: c.req.param("id") })));
|
|
10808
11257
|
api.post("/documents/:id/refresh", async (c) => ok(c, "documents.refresh", await deps.documents.refresh({ documentId: c.req.param("id") })));
|
|
11258
|
+
api.post("/documents/:id/pdf", async (c) => {
|
|
11259
|
+
const body = strictObject({ revision: revisionSchema }).parse(await c.req.json());
|
|
11260
|
+
const pdf = await deps.pdf.exportMarkdown(c.req.param("id"), body.revision, c.req.raw.signal);
|
|
11261
|
+
return c.body(new Uint8Array(pdf), 200, {
|
|
11262
|
+
"Content-Type": "application/pdf",
|
|
11263
|
+
"Content-Disposition": "attachment"
|
|
11264
|
+
});
|
|
11265
|
+
});
|
|
10809
11266
|
api.get("/search", async (c) => {
|
|
10810
11267
|
const { query, mode, limit, maxBytes, cursor } = c.req.query();
|
|
10811
11268
|
const documents = c.req.queries("document") ?? [];
|
|
@@ -13346,6 +13803,10 @@ function createParseService(options = {}) {
|
|
|
13346
13803
|
op: "render",
|
|
13347
13804
|
input
|
|
13348
13805
|
}),
|
|
13806
|
+
print: (input) => request({
|
|
13807
|
+
op: "print",
|
|
13808
|
+
input
|
|
13809
|
+
}),
|
|
13349
13810
|
async close() {
|
|
13350
13811
|
closed = true;
|
|
13351
13812
|
collector.close(new VdeError("E_DAEMON_STOPPING", "The daemon is stopping."));
|
|
@@ -13709,8 +14170,10 @@ async function startDaemon(options) {
|
|
|
13709
14170
|
let preview = null;
|
|
13710
14171
|
let watcher = null;
|
|
13711
14172
|
let parse = null;
|
|
14173
|
+
let printer = null;
|
|
13712
14174
|
let search = null;
|
|
13713
14175
|
let feedback = null;
|
|
14176
|
+
let pdf = null;
|
|
13714
14177
|
let announceStopping = () => void 0;
|
|
13715
14178
|
let lockTimer = null;
|
|
13716
14179
|
let heapWatch = null;
|
|
@@ -13753,9 +14216,11 @@ async function startDaemon(options) {
|
|
|
13753
14216
|
await step("drain", () => ipc?.drain());
|
|
13754
14217
|
announceStopping();
|
|
13755
14218
|
await step("management", () => management?.close(), SHUTDOWN_CLOSE_TIMEOUT_MS);
|
|
14219
|
+
await step("pdf", () => pdf?.close(), SHUTDOWN_CLOSE_TIMEOUT_MS);
|
|
13756
14220
|
await step("preview", () => preview?.close(), SHUTDOWN_CLOSE_TIMEOUT_MS);
|
|
13757
14221
|
await step("store", () => store?.close());
|
|
13758
14222
|
await step("parse", () => parse?.close(), SHUTDOWN_CLOSE_TIMEOUT_MS);
|
|
14223
|
+
await step("print", () => printer?.close(), SHUTDOWN_CLOSE_TIMEOUT_MS);
|
|
13759
14224
|
await step("search", () => search?.close(), SHUTDOWN_CLOSE_TIMEOUT_MS);
|
|
13760
14225
|
await step("ipc", () => ipc?.close());
|
|
13761
14226
|
let ownsLock = false;
|
|
@@ -13855,6 +14320,16 @@ async function startDaemon(options) {
|
|
|
13855
14320
|
})
|
|
13856
14321
|
});
|
|
13857
14322
|
pruneGrants = () => render.pruneClosed();
|
|
14323
|
+
printer = createParseService({ timeoutMs: LIMITS.printRenderTimeoutMs });
|
|
14324
|
+
const exporting = createPdfService({
|
|
14325
|
+
store: openedStore,
|
|
14326
|
+
documents,
|
|
14327
|
+
printer,
|
|
14328
|
+
findBrowser: () => findBrowser(environment),
|
|
14329
|
+
platform: environment.platform,
|
|
14330
|
+
onEvent: (event, fields) => logger.log(event, fields)
|
|
14331
|
+
});
|
|
14332
|
+
pdf = exporting;
|
|
13858
14333
|
preview = await startPreviewServer({
|
|
13859
14334
|
render,
|
|
13860
14335
|
frameAncestors: () => [managementOrigin, devOrigin].filter((origin) => origin !== null),
|
|
@@ -13870,6 +14345,7 @@ async function startDaemon(options) {
|
|
|
13870
14345
|
render,
|
|
13871
14346
|
search: searching,
|
|
13872
14347
|
feedback: answering,
|
|
14348
|
+
pdf: exporting,
|
|
13873
14349
|
previewOrigin,
|
|
13874
14350
|
webRoot: webRootPath(),
|
|
13875
14351
|
devOrigin,
|
|
@@ -13907,7 +14383,8 @@ async function startDaemon(options) {
|
|
|
13907
14383
|
const { collectGarbage } = diagnosticsParamsSchema.parse(params ?? {});
|
|
13908
14384
|
const workers = {
|
|
13909
14385
|
search: await search?.diagnostics(collectGarbage, stopRequested.signal) ?? null,
|
|
13910
|
-
parse: await parse?.diagnostics(collectGarbage, stopRequested.signal) ?? null
|
|
14386
|
+
parse: await parse?.diagnostics(collectGarbage, stopRequested.signal) ?? null,
|
|
14387
|
+
print: await printer?.diagnostics(collectGarbage, stopRequested.signal) ?? null
|
|
13911
14388
|
};
|
|
13912
14389
|
const heapUsedBytes = measureHeap(collectGarbage);
|
|
13913
14390
|
const activeResources = {};
|