vde-open 0.1.2 → 0.1.5
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 +15 -2
- package/dist/cli.js +2 -2
- package/dist/daemon.js +2 -2
- package/dist/{main-CqENdUsw.js → main-BZ8Pvrn8.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/package.json +1 -1
- package/dist/web/assets/index-DsIZiz-K.js +0 -97
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ To install from a clone of this repository (pins Node.js 24.21.0 in `mise.toml`)
|
|
|
30
30
|
pnpm install --frozen-lockfile
|
|
31
31
|
pnpm build
|
|
32
32
|
pnpm test:pack # builds artifacts/vde-open-<version>.tgz and verifies an install in a separate directory
|
|
33
|
-
bun add -g "$PWD/artifacts/vde-open-0.1.
|
|
33
|
+
bun add -g "$PWD/artifacts/vde-open-0.1.5.tgz" # use an absolute path: bun add -g resolves relative paths from its global install directory, not the current one
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
## Developing with a linked CLI
|
|
@@ -62,7 +62,7 @@ The same CLI is installed under two names. Both use the same state and daemon.
|
|
|
62
62
|
|
|
63
63
|
If you already have a different `vo` (another tool's command, an alias, and so on), installing never overwrites or deletes it.
|
|
64
64
|
|
|
65
|
-
- If the install target's bin directory (for `npm install -g`, npm's global bin) already has another `vo` file, npm stops with `EEXIST`. Do not use `--force`; it replaces the existing `vo`. Install with Bun instead, or into another prefix (for example `npm install -g --prefix ~/.local/vde-open ./artifacts/vde-open-0.1.
|
|
65
|
+
- If the install target's bin directory (for `npm install -g`, npm's global bin) already has another `vo` file, npm stops with `EEXIST`. Do not use `--force`; it replaces the existing `vo`. Install with Bun instead, or into another prefix (for example `npm install -g --prefix ~/.local/vde-open ./artifacts/vde-open-0.1.5.tgz`) and use `vde-open` from that bin directory.
|
|
66
66
|
- For a `vo` or alias elsewhere, whichever comes first on `PATH` runs. In that case, use `vde-open`. If you want a short name, define an alias in your shell (for example `alias vdo=vde-open`).
|
|
67
67
|
|
|
68
68
|
## Basic usage
|
|
@@ -115,6 +115,17 @@ Markdown is rendered with TanStack Markdown 1.0.0. It is not fully compatible wi
|
|
|
115
115
|
- Code is highlighted only for JS, JSX, TS, TSX, JSON, YAML, HTML, CSS, Bash, and Markdown. Code larger than 256 KiB and other languages are not highlighted.
|
|
116
116
|
- Documents that cannot be parsed within 2 seconds, or that have more than 100,000 elements or more than 64 levels of nesting, are shown as source.
|
|
117
117
|
|
|
118
|
+
## Exporting Markdown to PDF
|
|
119
|
+
|
|
120
|
+
"Export PDF" in the header of a Markdown document saves the shown revision as a PDF, without a print dialog.
|
|
121
|
+
|
|
122
|
+
- The PDF is an A4 document on a white background (the color theme of the management UI is not used). Every page has the document title in the header and the page number ("3 / 12") in the footer, and the headings become the PDF bookmarks.
|
|
123
|
+
- The daemon prints it with the Google Chrome or Microsoft Edge (131 or later) installed on the machine, run headless with a temporary profile that is removed afterwards. Nothing is downloaded. To use another Chromium-based browser, or one in another location, set `VDE_OPEN_BROWSER` to the absolute path of its executable for the daemon (after changing it, run `vo daemon restart`).
|
|
124
|
+
- The rules of the view apply: raw HTML is shown as text, only images registered for the document are included (others show their alternative text), and links to other local documents become plain text.
|
|
125
|
+
- The file name is the document's file name with `.pdf` (`README.md` → `README.pdf`). A document read from stdin uses its title.
|
|
126
|
+
- Fonts are the ones installed on the machine (San Francisco with Hiragino Sans on macOS, Segoe UI with Yu Gothic on Windows, Noto Sans CJK on Linux), so the PDF looks slightly different on each OS.
|
|
127
|
+
- HTML documents cannot be exported. Printing must finish within 60 seconds.
|
|
128
|
+
|
|
118
129
|
## Where the state is stored, and stopping
|
|
119
130
|
|
|
120
131
|
- The state (open documents, revisions, questions and answers) is stored here. Change it with `VDE_OPEN_HOME`.
|
|
@@ -143,6 +154,7 @@ The person answers in the answer panel of the management UI. The answers are sub
|
|
|
143
154
|
| Exit code 8 (cannot connect to or start the daemon) | Check with `vo daemon status`, and look for leftover files with `vo doctor` |
|
|
144
155
|
| Images or CSS are not shown | Open "Differences from the original document" and register them with `--assets-root` or `--asset` |
|
|
145
156
|
| Search does not find a document | Check with `vo list --json` that the document is open and its `searchState` is `ready` |
|
|
157
|
+
| Export PDF says Google Chrome or Microsoft Edge is needed | Install Chrome or Edge 131 or later, or set `VDE_OPEN_BROWSER` to the absolute path of a Chromium-based browser and run `vo daemon restart` |
|
|
146
158
|
|
|
147
159
|
## Verified scope
|
|
148
160
|
|
|
@@ -154,6 +166,7 @@ The person answers in the answer panel of the management UI. The answers are sub
|
|
|
154
166
|
| Browsers (macOS) | Chromium (Playwright's Chrome Headless Shell): the full suite is verified. Firefox 155 and WebKit 26.6 (Playwright 1.63.0): the view isolation, CSP, HTML bridge, and authentication tests (`pnpm test:e2e:cross`) are verified |
|
|
155
167
|
| Browsers (not verified) | Other UI interactions in Firefox and WebKit (search, answer panel, narrow screens, a list of 1,000 documents) are not verified |
|
|
156
168
|
| Markdown syntax | As described in "Markdown display limits" above. Full CommonMark and GFM are not verified |
|
|
169
|
+
| PDF export | macOS (locally): verified with Google Chrome 154 and Microsoft Edge 154. Linux on CI (`ubuntu-latest`): verified with the runner's Google Chrome (e2e). Windows: printing with a real browser is not verified (the pack smoke test checks rendering the print document) |
|
|
157
170
|
|
|
158
171
|
More details: [docs/performance.md](docs/performance.md) (measurements), [docs/architecture.md](docs/architecture.md) and [docs/security-model.md](docs/security-model.md) (design). Development records (in Japanese): [docs/implementation-status.md](docs/implementation-status.md), [docs/dependency-validation.md](docs/dependency-validation.md), and [docs/adr/](docs/adr/).
|
|
159
172
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as decodeStateFile, D as daemonEntryPath, E as resolveStateRoot, O as referencedBlobs, S as renameWithRetry, T as resolveRuntimeLocation, a as DAEMON_LOCK_NAME, b as proofMatches, c as readIpcKey, d as inspectOwner, f as readCurrentLock, g as encodeFrame, h as FrameDecoder, i as requirePrivateDirectoryIfExists, k as version, l as readPointer, m as systemProcessProbe, n as ensurePrivateDirectory, o as POINTER_FILE, p as releaseLock, r as inspectPrivateDirectory, s as START_LOCK_NAME, t as startDaemon, u as acquireLock, v as computeProof, w as currentPathEnvironment, x as removeWithRetry, y as createNonce } from "./main-
|
|
3
|
-
import { A as successEnvelope, C as VdeError, D as ExitCode, E as isVdeError, O as envelopeSchema, T as isErrorCode, Y as LIMITS, k as errorEnvelope, w as exitCodeForError } from "./search-index-
|
|
2
|
+
import { C as decodeStateFile, D as daemonEntryPath, E as resolveStateRoot, O as referencedBlobs, S as renameWithRetry, T as resolveRuntimeLocation, a as DAEMON_LOCK_NAME, b as proofMatches, c as readIpcKey, d as inspectOwner, f as readCurrentLock, g as encodeFrame, h as FrameDecoder, i as requirePrivateDirectoryIfExists, k as version, l as readPointer, m as systemProcessProbe, n as ensurePrivateDirectory, o as POINTER_FILE, p as releaseLock, r as inspectPrivateDirectory, s as START_LOCK_NAME, t as startDaemon, u as acquireLock, v as computeProof, w as currentPathEnvironment, x as removeWithRetry, y as createNonce } from "./main-BZ8Pvrn8.js";
|
|
3
|
+
import { A as successEnvelope, C as VdeError, D as ExitCode, E as isVdeError, O as envelopeSchema, T as isErrorCode, Y as LIMITS, k as errorEnvelope, w as exitCodeForError } from "./search-index-B7DqaWh6.js";
|
|
4
4
|
import fs, { fstatSync } from "node:fs";
|
|
5
5
|
import { lstat, readFile, readdir, stat as stat$1, writeFile } from "node:fs/promises";
|
|
6
6
|
import path, { join, resolve } from "node:path";
|
package/dist/daemon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { k as version, t as startDaemon, w as currentPathEnvironment } from "./main-
|
|
2
|
-
import { E as isVdeError } from "./search-index-
|
|
1
|
+
import { k as version, t as startDaemon, w as currentPathEnvironment } from "./main-BZ8Pvrn8.js";
|
|
2
|
+
import { E as isVdeError } from "./search-index-B7DqaWh6.js";
|
|
3
3
|
//#region src/daemon.ts
|
|
4
4
|
function notifyParent(message) {
|
|
5
5
|
if (!process.send) return;
|
|
@@ -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.5";
|
|
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 = {};
|