@poe-platform/safe-bash 0.1.710 → 0.1.712
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 +21 -1
- package/dist/safe-bash/commands/fmt/index.d.ts +2 -0
- package/dist/safe-bash/commands/fmt/index.d.ts.map +1 -0
- package/dist/safe-bash/commands/fmt/index.js +2 -0
- package/dist/safe-bash/commands/fmt/index.js.map +1 -0
- package/dist/safe-bash/commands/fmt.d.ts +1 -2
- package/dist/safe-bash/commands/fmt.d.ts.map +1 -1
- package/dist/safe-bash/commands/fmt.js +1 -838
- package/dist/safe-bash/commands/fmt.js.map +1 -1
- package/dist/safe-bash/commands/time-env/printenv.d.ts +1 -1
- package/dist/safe-bash/commands/time-env/printenv.d.ts.map +1 -1
- package/dist/safe-bash/commands/time-env/sleep.d.ts +1 -1
- package/dist/safe-bash/commands/time-env/sleep.d.ts.map +1 -1
- package/dist/safe-bash/core.browser.js +155 -2099
- package/dist/safe-bash/core.browser.js.map +3 -3
- package/dist/safe-bash/playwright/private-target-transport.d.ts.map +1 -1
- package/dist/safe-bash/playwright/private-target-transport.js +18 -2
- package/dist/safe-bash/playwright/private-target-transport.js.map +1 -1
- package/dist/safe-bash/playwright/profile.d.ts +3 -1
- package/dist/safe-bash/playwright/profile.d.ts.map +1 -1
- package/dist/safe-bash/playwright/profile.js +3 -3
- package/dist/safe-bash/playwright/profile.js.map +1 -1
- package/dist/safe-bash-command-exiftool/index.js +1401 -6
- package/dist/safe-bash-command-exiftool/index.js.map +7 -0
- package/dist/safe-bash-command-fmt/arguments.d.ts +8 -0
- package/dist/safe-bash-command-fmt/command.d.ts +20 -0
- package/dist/safe-bash-command-fmt/contracts.d.ts +42 -0
- package/dist/safe-bash-command-fmt/engine.d.ts +20 -0
- package/dist/safe-bash-command-fmt/index.d.ts +5 -0
- package/dist/safe-bash-command-fmt/index.js +2419 -0
- package/dist/safe-bash-command-fmt/index.js.map +7 -0
- package/dist/safe-bash-command-fmt/sdk.d.ts +15 -0
- package/dist/safe-bash-command-wkhtmltopdf/index.js +1458 -7
- package/dist/safe-bash-command-wkhtmltopdf/index.js.map +7 -0
- package/dist/safe-bash-contracts/index.d.ts +8 -0
- package/dist/safe-bash-contracts/index.js +8 -0
- package/dist/safe-playwright-cloudflare/browser-run-code.js +15 -1
- package/package.json +6 -2
- package/dist/safe-bash-command-exiftool/argfiles.js +0 -163
- package/dist/safe-bash-command-exiftool/arguments.js +0 -128
- package/dist/safe-bash-command-exiftool/command.js +0 -324
- package/dist/safe-bash-command-exiftool/csv.js +0 -78
- package/dist/safe-bash-command-exiftool/paths.js +0 -18
- package/dist/safe-bash-command-exiftool/png.js +0 -308
- package/dist/safe-bash-command-exiftool/publication.js +0 -80
- package/dist/safe-bash-command-exiftool/registry.js +0 -14
- package/dist/safe-bash-command-exiftool/resources.js +0 -29
- package/dist/safe-bash-command-exiftool/scalar.js +0 -103
- package/dist/safe-bash-command-exiftool/sdk.js +0 -113
- package/dist/safe-bash-command-exiftool/shifts.js +0 -29
- package/dist/safe-bash-command-wkhtmltopdf/command.js +0 -373
- package/dist/safe-bash-command-wkhtmltopdf/engine.js +0 -99
- package/dist/safe-bash-command-wkhtmltopdf/errors.js +0 -11
- package/dist/safe-bash-command-wkhtmltopdf/limits.js +0 -69
- package/dist/safe-bash-command-wkhtmltopdf/outcome.js +0 -38
- package/dist/safe-bash-command-wkhtmltopdf/parser.js +0 -146
- package/dist/safe-bash-command-wkhtmltopdf/resources.js +0 -284
- package/dist/safe-bash-command-wkhtmltopdf/settings.js +0 -35
- package/dist/safe-bash-command-wkhtmltopdf/switches.js +0 -127
- package/dist/safe-bash-command-wkhtmltopdf/values.js +0 -66
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/** Numeric shift grammar differs from conservative JSON token admission. */
|
|
2
|
-
export function isNumericShift(value) {
|
|
3
|
-
const end = value.endsWith("\n") ? value.length - 1 : value.length;
|
|
4
|
-
let index = value[0] === "+" || value[0] === "-" ? 1 : 0;
|
|
5
|
-
const digit = () => index < end && value.charCodeAt(index) >= 48 && value.charCodeAt(index) <= 57;
|
|
6
|
-
const start = index;
|
|
7
|
-
while (digit())
|
|
8
|
-
index++;
|
|
9
|
-
let digits = index - start;
|
|
10
|
-
if (value[index] === "." || value[index] === ",") {
|
|
11
|
-
const start = ++index;
|
|
12
|
-
while (digit())
|
|
13
|
-
index++;
|
|
14
|
-
digits += index - start;
|
|
15
|
-
}
|
|
16
|
-
if (!digits)
|
|
17
|
-
return false;
|
|
18
|
-
if (value[index] === "e" || value[index] === "E") {
|
|
19
|
-
index++;
|
|
20
|
-
if (value[index] === "+" || value[index] === "-")
|
|
21
|
-
index++;
|
|
22
|
-
const start = index;
|
|
23
|
-
while (digit())
|
|
24
|
-
index++;
|
|
25
|
-
if (index === start)
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
return index === end;
|
|
29
|
-
}
|
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
import { commandRuntimeIdentity, getCommandArguments } from "../safe-bash-contracts/command.js";
|
|
2
|
-
import { readBytes, writeBytes } from "../safe-bash-contracts/io.js";
|
|
3
|
-
import { shellValueByteLength } from "../safe-bash-contracts/value.js";
|
|
4
|
-
import { FsError } from "../safe-bash-contracts/errors.js";
|
|
5
|
-
import { createOutputOperation } from "../safe-bash-contracts/output.js";
|
|
6
|
-
import { basename, compareEntries, dirname, resolvePath } from "@poe-platform/safe-fs/core";
|
|
7
|
-
import { WkhtmltopdfError } from "./errors.js";
|
|
8
|
-
import { requireRendererFeatures } from "./engine.js";
|
|
9
|
-
import { ParseBudget } from "./limits.js";
|
|
10
|
-
import { conversionOutcome } from "./outcome.js";
|
|
11
|
-
import { parseInvocation, tokenizeBatchLine } from "./parser.js";
|
|
12
|
-
import { withResources } from "./resources.js";
|
|
13
|
-
export const wkhtmltopdfLimits = Object.freeze({
|
|
14
|
-
parse: Object.freeze({ maxArguments: 1024, maxTextBytes: 65536, maxObjects: 64, maxWork: 1048576 }),
|
|
15
|
-
resources: Object.freeze({ maxInputBytes: 16777216, maxDecodedBytes: 16777216, maxRetainedBytes: 33554432, maxWork: 67108864, maxResources: 128 }),
|
|
16
|
-
maxOutputBytes: 16777216, maxOutputChunks: 65536, maxBatchJobs: 128,
|
|
17
|
-
});
|
|
18
|
-
const encoder = new TextEncoder();
|
|
19
|
-
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
20
|
-
const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
|
|
21
|
-
const byteType = Object.getOwnPropertyDescriptor(typedArrayPrototype, Symbol.toStringTag).get;
|
|
22
|
-
const byteLength = Object.getOwnPropertyDescriptor(typedArrayPrototype, "byteLength").get;
|
|
23
|
-
/** The SDK uses exactly the CLI's literal argument model, signals and destinations. */
|
|
24
|
-
export async function runWkhtmltopdf(context, options) {
|
|
25
|
-
context.signal.throwIfAborted();
|
|
26
|
-
const controller = new AbortController();
|
|
27
|
-
const signal = controller.signal;
|
|
28
|
-
let opening;
|
|
29
|
-
let closing;
|
|
30
|
-
let cleanupPromise;
|
|
31
|
-
let resourceRun;
|
|
32
|
-
let stdout;
|
|
33
|
-
let stderr;
|
|
34
|
-
const retire = () => {
|
|
35
|
-
if (!opening)
|
|
36
|
-
return Promise.resolve();
|
|
37
|
-
closing ??= opening.then(document => document.close(), () => { });
|
|
38
|
-
return closing;
|
|
39
|
-
};
|
|
40
|
-
const onAbort = () => controller.abort(context.signal.reason);
|
|
41
|
-
const cleanup = () => {
|
|
42
|
-
cleanupPromise ??= (async () => {
|
|
43
|
-
controller.abort(new WkhtmltopdfError("INVALID_VALUE", "Invocation has ended"));
|
|
44
|
-
context.signal.removeEventListener("abort", onAbort);
|
|
45
|
-
const drained = await Promise.allSettled([
|
|
46
|
-
retire(), resourceRun?.then(() => { }, () => { }), stdout?.close(), stderr?.close(),
|
|
47
|
-
]);
|
|
48
|
-
const failures = drained.filter(value => value.status === "rejected").map(value => value.reason);
|
|
49
|
-
if (failures.length)
|
|
50
|
-
throw new AggregateError(failures, "Invocation cleanup failed");
|
|
51
|
-
})();
|
|
52
|
-
return cleanupPromise;
|
|
53
|
-
};
|
|
54
|
-
// Registration precedes argv copies, input opens, renderer acquisition and output.
|
|
55
|
-
context.registerCleanup?.(cleanup);
|
|
56
|
-
let primary = false;
|
|
57
|
-
let failure;
|
|
58
|
-
let result;
|
|
59
|
-
try {
|
|
60
|
-
context.signal.addEventListener("abort", onAbort, { once: true });
|
|
61
|
-
if (context.signal.aborted)
|
|
62
|
-
onAbort();
|
|
63
|
-
signal.throwIfAborted();
|
|
64
|
-
stdout = createOutputOperation({ signal, registerCleanup: callback => context.registerCleanup?.(callback) }, context.stdout);
|
|
65
|
-
stderr = createOutputOperation({ signal, registerCleanup: callback => context.registerCleanup?.(callback) }, context.stderr);
|
|
66
|
-
const limits = Object.freeze({
|
|
67
|
-
...options.limits, parse: Object.freeze({ ...options.limits.parse }), resources: Object.freeze({ ...options.limits.resources }),
|
|
68
|
-
});
|
|
69
|
-
for (const value of [limits.maxOutputBytes, limits.maxOutputChunks, limits.maxBatchJobs]) {
|
|
70
|
-
if (!Number.isSafeInteger(value) || value < 1)
|
|
71
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Command limits must be positive safe integers");
|
|
72
|
-
}
|
|
73
|
-
for (const key of ["maxInputBytes", "maxDecodedBytes", "maxRetainedBytes", "maxWork", "maxResources"]) {
|
|
74
|
-
if (!Number.isSafeInteger(limits.resources[key]) || limits.resources[key] < 1) {
|
|
75
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Resource limits must be positive safe integers");
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
// Admit text/argument bounds before materializing carrier byte copies.
|
|
79
|
-
const admission = new ParseBudget({ limits: limits.parse, signal });
|
|
80
|
-
admission.admitArguments(context.args.length);
|
|
81
|
-
for (const arg of context.args)
|
|
82
|
-
admission.admitText(arg);
|
|
83
|
-
const carrier = context.argumentValues === undefined ? undefined : getCommandArguments(context);
|
|
84
|
-
const argv = [];
|
|
85
|
-
let argumentBytes = 0;
|
|
86
|
-
for (let index = 0; index < context.args.length; index++) {
|
|
87
|
-
signal.throwIfAborted();
|
|
88
|
-
if (carrier === undefined || typeof carrier.values[index] === "string") {
|
|
89
|
-
argv.push(context.args[index]);
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
const bytes = carrier.bytes(index);
|
|
93
|
-
if (bytes.byteLength > limits.parse.maxTextBytes - argumentBytes)
|
|
94
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "CLI argument byte limit exceeded");
|
|
95
|
-
argumentBytes += bytes.byteLength;
|
|
96
|
-
try {
|
|
97
|
-
argv.push(decoder.decode(bytes));
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "CLI arguments must be valid UTF-8");
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
const parse = (args, batchJob = false) => parseInvocation(args, { limits: limits.parse, signal, endOfOptions: true, batchJob });
|
|
104
|
-
const initial = parse(argv);
|
|
105
|
-
if (initial.mode === "information") {
|
|
106
|
-
let text;
|
|
107
|
-
if (initial.global.action === "help" || initial.global.action === "extended-help") {
|
|
108
|
-
text = "Usage: wkhtmltopdf [options] [page|cover input|toc]... output\nStatic first-party renderer requires an explicit binding. Input/output '-' use stdin/stdout.\n";
|
|
109
|
-
}
|
|
110
|
-
else if (initial.global.action === "version") {
|
|
111
|
-
text = "wkhtmltopdf safe static adapter (source 024b2b2bb459dd904d15b911d04c6df4ff2c9031; Qt compatibility unqualified)\n";
|
|
112
|
-
}
|
|
113
|
-
else
|
|
114
|
-
throw new WkhtmltopdfError("UNSUPPORTED_CAPABILITY", "Information action is unavailable in the static profile", initial.global.action);
|
|
115
|
-
if (shellValueByteLength(text) > limits.maxOutputBytes) {
|
|
116
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "Information output byte limit exceeded");
|
|
117
|
-
}
|
|
118
|
-
await writeBytes(stdout.output, encoder.encode(text), signal);
|
|
119
|
-
result = { kind: "information", exitCode: 0 };
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
const renderer = options.renderer;
|
|
123
|
-
if (!renderer)
|
|
124
|
-
throw new WkhtmltopdfError("UNSUPPORTED_CAPABILITY", "A qualified first-party static renderer binding is required");
|
|
125
|
-
requireRendererFeatures(renderer.profile, []);
|
|
126
|
-
let outputBytes = 0;
|
|
127
|
-
let outputChunks = 0;
|
|
128
|
-
let jobs = 0;
|
|
129
|
-
let inputBytes = 0;
|
|
130
|
-
let currentInputs = [];
|
|
131
|
-
let produced;
|
|
132
|
-
// Capture the drain promise before synchronous host opens can call cleanup.
|
|
133
|
-
resourceRun = Promise.resolve().then(() => withResources({
|
|
134
|
-
limits: limits.resources, signal,
|
|
135
|
-
vfs: async (reference, readSignal) => {
|
|
136
|
-
if (reference === "vfs:output") {
|
|
137
|
-
if (!produced)
|
|
138
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "No renderer output is active");
|
|
139
|
-
const document = produced;
|
|
140
|
-
return {
|
|
141
|
-
chunks: (async function* () {
|
|
142
|
-
for await (const chunk of document.chunks) {
|
|
143
|
-
readSignal.throwIfAborted();
|
|
144
|
-
if (byteType.call(chunk) !== "Uint8Array")
|
|
145
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Renderer must produce byte chunks");
|
|
146
|
-
const length = byteLength.call(chunk);
|
|
147
|
-
if (++outputChunks > limits.maxOutputChunks || length > limits.maxOutputBytes - outputBytes) {
|
|
148
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "PDF output stage limit exceeded");
|
|
149
|
-
}
|
|
150
|
-
outputBytes += length;
|
|
151
|
-
yield chunk;
|
|
152
|
-
}
|
|
153
|
-
})(),
|
|
154
|
-
close: retire,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
const index = Number(reference.slice("vfs:input/".length));
|
|
158
|
-
const input = currentInputs[index];
|
|
159
|
-
if (input === undefined)
|
|
160
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Unknown invocation input identity");
|
|
161
|
-
if (input === "-")
|
|
162
|
-
return {
|
|
163
|
-
chunks: (async function* () {
|
|
164
|
-
for await (const chunk of readBytes(context.stdin, readSignal)) {
|
|
165
|
-
inputBytes += byteLength.call(chunk);
|
|
166
|
-
context.inputBudget?.check(inputBytes);
|
|
167
|
-
yield chunk;
|
|
168
|
-
}
|
|
169
|
-
})(),
|
|
170
|
-
async close() { },
|
|
171
|
-
};
|
|
172
|
-
if (!context.fs.openReadFile)
|
|
173
|
-
throw new WkhtmltopdfError("UNSUPPORTED_CAPABILITY", "VFS requires bounded read handles");
|
|
174
|
-
const handle = await context.fs.openReadFile(resolvePath(context.cwd, input), { signal: readSignal });
|
|
175
|
-
let position = 0;
|
|
176
|
-
return {
|
|
177
|
-
chunks: (async function* () {
|
|
178
|
-
for (;;) {
|
|
179
|
-
readSignal.throwIfAborted();
|
|
180
|
-
const chunk = await handle.read(position, Math.min(65536, limits.resources.maxInputBytes, limits.resources.maxDecodedBytes, limits.resources.maxRetainedBytes, Math.max(1, context.inputBudget?.maxBytes ?? limits.resources.maxInputBytes)), { signal: readSignal });
|
|
181
|
-
if (byteType.call(chunk) !== "Uint8Array")
|
|
182
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "VFS must produce byte chunks");
|
|
183
|
-
const length = byteLength.call(chunk);
|
|
184
|
-
if (!length)
|
|
185
|
-
return;
|
|
186
|
-
inputBytes += length;
|
|
187
|
-
context.inputBudget?.check(inputBytes);
|
|
188
|
-
position += length;
|
|
189
|
-
yield chunk;
|
|
190
|
-
}
|
|
191
|
-
})(),
|
|
192
|
-
close: () => handle.close(),
|
|
193
|
-
};
|
|
194
|
-
},
|
|
195
|
-
}, async (resources) => {
|
|
196
|
-
const convert = async (job, batch) => {
|
|
197
|
-
if (++jobs > limits.maxBatchJobs)
|
|
198
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "Batch job limit exceeded");
|
|
199
|
-
currentInputs = job.objects.map(object => {
|
|
200
|
-
if (object.input === null)
|
|
201
|
-
throw new WkhtmltopdfError("UNSUPPORTED_CAPABILITY", "TOC requires a qualified outline/XSLT engine");
|
|
202
|
-
if (batch && object.input === "-")
|
|
203
|
-
throw new WkhtmltopdfError("UNSUPPORTED_CAPABILITY", "Batch conversion cannot share stdin with HTML input");
|
|
204
|
-
return object.input;
|
|
205
|
-
});
|
|
206
|
-
let destination;
|
|
207
|
-
if (job.output !== "-") {
|
|
208
|
-
if (!context.fs.writeFileConditional) {
|
|
209
|
-
throw new WkhtmltopdfError("UNSUPPORTED_CAPABILITY", "VFS requires conditional file publication");
|
|
210
|
-
}
|
|
211
|
-
const requested = resolvePath(context.cwd, job.output);
|
|
212
|
-
const parentPath = await context.fs.realpath(dirname(requested), { signal });
|
|
213
|
-
const path = resolvePath(parentPath, basename(requested));
|
|
214
|
-
const parent = await context.fs.stat(parentPath, { signal });
|
|
215
|
-
let expected = null;
|
|
216
|
-
try {
|
|
217
|
-
expected = await context.fs.lstat(path, { signal });
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
if (!(error instanceof FsError && error.code === "ENOENT"))
|
|
221
|
-
throw error;
|
|
222
|
-
}
|
|
223
|
-
// Refuse final symlinks rather than granting authority over their target.
|
|
224
|
-
if (expected !== null && expected.type !== "file") {
|
|
225
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Output must be a regular VFS file or absent");
|
|
226
|
-
}
|
|
227
|
-
for (let index = 0; index < currentInputs.length; index++) {
|
|
228
|
-
const input = currentInputs[index];
|
|
229
|
-
if (input === "-")
|
|
230
|
-
continue;
|
|
231
|
-
const source = await context.fs.realpath(resolvePath(context.cwd, input), { signal });
|
|
232
|
-
currentInputs[index] = source;
|
|
233
|
-
if (source === path || (expected !== null &&
|
|
234
|
-
await compareEntries(context.fs, source, context.fs, path, { signal }) !== "distinct")) {
|
|
235
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Input and output VFS identities must be distinct");
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
destination = { path, condition: { parent, expected, signal } };
|
|
239
|
-
}
|
|
240
|
-
const inputs = [];
|
|
241
|
-
for (let index = 0; index < currentInputs.length; index++)
|
|
242
|
-
inputs.push(await resources.load("vfs:input/" + index));
|
|
243
|
-
signal.throwIfAborted();
|
|
244
|
-
opening = Promise.resolve().then(() => {
|
|
245
|
-
signal.throwIfAborted();
|
|
246
|
-
return renderer.open({ job, inputs, signal, limits });
|
|
247
|
-
});
|
|
248
|
-
closing = undefined;
|
|
249
|
-
const document = await opening;
|
|
250
|
-
produced = document;
|
|
251
|
-
signal.throwIfAborted();
|
|
252
|
-
const outcome = conversionOutcome({
|
|
253
|
-
mode: batch ? "batch" : "single", success: document.success, errorCode: document.errorCode,
|
|
254
|
-
...(document.networkErrorName === undefined ? {} : { networkErrorName: document.networkErrorName }),
|
|
255
|
-
});
|
|
256
|
-
let pdf;
|
|
257
|
-
if (outcome.exitCode === 0) {
|
|
258
|
-
// The same ledger covers retained HTML, copied PDF chunks, assembly and work.
|
|
259
|
-
pdf = await resources.load("vfs:output");
|
|
260
|
-
}
|
|
261
|
-
await retire();
|
|
262
|
-
opening = undefined;
|
|
263
|
-
closing = undefined;
|
|
264
|
-
produced = undefined;
|
|
265
|
-
signal.throwIfAborted();
|
|
266
|
-
if (outcome.exitCode === 0) {
|
|
267
|
-
if (job.output === "-")
|
|
268
|
-
await writeBytes(stdout.output, pdf, signal);
|
|
269
|
-
else
|
|
270
|
-
await context.fs.writeFileConditional(destination.path, pdf, destination.condition);
|
|
271
|
-
}
|
|
272
|
-
else
|
|
273
|
-
await diagnostic({ ...context, stderr: stderr.output }, outcome, signal);
|
|
274
|
-
return outcome;
|
|
275
|
-
};
|
|
276
|
-
let outcome = { exitCode: 0, diagnostic: null };
|
|
277
|
-
if (initial.mode === "batch") {
|
|
278
|
-
// Shared resource accounting bounds batch text as well as every job input.
|
|
279
|
-
currentInputs = ["-"];
|
|
280
|
-
const bytes = await resources.load("vfs:input/0");
|
|
281
|
-
let text;
|
|
282
|
-
try {
|
|
283
|
-
text = decoder.decode(bytes);
|
|
284
|
-
}
|
|
285
|
-
catch {
|
|
286
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Batch input must be valid UTF-8");
|
|
287
|
-
}
|
|
288
|
-
let start = 0;
|
|
289
|
-
for (let end = 0; end <= text.length; end++) {
|
|
290
|
-
signal.throwIfAborted();
|
|
291
|
-
if (end !== text.length && text[end] !== "\n")
|
|
292
|
-
continue;
|
|
293
|
-
if (end === text.length && start === end)
|
|
294
|
-
break;
|
|
295
|
-
const args = tokenizeBatchLine(text.slice(start, end), { limits: limits.parse, signal });
|
|
296
|
-
const job = parse([...argv, ...args], true);
|
|
297
|
-
if (job.mode !== "conversion")
|
|
298
|
-
throw new WkhtmltopdfError("UNSUPPORTED_CAPABILITY", "Batch lines must describe conversion jobs");
|
|
299
|
-
outcome = await convert(job, true);
|
|
300
|
-
if (outcome.exitCode !== 0)
|
|
301
|
-
break;
|
|
302
|
-
start = end + 1;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
else
|
|
306
|
-
outcome = await convert(initial, false);
|
|
307
|
-
return { kind: "conversion", exitCode: outcome.exitCode, jobs, outcome, usage: resources.usage };
|
|
308
|
-
}));
|
|
309
|
-
result = await resourceRun;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
catch (error) {
|
|
313
|
-
if (context.signal.aborted) {
|
|
314
|
-
primary = true;
|
|
315
|
-
failure = context.signal.reason;
|
|
316
|
-
}
|
|
317
|
-
else if (signal.aborted) {
|
|
318
|
-
primary = true;
|
|
319
|
-
failure = signal.reason;
|
|
320
|
-
}
|
|
321
|
-
else if (error instanceof WkhtmltopdfError || error instanceof FsError) {
|
|
322
|
-
try {
|
|
323
|
-
const option = error instanceof WkhtmltopdfError ? error.option : undefined;
|
|
324
|
-
await writeBytes(stderr.output, encoder.encode(`wkhtmltopdf: ${error.code}${option ? ` (${option})` : ""}: ${error.message}\n`), signal);
|
|
325
|
-
result = { kind: "rejected", exitCode: 1, code: error.code };
|
|
326
|
-
}
|
|
327
|
-
catch (writeError) {
|
|
328
|
-
primary = true;
|
|
329
|
-
failure = writeError;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
primary = true;
|
|
334
|
-
failure = error;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
finally {
|
|
338
|
-
try {
|
|
339
|
-
await cleanup();
|
|
340
|
-
}
|
|
341
|
-
catch (error) {
|
|
342
|
-
if (primary)
|
|
343
|
-
failure = new AggregateError([failure, error], "Invocation and cleanup failed");
|
|
344
|
-
else {
|
|
345
|
-
primary = true;
|
|
346
|
-
failure = error;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
if (primary)
|
|
351
|
-
throw failure;
|
|
352
|
-
return result;
|
|
353
|
-
}
|
|
354
|
-
async function diagnostic(context, outcome, signal) {
|
|
355
|
-
const value = outcome.diagnostic;
|
|
356
|
-
if (!value)
|
|
357
|
-
return;
|
|
358
|
-
const message = value.kind === "http" ? `Exit with code ${outcome.exitCode} due to http error: ${value.code} ${value.description}` :
|
|
359
|
-
value.kind === "network" ? `Exit with code 1 due to network error: ${value.name ?? "UnqualifiedNetworkError"}` : "Exit with code 1, due to unknown error.";
|
|
360
|
-
await writeBytes(context.stderr, encoder.encode(message + "\n"), signal);
|
|
361
|
-
}
|
|
362
|
-
export function createWkhtmltopdfCommand(options = { limits: wkhtmltopdfLimits }) {
|
|
363
|
-
return Object.freeze({
|
|
364
|
-
name: "wkhtmltopdf", runtimeIdentity: commandRuntimeIdentity,
|
|
365
|
-
description: "Bounded static HTML-to-PDF adapter with an explicit first-party renderer",
|
|
366
|
-
execute: (context) => runWkhtmltopdf(context, options),
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
export const wkhtmltopdfCommand = createWkhtmltopdfCommand();
|
|
370
|
-
export function wkhtmltopdfCommands(options = { limits: wkhtmltopdfLimits }) {
|
|
371
|
-
const command = createWkhtmltopdfCommand(options);
|
|
372
|
-
return { name: "wkhtmltopdf-commands", setup(host) { host.commands.register(command); } };
|
|
373
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { WkhtmltopdfError } from "./errors.js";
|
|
2
|
-
const rendererFeatures = [
|
|
3
|
-
"html5", "computed-css", "selectors", "css-units", "block-inline", "font-shaping",
|
|
4
|
-
"lists", "images", "table-spans", "pagination", "break-rules", "widows-orphans",
|
|
5
|
-
"positioning", "page-furniture", "flex", "grid",
|
|
6
|
-
];
|
|
7
|
-
/** Admission only. Never substitutes another layout mode for an absent feature. */
|
|
8
|
-
export function requireRendererFeatures(profile, required) {
|
|
9
|
-
if (typeof profile.id !== "string" || !profile.id || profile.id.length > 128 ||
|
|
10
|
-
profile.features.length > rendererFeatures.length || required.length > rendererFeatures.length) {
|
|
11
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Expected a bounded renderer profile and feature requirements");
|
|
12
|
-
}
|
|
13
|
-
for (const feature of [...profile.features, ...required]) {
|
|
14
|
-
if (!rendererFeatures.includes(feature)) {
|
|
15
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Unknown renderer feature");
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
for (const feature of required) {
|
|
19
|
-
if (!profile.features.includes(feature)) {
|
|
20
|
-
throw new WkhtmltopdfError("UNSUPPORTED_CAPABILITY", `Renderer profile ${profile.id} does not support ${feature}`);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Pure final-layout accounting, not HTML layout or PDF emission. Admission completes
|
|
26
|
-
* before retaining output records. No resources are acquired or cleanup hooks needed.
|
|
27
|
-
*/
|
|
28
|
-
export function planPageSequence(objects, options) {
|
|
29
|
-
const checkCancellation = () => { if (options.signal?.aborted)
|
|
30
|
-
throw options.signal.reason; };
|
|
31
|
-
checkCancellation();
|
|
32
|
-
const { maxObjects, maxPhysicalPages, maxWork } = options.limits;
|
|
33
|
-
for (const value of [maxObjects, maxPhysicalPages, maxWork, options.copies]) {
|
|
34
|
-
if (!Number.isSafeInteger(value) || value < 1) {
|
|
35
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Page sequence limits and copies must be positive safe integers");
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
if (typeof options.collate !== "boolean" || !Number.isInteger(options.pageOffset) ||
|
|
39
|
-
options.pageOffset < -2147483648 || options.pageOffset > 2147483647) {
|
|
40
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Expected boolean collate and int32 page offset");
|
|
41
|
-
}
|
|
42
|
-
if (objects.length > maxObjects || objects.length > maxWork) {
|
|
43
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "Page sequence object/work limit exceeded");
|
|
44
|
-
}
|
|
45
|
-
let outlineTotal = 0;
|
|
46
|
-
let logicalTotal = 0;
|
|
47
|
-
// Division prevents multiplication overflow during copies admission.
|
|
48
|
-
const sourcePageLimit = Math.floor(Math.min(maxPhysicalPages, maxWork - objects.length) / options.copies);
|
|
49
|
-
for (const object of objects) {
|
|
50
|
-
checkCancellation();
|
|
51
|
-
if (!Number.isSafeInteger(object.physicalPages) || object.physicalPages < 0 || typeof object.pagesCount !== "boolean") {
|
|
52
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Expected a nonnegative safe page count and boolean pagesCount");
|
|
53
|
-
}
|
|
54
|
-
if (object.physicalPages > sourcePageLimit - outlineTotal) {
|
|
55
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "Page sequence physical-page/work limit exceeded");
|
|
56
|
-
}
|
|
57
|
-
outlineTotal += object.physicalPages;
|
|
58
|
-
if (object.pagesCount)
|
|
59
|
-
logicalTotal += object.physicalPages;
|
|
60
|
-
}
|
|
61
|
-
if (!Number.isSafeInteger(logicalTotal + Math.abs(options.pageOffset) + 1)) {
|
|
62
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "Logical page number exceeds checked precision");
|
|
63
|
-
}
|
|
64
|
-
const passes = options.collate ? options.copies : 1;
|
|
65
|
-
if (outlineTotal > 0) {
|
|
66
|
-
const remainingWork = maxWork - objects.length - outlineTotal * options.copies;
|
|
67
|
-
if (objects.length > Math.floor(remainingWork / passes)) {
|
|
68
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "Page sequence repeated traversal work limit exceeded");
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
const pages = [];
|
|
72
|
-
const emit = (objectIndex, pageIndex, copyIndex, logicalPage, outlinePage) => {
|
|
73
|
-
checkCancellation();
|
|
74
|
-
pages.push({ objectIndex, pageIndex, copyIndex, outputPage: pages.length + 1, logicalPage, outlinePage });
|
|
75
|
-
};
|
|
76
|
-
// Empty documents must not loop copies times without charging output work.
|
|
77
|
-
if (outlineTotal === 0)
|
|
78
|
-
return { pages, logicalTotal, outlineTotal };
|
|
79
|
-
for (let pass = 0; pass < passes; pass++) {
|
|
80
|
-
let logicalPage = 1 + options.pageOffset;
|
|
81
|
-
let outlinePage = 1;
|
|
82
|
-
for (let objectIndex = 0; objectIndex < objects.length; objectIndex++) {
|
|
83
|
-
checkCancellation();
|
|
84
|
-
const object = objects[objectIndex];
|
|
85
|
-
for (let pageIndex = 0; pageIndex < object.physicalPages; pageIndex++) {
|
|
86
|
-
if (options.collate)
|
|
87
|
-
emit(objectIndex, pageIndex, pass, logicalPage, outlinePage);
|
|
88
|
-
else
|
|
89
|
-
for (let copyIndex = 0; copyIndex < options.copies; copyIndex++) {
|
|
90
|
-
emit(objectIndex, pageIndex, copyIndex, logicalPage, outlinePage);
|
|
91
|
-
}
|
|
92
|
-
if (object.pagesCount)
|
|
93
|
-
logicalPage++;
|
|
94
|
-
outlinePage++;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return { pages, logicalTotal, outlineTotal };
|
|
99
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { WkhtmltopdfError } from "./errors.js";
|
|
2
|
-
// Admission counts input UTF-8 bytes without allocating an encoded copy.
|
|
3
|
-
// maxWork covers scans, option dispatch and cloned retained settings/entries.
|
|
4
|
-
export class ParseBudget {
|
|
5
|
-
options;
|
|
6
|
-
work = 0;
|
|
7
|
-
bytes = 0;
|
|
8
|
-
objects = 0;
|
|
9
|
-
limits;
|
|
10
|
-
constructor(options) {
|
|
11
|
-
this.options = options;
|
|
12
|
-
this.checkCancellation();
|
|
13
|
-
this.limits = {
|
|
14
|
-
maxArguments: options.limits?.maxArguments,
|
|
15
|
-
maxTextBytes: options.limits?.maxTextBytes,
|
|
16
|
-
maxObjects: options.limits?.maxObjects,
|
|
17
|
-
maxWork: options.limits?.maxWork,
|
|
18
|
-
};
|
|
19
|
-
for (const value of Object.values(this.limits)) {
|
|
20
|
-
if (!Number.isSafeInteger(value) || value < 1) {
|
|
21
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Parser limits must be positive safe integers");
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
checkCancellation() {
|
|
26
|
-
if (this.options.signal?.aborted)
|
|
27
|
-
throw this.options.signal.reason;
|
|
28
|
-
}
|
|
29
|
-
step(amount = 1) {
|
|
30
|
-
this.checkCancellation();
|
|
31
|
-
if (amount > this.limits.maxWork - this.work) {
|
|
32
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "Parser work limit exceeded");
|
|
33
|
-
}
|
|
34
|
-
this.work += amount;
|
|
35
|
-
}
|
|
36
|
-
admitArguments(count) {
|
|
37
|
-
this.step();
|
|
38
|
-
if (count > this.limits.maxArguments) {
|
|
39
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "Parser argument limit exceeded");
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
admitText(text) {
|
|
43
|
-
for (let i = 0; i < text.length; i++) {
|
|
44
|
-
this.step();
|
|
45
|
-
const code = text.charCodeAt(i);
|
|
46
|
-
let size = code < 128 ? 1 : code < 2048 ? 2 : 3;
|
|
47
|
-
if (code >= 0xd800 && code <= 0xdbff) {
|
|
48
|
-
const next = text.charCodeAt(i + 1);
|
|
49
|
-
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
50
|
-
size = 4;
|
|
51
|
-
i++;
|
|
52
|
-
this.step();
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (size > this.limits.maxTextBytes - this.bytes) {
|
|
56
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "Parser text byte limit exceeded");
|
|
57
|
-
}
|
|
58
|
-
this.bytes += size;
|
|
59
|
-
if (code === 0)
|
|
60
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "NUL is not admitted in CLI text");
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
admitObject(replacementCount) {
|
|
64
|
-
this.step(64 + replacementCount * 2);
|
|
65
|
-
if (++this.objects > this.limits.maxObjects) {
|
|
66
|
-
throw new WkhtmltopdfError("LIMIT_EXCEEDED", "Parser object limit exceeded");
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { WkhtmltopdfError } from "./errors.js";
|
|
2
|
-
const httpDescriptions = {
|
|
3
|
-
400: "Bad Request", 401: "Unauthorized", 402: "Payment Required",
|
|
4
|
-
403: "Forbidden", 404: "Page not found", 405: "Method Not Allowed",
|
|
5
|
-
500: "Internal Server Error", 501: "Not Implemented",
|
|
6
|
-
503: "Service Unavailable", 505: "HTTP Version Not Supported",
|
|
7
|
-
};
|
|
8
|
-
/** Pure source-defined status mapping; no renderer or native profile qualification. */
|
|
9
|
-
export function conversionOutcome(completion) {
|
|
10
|
-
const { mode, success, errorCode, networkErrorName } = completion;
|
|
11
|
-
if ((mode !== "single" && mode !== "batch") || typeof success !== "boolean" ||
|
|
12
|
-
!Number.isInteger(errorCode) || errorCode < 0 || errorCode > 2147483647) {
|
|
13
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Expected a conversion mode, boolean success and nonnegative int32 loader code");
|
|
14
|
-
}
|
|
15
|
-
if (networkErrorName !== undefined) {
|
|
16
|
-
if (typeof networkErrorName !== "string" || !networkErrorName.length || networkErrorName.length > 128) {
|
|
17
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Network enum name must contain 1–128 ASCII identifier characters");
|
|
18
|
-
}
|
|
19
|
-
for (const character of networkErrorName) {
|
|
20
|
-
if (!(character >= "a" && character <= "z") && !(character >= "A" && character <= "Z") &&
|
|
21
|
-
!(character >= "0" && character <= "9") && character !== "_") {
|
|
22
|
-
throw new WkhtmltopdfError("INVALID_VALUE", "Network enum name must contain ASCII identifier characters");
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
if (mode === "batch")
|
|
27
|
-
return { exitCode: success ? 0 : 1, diagnostic: null };
|
|
28
|
-
if (errorCode >= 1000) {
|
|
29
|
-
return { exitCode: 1, diagnostic: { kind: "network", code: errorCode - 1000, name: networkErrorName ?? null } };
|
|
30
|
-
}
|
|
31
|
-
if (errorCode !== 0) {
|
|
32
|
-
return {
|
|
33
|
-
exitCode: errorCode === 404 ? 2 : errorCode === 401 ? 3 : 1,
|
|
34
|
-
diagnostic: { kind: "http", code: errorCode, description: httpDescriptions[errorCode] ?? "" },
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
return { exitCode: success ? 0 : 1, diagnostic: success ? null : { kind: "unknown" } };
|
|
38
|
-
}
|