diff --git a/docs/assets/nextjs/project.zip b/docs/assets/nextjs/project.zip index d8d00ad..afb5ddb 100644 Binary files a/docs/assets/nextjs/project.zip and b/docs/assets/nextjs/project.zip differ diff --git a/docs/assets/nextjs/source.json b/docs/assets/nextjs/source.json index fa166c4..45b3753 100644 --- a/docs/assets/nextjs/source.json +++ b/docs/assets/nextjs/source.json @@ -1,12 +1,12 @@ { "sourceRepository": "https://github.com/fullbleed-engine/fullbleed-node", - "sourceCommit": "fe0fa10499428841ba3c0eac8571b5fc31b00fdc", + "sourceCommit": "4f574b5e11d3a680a50c561bf9ea071de29d0cb8", "files": 18, "assets": [ { "file": "project.zip", - "sha256": "9a211e5553f81508c54c87ab52d02f498f17f0e2703eff088cfd440867969564", - "bytes": 104399 + "sha256": "d6586c7b3d5f465fa08ac292b02c634b2ad649f0722616c47b5bc26cf751e157", + "bytes": 105014 }, { "file": "invoice.pdf", diff --git a/docs/assets/nextjs/verification.json b/docs/assets/nextjs/verification.json index f6538d6..d060af0 100644 --- a/docs/assets/nextjs/verification.json +++ b/docs/assets/nextjs/verification.json @@ -1,10 +1,9 @@ { - "checkedAt": "2026-10-05T23:55:42.874759+00:00", + "checkedAt": "2026-10-06T01:06:26.050552+00:00", "sourceRepository": "https://github.com/fullbleed-engine/fullbleed-node", - "sourceCommit": "fe0fa10499428841ba3c0eac8571b5fc31b00fdc", - "mergedCommit": "39062ed78bde8cca2bb73842e6c04b0dc6531a71", - "pullRequest": "https://github.com/fullbleed-engine/fullbleed-node/pull/15", - "ciRun": "https://github.com/fullbleed-engine/fullbleed-node/actions/runs/37279997939", + "sourceCommit": "4f574b5e11d3a680a50c561bf9ea071de29d0cb8", + "pullRequest": "https://github.com/fullbleed-engine/fullbleed-node/pull/17", + "ciRun": "https://github.com/fullbleed-engine/fullbleed-node/actions/runs/37397228563", "standaloneChecks": [ { "name": "standalone artifact is isolated from the source application", @@ -62,6 +61,14 @@ "name": "a valid document renders after the page limit rejects a job", "passed": true }, + { + "name": "a failed render child returns an HTTP error while the production server stays alive", + "passed": true + }, + { + "name": "the production server returns a valid PDF after a render-process failure", + "passed": true + }, { "name": "client JavaScript excludes the server PDF engine and worker", "passed": true @@ -69,14 +76,14 @@ ], "ciResults": [ { - "artifact": "nextjs-ubuntu-latest-22", - "checkedAt": "2026-10-05T07:50:52.448Z", + "checkedAt": "2026-10-06T01:04:06.668Z", "node": "v22.23.3", "platform": "linux", "next": "16.3.8", - "nodePackage": "0.1.5", + "nodePackage": "0.2.0", "engineVersion": "2.5.8", - "packageLockSha256": "eb0b1ff6cf424cebf8464f44b38192a175465bcb77626a7645a18c85f3f0ce40", + "isolation": "process", + "packageLockSha256": "ceda9f7f9099fa91ab5a98d6ce8fd0ba4d2db761b46a19b412688f8db045c179", "pages": 1, "pdfSha256": "9d93174ed961bb2ef577f5f38d2836763b414edc53365f1a9c61e2b50d40f054", "burst": [ @@ -93,17 +100,18 @@ 503, 503 ], - "checksPassed": 15 + "artifact": "nextjs-ubuntu-latest-22", + "checksPassed": 17 }, { - "artifact": "nextjs-ubuntu-latest-24", - "checkedAt": "2026-10-05T07:50:44.440Z", + "checkedAt": "2026-10-06T01:04:06.836Z", "node": "v24.21.0", "platform": "linux", "next": "16.3.8", - "nodePackage": "0.1.5", + "nodePackage": "0.2.0", "engineVersion": "2.5.8", - "packageLockSha256": "eb0b1ff6cf424cebf8464f44b38192a175465bcb77626a7645a18c85f3f0ce40", + "isolation": "process", + "packageLockSha256": "ceda9f7f9099fa91ab5a98d6ce8fd0ba4d2db761b46a19b412688f8db045c179", "pages": 1, "pdfSha256": "9d93174ed961bb2ef577f5f38d2836763b414edc53365f1a9c61e2b50d40f054", "burst": [ @@ -120,17 +128,18 @@ 503, 503 ], - "checksPassed": 15 + "artifact": "nextjs-ubuntu-latest-24", + "checksPassed": 17 }, { - "artifact": "nextjs-windows-latest-24", - "checkedAt": "2026-10-05T07:51:41.715Z", + "checkedAt": "2026-10-06T01:04:28.012Z", "node": "v24.21.0", "platform": "win32", "next": "16.3.8", - "nodePackage": "0.1.5", + "nodePackage": "0.2.0", "engineVersion": "2.5.8", - "packageLockSha256": "eb0b1ff6cf424cebf8464f44b38192a175465bcb77626a7645a18c85f3f0ce40", + "isolation": "process", + "packageLockSha256": "ceda9f7f9099fa91ab5a98d6ce8fd0ba4d2db761b46a19b412688f8db045c179", "pages": 1, "pdfSha256": "9d93174ed961bb2ef577f5f38d2836763b414edc53365f1a9c61e2b50d40f054", "burst": [ @@ -147,25 +156,22 @@ 503, 503 ], - "checksPassed": 15 + "artifact": "nextjs-windows-latest-24", + "checksPassed": 17 } ], - "packageChecks": { - "run": "https://github.com/fullbleed-engine/fullbleed-node/actions/runs/37279997930", - "jobsPassed": 10, - "conclusion": "success" - }, - "downloadVerification": { + "downloadedStarter": { "ok": true, - "checkedAt": "2026-10-05T07:51:53.410802+00:00", - "sourceCommit": "fe0fa10499428841ba3c0eac8571b5fc31b00fdc", + "checkedAt": "2026-10-06T01:05:03.951160+00:00", + "sourceCommit": "4f574b5e11d3a680a50c561bf9ea071de29d0cb8", "downloadFiles": 18, - "projectZipSha256": "9a211e5553f81508c54c87ab52d02f498f17f0e2703eff088cfd440867969564", + "projectZipSha256": "d6586c7b3d5f465fa08ac292b02c634b2ad649f0722616c47b5bc26cf751e157", "node": "v22.23.3", "platform": "win32", "next": "16.3.8", - "nodePackage": "0.1.5", + "nodePackage": "0.2.0", "engineVersion": "2.5.8", + "isolation": "process", "commands": [ { "name": "install", @@ -243,6 +249,14 @@ "name": "a valid document renders after the page limit rejects a job", "passed": true }, + { + "name": "a failed render child returns an HTTP error while the production server stays alive", + "passed": true + }, + { + "name": "the production server returns a valid PDF after a render-process failure", + "passed": true + }, { "name": "client JavaScript excludes the server PDF engine and worker", "passed": true @@ -250,5 +264,48 @@ ], "scope": "Fresh public ZIP installation, production build, isolated standalone HTTP server, and exact sample-asset comparison. No application authentication or hosted-provider claim." }, - "scope": "Published npm 0.1.5 in the Next.js standalone starter. Three CI environments and a fresh downloadable ZIP install pass 15 HTTP/worker/error-recovery checks. The retained invoice PDF and preview match. No application authorization, hosting-provider, speed, or standards certification claim." + "browser": { + "ok": true, + "checked_at": "2026-10-06T01:05:29.710577+00:00", + "browser": "154.0.8037.98", + "isolation": "process", + "checks": [ + { + "width": 1440, + "keyboard": false, + "layout": { + "width": 1440, + "documentWidth": 1440, + "bodyWidth": 1440 + }, + "pdf_sha256": "9d93174ed961bb2ef577f5f38d2836763b414edc53365f1a9c61e2b50d40f054", + "page_errors": [] + }, + { + "width": 390, + "keyboard": true, + "layout": { + "width": 390, + "documentWidth": 390, + "bodyWidth": 390 + }, + "pdf_sha256": "9d93174ed961bb2ef577f5f38d2836763b414edc53365f1a9c61e2b50d40f054", + "page_errors": [] + }, + { + "width": 320, + "keyboard": false, + "layout": { + "width": 320, + "documentWidth": 320, + "bodyWidth": 320 + }, + "pdf_sha256": "9d93174ed961bb2ef577f5f38d2836763b414edc53365f1a9c61e2b50d40f054", + "page_errors": [] + } + ], + "scope": "Independent headless Chrome and isolated Next standalone artifact; real PDF downloads, keyboard activation, narrow layouts. Existing authenticated browser sessions were not used." + }, + "scope": "Published npm 0.2.0 with process isolation in the Next.js standalone starter. Three CI environments and a fresh downloadable ZIP install pass 17 HTTP/process/error-recovery checks. Real browser downloads match the retained invoice. No application authorization, hosted-provider, speed, or standards certification claim.", + "mergedCommit": "36a5b1d382c27d5a0f3414c732a88529e0bf5c7b" } diff --git a/docs/assets/node/project.json b/docs/assets/node/project.json index 34ce19d..580f5d0 100644 --- a/docs/assets/node/project.json +++ b/docs/assets/node/project.json @@ -1,10 +1,10 @@ { - "package_version": "0.1.5", + "package_version": "0.2.0", "engine_version": "2.5.8", - "source_commit": "847b943934ee050c735c712ffcaaff1d3d4c277e", - "package_url": "https://github.com/fullbleed-engine/fullbleed-node/releases/download/v0.1.5/fullbleed-0.1.5.tgz", - "package_integrity": "sha512-3AvPt+n2j/0KMoUk1rq/cu4dzcsnyUNKJNOwpUJkW48vkndFcroFtXgMx4CXJX46O2/gDSli67tuhUUiVsu5OQ==", - "package_sha256": "817e9ba5a4741b6626b62c5e829fdd921c0b69d1e165fb4905483d56887237dc", + "source_commit": "969f23c479cd02f366cc1d2e099b6bda19dfaac9", + "package_url": "https://github.com/fullbleed-engine/fullbleed-node/releases/download/v0.2.0/fullbleed-0.2.0.tgz", + "package_integrity": "sha512-7khp008/oaJxMbroCQuv82ICOnJVQ1RUgYsOZWAqcW92qjKEAVzRK80UZDIJkNWcaMh1yDdTR8BJ5PAFc3QbnA==", + "package_sha256": "90bd0bbf4ae542b6be1deb3103ba492b9d8675e47b71f895060b40419d705f4a", "rendered": { "invoice.pdf": "9d93174ed961bb2ef577f5f38d2836763b414edc53365f1a9c61e2b50d40f054", "invoice.png": "b76f6db748fb47b928c93c12fe788de94a0f5d8da4bb350f644a58705c163cf7" @@ -27,7 +27,7 @@ }, "README.md": { "bytes": 902, - "sha256": "bc0a1e02e586dceb49ce48c5b08cbae46f840474a865b177ba0cc926ada55088" + "sha256": "98501bbbfe6844436d01f3d53cf7ea33c58910333734a981ff4ec599cc26f8ba" }, "invoice.css": { "bytes": 3546, @@ -39,16 +39,16 @@ }, "package-lock.json": { "bytes": 1075, - "sha256": "2432927cb75a10910107f41ff9e372dde8cbcf170588b07bd4948a066c71a24c" + "sha256": "9041f8e21fcde488123d7178b0d0f00b98b6e856141e2c0465ff914daf59d549" }, "package.json": { "bytes": 241, - "sha256": "07cdcec79b81d4f5dc9df229c0a97523ef1af7c32576adc6cd16c7673e1fce64" + "sha256": "6f638329f16f5dd22b767ce3784ce01ef219fcf7bb371261d0a28e0a2b3b5184" }, "render.mjs": { "bytes": 1064, "sha256": "dd63e83658800df2672c8c5ca1b176dbe49fc0d5c49b8c2b79af91515a68e776" } }, - "zip_sha256": "8b49592ad3a49d52ea27fc8eff14e013f9b63da201ca118e2138849f1fc5bcbb" + "zip_sha256": "3cdf9f3fb454dd269f4b2430eec7bd7ff468ce65d24e30f949c48ff4e39022a3" } diff --git a/docs/assets/node/project.zip b/docs/assets/node/project.zip index a9e5575..56c4f5b 100644 Binary files a/docs/assets/node/project.zip and b/docs/assets/node/project.zip differ diff --git a/docs/getting-started/node.md b/docs/getting-started/node.md index 23fb2b6..d96843d 100644 --- a/docs/getting-started/node.md +++ b/docs/getting-started/node.md @@ -8,9 +8,13 @@ Fullbleed's optional Node package renders static HTML/CSS into PDFs using a bundled WebAssembly build of the Rust engine. It includes fonts and runs locally; you do not need Python, Rust, or a browser to use the installed package. -This guide uses **Node package 0.1.5**, powered by **Fullbleed 2.5.8**. The +This guide uses **Node package 0.2.0**, powered by **Fullbleed 2.5.8**. The integration and core engine have separate versions. +Version 0.2.0 adds optional process isolation for server applications. A failed +render child can reject its request while the caller keeps running. The engine +and default worker mode are unchanged from 0.1.5. + Version 0.1.5 fixes regular font selection when an italic face is registered first. Use the actual family name for normal/italic CSS selection, or a PostScript name for an explicit face. Review appearance and line breaks when upgrading; the @@ -30,7 +34,7 @@ With Node.js 22 or newer, install the [npm package](https://www.npmjs.com/packag npm install fullbleed ``` -To pin this guide's version, use `npm install --save-exact fullbleed@0.1.5`. +To pin this guide's version, use `npm install --save-exact fullbleed@0.2.0`. The npm package contains the same verified bytes as the GitHub release. Save this as `invoice.mjs` and run `node invoice.mjs`: @@ -88,7 +92,7 @@ of the finalized PDF. Fullbleed 2.5.5 fixes gradient fills that were missing fro these PNG previews in earlier releases. [Open the report PDF](../assets/node/report.pdf){ .md-button } -[Explore its HTML and CSS](https://github.com/fullbleed-engine/fullbleed-node/tree/v0.1.5/examples){ .md-button } +[Explore its HTML and CSS](https://github.com/fullbleed-engine/fullbleed-node/tree/v0.2.0/examples){ .md-button } ## Bring fonts and image assets @@ -99,7 +103,7 @@ in CSS. Pass images through `assets`, mapping relative names to `Buffer` or The engine uses an in-memory filesystem for the supplied inputs and does not fetch remote URLs. Data URIs also work for document images. The -[package API reference](https://github.com/fullbleed-engine/fullbleed-node/tree/v0.1.5#fonts-and-assets) +[package API reference](https://github.com/fullbleed-engine/fullbleed-node/tree/v0.2.0#fonts-and-assets) includes a complete font and asset example. ## Use it in an application @@ -108,7 +112,7 @@ For a working web-app integration, use the [Next.js PDF download starter](../gui It includes an App Router handler, designed invoice, bundled-asset configuration and an isolated check of the production standalone server. -Rendering runs in a worker, with separate document state for concurrent calls. +By default, rendering runs in a worker, with separate document state for concurrent calls. The promise waits for its worker to exit, including after failure, timeout, or cancellation. Sequential `await renderPdf()` calls therefore do not overlap worker lifetimes. A timeout or abort starts termination; the promise settles @@ -124,13 +128,43 @@ rejected. Use registered font families: unavailable CSS families can fall back, and the engine's glyph report does not establish visual correctness. Review the final PDF and previews when adapting a design. +### Contain render-process failures + +On a Node host that permits child processes, use `isolation: 'process'` to run +each request's engine and worker in a fresh process: + +```javascript +const result = await renderPdf({ + html: '

Monthly statement

', + isolation: 'process', + timeoutMs: 10_000, + maxPages: 20, +}); +``` + +The caller does not compile WebAssembly or create render workers in this mode. +The promise waits for process exit and IPC disconnection before settling, even +after success. A failed child exit rejects with `PROCESS_FAILED`; available +`exitCode` and `signal` fields describe the exit. Timeouts and aborts terminate +the child before rejecting. A PDF received before a failed shutdown is not +returned as a successful result. + +Process startup adds latency and memory use. There is no process pool or +automatic retry; keep concurrency bounded. Application startup flags and +`NODE_OPTIONS` are not replayed in the child. This option requires a normal +Node executable and is not an operating-system security sandbox. See the +[process isolation guide](https://github.com/fullbleed-engine/fullbleed-node/blob/v0.2.0/docs/process-isolation.md) +for deployment details and the tested failure paths. The Next.js starter uses +this mode for its PDF route. + An intermittent process crash on Linux with Node 24.21.0 was observed with `fullbleed@0.1.2`; [issue #7](https://github.com/fullbleed-engine/fullbleed-node/issues/7) remains open. Version 0.1.3 fixes worker shutdown timing, but has not established that this native crash is resolved. See the [runtime investigation](https://github.com/fullbleed-engine/fullbleed-node/blob/main/docs/runtime-diagnostics.md) for the observed scope and synthetic diagnostic. A process crash can occur -before JavaScript can return an error. +before JavaScript can return an error in worker mode. Process isolation contains +tested child failures; it does not establish a fix for that native crash. This first Node API covers ordinary PDF rendering and previews. Use the [Python API](../engine/pdf-engine.md) or [Rust crate](rust.md) for profiles, @@ -144,15 +178,17 @@ TypeScript. Its release evidence compares retained native and WebAssembly fixtures and installs the same tarball across Node 22, 24, and 26 on Windows, Linux, and macOS. These are scoped engineering checks, not PDF standards certification. -The [npm publication record](https://github.com/fullbleed-engine/fullbleed-node/releases/download/v0.1.5/npm-publication-0.1.5.json) +The [npm publication record](https://github.com/fullbleed-engine/fullbleed-node/releases/download/v0.2.0/npm-publication-0.2.0.json) confirms the registry tarball matches that release. A fresh registry install on Windows / Node 22 reproduces its PDF/PNG hashes, including ESM/CommonJS and -recovery after a cold timeout. Seven font fixtures also pass independent font, -text, and page-pixel checks against the registry-installed package. Ten custom-family -cases also match explicit-face controls; the same checker rejects the incorrect -0.1.4 result. [Inspect the family-selection evidence](https://github.com/fullbleed-engine/fullbleed-node/releases/download/v0.1.5/font-family-verification.json). +recovery after a cold timeout. The process consumer also forbids parent +WebAssembly and render workers, checks that application preloads stay out of +children, forces a child termination, and renders again. The release's CI +checks retain seven embedded-font fixtures and ten custom-family cases with +independent font, text, and page-pixel checks. +[Inspect the family-selection evidence](https://github.com/fullbleed-engine/fullbleed-node/releases/download/v0.2.0/font-family-verification.json). [Package source and API](https://github.com/fullbleed-engine/fullbleed-node) · [npm package](https://www.npmjs.com/package/fullbleed) · -[Versioned release and evidence](https://github.com/fullbleed-engine/fullbleed-node/releases/tag/v0.1.5) · +[Versioned release and evidence](https://github.com/fullbleed-engine/fullbleed-node/releases/tag/v0.2.0) · [CSS coverage](../css-coverage.md) · [Examples](../examples.md) diff --git a/docs/guides/nextjs-pdf.md b/docs/guides/nextjs-pdf.md index 714698d..8b2656d 100644 --- a/docs/guides/nextjs-pdf.md +++ b/docs/guides/nextjs-pdf.md @@ -34,17 +34,18 @@ Open `http://127.0.0.1:3000` and select **Download sample PDF**. The route `/api/invoices/NS-1042` returns the invoice as an attachment. The browser stays on the starter page. Use `npm run dev` while editing the app. -The lockfile pins Next.js 16.3.8, React 19.3.0 and Fullbleed Node 0.1.5, powered +The lockfile pins Next.js 16.3.8, React 19.3.0 and Fullbleed Node 0.2.0, powered by engine 2.5.8. Fullbleed is installed from npm. The registry tarball is byte-identical to the verified GitHub release. For an existing Node application, start with the [Node.js quickstart](../getting-started/node.md). ## Keep Fullbleed on the Node server -The route declares `runtime = 'nodejs'`. Fullbleed uses worker threads and an -in-memory WebAssembly engine, and reads bundled assets from its installed -package. The app keeps it outside Next's server bundling and explicitly includes -those files in the standalone output: +The route declares `runtime = 'nodejs'` and calls Fullbleed with +`isolation: 'process'`. Each request runs its worker and WebAssembly engine in +a fresh Node process, using bundled assets from the installed package. The app +keeps Fullbleed outside Next's server bundling and explicitly includes those +runtime files in the standalone output: ```javascript // next.config.mjs — the project also sets its tracing/build root. @@ -69,14 +70,14 @@ browser assets automatically. The PDF engine is imported only by the server route. The browser receives the page, a static PNG preview and the requested PDF; it does not load Fullbleed's -worker or WASM. This integration needs a Node server, not an Edge runtime or -a static site export. +worker or WASM. Use a Node server that permits child processes, workers, and +WebAssembly. Edge runtimes and static site exports cannot run this route. ## Return a private PDF response The [complete route](https://github.com/fullbleed-engine/fullbleed-node/blob/main/examples/nextjs/app/api/invoices/%5Bid%5D/route.js) looks up the fictional invoice, loads its template, then calls `renderPdf` with -a five-page cap, a 15-second deadline and the request's abort signal. +a five-page cap, a 15-second deadline, process isolation and the request's abort signal. | Result | HTTP behavior | | --- | --- | @@ -84,6 +85,7 @@ a five-page cap, a 15-second deadline and the request's abort signal. | Unknown fixture ID | `404` with a small JSON error | | Another render is active | `503 BUSY` with `Retry-After: 2`; no waiting queue | | Renderer rejects the document | Generic `500` error; no partial PDF or document input in the response | +| Render child exits unsuccessfully | Generic `500` error; the server can handle the next request | | Render deadline expires | `504` error | One render is allowed per server process. Use shared rate and usage controls @@ -91,10 +93,14 @@ across replicas, and measure memory before raising concurrency. A worker can grow to the Node package's 512 MiB WASM ceiling. Hosting-provider limits and serverless deployments require their own validation. -Fullbleed waits for a rendering worker to exit before its promise settles, -including after failure, timeout, or cancellation. The route keeps its capacity -slot until that worker stops. A timeout starts termination; settlement includes -the time needed to stop the worker. +Fullbleed waits for process exit and IPC disconnection before its promise +settles, including after failure, timeout, or cancellation. The route keeps +its capacity slot until cleanup finishes. Process startup adds latency and +memory overhead; there is no process pool or automatic retry. Timeouts start +termination, and settlement includes the time needed to stop the child. + +This contains tested child failures. It does not establish a fix for the native +crash tracked in [issue #7](https://github.com/fullbleed-engine/fullbleed-node/issues/7). ## Use your design and authorized data @@ -122,9 +128,11 @@ After building, run `npm run verify`. It copies the standalone app into a fresh temporary directory outside the source project, starts it on loopback, and checks real HTTP PDFs against direct engine output. It also exercises unknown IDs, a concurrent burst, missing-glyph and page-limit failures, and recovery. +It then deliberately fails the isolated artifact's render child, verifies the +HTTP error and live server, restores the child entrypoint, and downloads a valid PDF. The server closes when the checks finish; reports and a PDF remain in `output`. -The [retained verification](../assets/nextjs/verification.json) records 15 passing +The [retained verification](../assets/nextjs/verification.json) records 17 passing standalone checks on Linux with Node 22/24 and Windows with Node 24. All three runs produced the same sample PDF and PNG bytes. Local Chrome checks also cover the real download, keyboard activation and a narrow viewport. Documentation CI diff --git a/examples/node/README.md b/examples/node/README.md index 7142e32..a7ca99d 100644 --- a/examples/node/README.md +++ b/examples/node/README.md @@ -1,6 +1,6 @@ # A designed invoice from Node.js -This fictional Northstar Studio invoice uses Fullbleed's Node package 0.1.5, +This fictional Northstar Studio invoice uses Fullbleed's Node package 0.2.0, which bundles the Fullbleed 2.5.8 engine and Inter, DM Serif Display, and Bebas Neue fonts. You need Node.js 22 or newer; no Python or Rust installation is needed. @@ -13,7 +13,7 @@ Open `output/invoice/invoice.pdf` and `output/invoice/page-1.png`. Change the `customer` value in `render.mjs`; the script escapes it before insertion into HTML. Edit `invoice.html` and `invoice.css` for your own layout and data. -The lockfile pins Fullbleed 0.1.5 from npm and its integrity hash. +The lockfile pins Fullbleed 0.2.0 from npm and its integrity hash. The package renders locally; it does not upload documents to a service. The sample and engine are MIT licensed. The package retains the bundled font diff --git a/examples/node/package-lock.json b/examples/node/package-lock.json index d050eca..5ea1372 100644 --- a/examples/node/package-lock.json +++ b/examples/node/package-lock.json @@ -8,7 +8,7 @@ "name": "fullbleed-node-invoice", "version": "1.0.0", "dependencies": { - "fullbleed": "0.1.5" + "fullbleed": "0.2.0" }, "engines": { "node": ">=22" @@ -21,9 +21,9 @@ "license": "MIT OR Apache-2.0" }, "node_modules/fullbleed": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.1.5.tgz", - "integrity": "sha512-3AvPt+n2j/0KMoUk1rq/cu4dzcsnyUNKJNOwpUJkW48vkndFcroFtXgMx4CXJX46O2/gDSli67tuhUUiVsu5OQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/fullbleed/-/fullbleed-0.2.0.tgz", + "integrity": "sha512-7khp008/oaJxMbroCQuv82ICOnJVQ1RUgYsOZWAqcW92qjKEAVzRK80UZDIJkNWcaMh1yDdTR8BJ5PAFc3QbnA==", "license": "MIT", "dependencies": { "@bjorn3/browser_wasi_shim": "0.4.2" diff --git a/examples/node/package.json b/examples/node/package.json index f4868e9..e301e82 100644 --- a/examples/node/package.json +++ b/examples/node/package.json @@ -10,6 +10,6 @@ "node": ">=22" }, "dependencies": { - "fullbleed": "0.1.5" + "fullbleed": "0.2.0" } } diff --git a/examples/node/source.json b/examples/node/source.json index b82f718..60c83fc 100644 --- a/examples/node/source.json +++ b/examples/node/source.json @@ -1,10 +1,10 @@ { - "package_version": "0.1.5", + "package_version": "0.2.0", "engine_version": "2.5.8", - "source_commit": "847b943934ee050c735c712ffcaaff1d3d4c277e", - "package_url": "https://github.com/fullbleed-engine/fullbleed-node/releases/download/v0.1.5/fullbleed-0.1.5.tgz", - "package_integrity": "sha512-3AvPt+n2j/0KMoUk1rq/cu4dzcsnyUNKJNOwpUJkW48vkndFcroFtXgMx4CXJX46O2/gDSli67tuhUUiVsu5OQ==", - "package_sha256": "817e9ba5a4741b6626b62c5e829fdd921c0b69d1e165fb4905483d56887237dc", + "source_commit": "969f23c479cd02f366cc1d2e099b6bda19dfaac9", + "package_url": "https://github.com/fullbleed-engine/fullbleed-node/releases/download/v0.2.0/fullbleed-0.2.0.tgz", + "package_integrity": "sha512-7khp008/oaJxMbroCQuv82ICOnJVQ1RUgYsOZWAqcW92qjKEAVzRK80UZDIJkNWcaMh1yDdTR8BJ5PAFc3QbnA==", + "package_sha256": "90bd0bbf4ae542b6be1deb3103ba492b9d8675e47b71f895060b40419d705f4a", "rendered": { "invoice.pdf": "9d93174ed961bb2ef577f5f38d2836763b414edc53365f1a9c61e2b50d40f054", "invoice.png": "b76f6db748fb47b928c93c12fe788de94a0f5d8da4bb350f644a58705c163cf7" diff --git a/tools/verify_nextjs_starter.py b/tools/verify_nextjs_starter.py index f316062..e3362bc 100644 --- a/tools/verify_nextjs_starter.py +++ b/tools/verify_nextjs_starter.py @@ -75,7 +75,8 @@ def run(label, arguments): preview = json.loads((project / "output/preview.json").read_text(encoding="utf8")) verification = json.loads((project / "output/verification.json").read_text(encoding="utf8")) assert preview["pages"] == 1 and preview["missingGlyphs"] == 0 -assert len(verification["checks"]) == 15 and all(item["passed"] for item in verification["checks"]) +assert len(verification["checks"]) == 17 and all(item["passed"] for item in verification["checks"]) +assert verification["isolation"] == "process" for actual, expected in [("output/invoice.pdf", "invoice.pdf"), ("output/route.pdf", "invoice.pdf"), ("public/invoice.png", "invoice.png")]: @@ -89,7 +90,7 @@ def run(label, arguments): "projectZipSha256": expected_assets["project.zip"]["sha256"], "node": verification["node"], "platform": verification["platform"], "next": verification["next"], "nodePackage": verification["nodePackage"], - "engineVersion": verification["engineVersion"], "commands": commands, + "engineVersion": verification["engineVersion"], "isolation": verification["isolation"], "commands": commands, "pdfSha256": digest((out / "route.pdf").read_bytes()), "previewSha256": digest((out / "invoice.png").read_bytes()), "checks": verification["checks"], diff --git a/tools/verify_node_examples.py b/tools/verify_node_examples.py index 205c17d..4294dd2 100644 --- a/tools/verify_node_examples.py +++ b/tools/verify_node_examples.py @@ -69,7 +69,7 @@ def run(label, command, cwd=workspace): source = (ROOT / "docs/getting-started/node.md").read_text(encoding="utf8") blocks = re.findall(r"```javascript\n(.*?)```", source, re.S) -assert len(blocks) == 1 +assert len(blocks) == 2 home = (ROOT / 'docs/index.md').read_text(encoding='utf8') home_code = re.findall(r'```javascript\n(.*?)```', home, re.S) assert len(home_code) == 1 and dedent(home_code[0]).strip() == blocks[0].strip(), 'Homepage Node example differs from the executed guide.' @@ -81,12 +81,26 @@ def run(label, command, cwd=workspace): for name in ["invoice.pdf", "invoice.png"]: data = (snippet / name).read_bytes() assert digest(data) == manifest["quickstart"][name], name +process_script = """import assert from 'node:assert/strict'; +import { writeFile } from 'node:fs/promises'; +import { renderPdf } from 'fullbleed'; +""" + blocks[1] + """ +assert.equal(result.pages, 1); +assert.equal(result.missingGlyphs, 0); +assert.equal(result.pdf.subarray(0, 5).toString(), '%PDF-'); +await writeFile('process-statement.pdf', result.pdf); +""" +(snippet / "process-snippet.mjs").write_text(process_script, encoding="utf8", newline="\n") +run("process-snippet", ["process-snippet.mjs"], snippet) +process_pdf = (snippet / "process-statement.pdf").read_bytes() +(out / "process-statement.pdf").write_bytes(process_pdf) report = {"ok": True, "node": subprocess.check_output([str(node), "--version"], text=True).strip(), "platform": os.name, "package_version": installed["version"], "engine_version": installed["fullbleed"]["engineVersion"], "installed_package_matches_manifest": True, "source_commit": manifest["source_commit"], "commands": commands, "download_files": len(manifest["files"]), "project_zip_sha256": manifest["zip_sha256"], "outputs": outputs, "quickstart": manifest["quickstart"], "homepage_snippet_matches_executed_guide": True, + "process_snippet": {"ok": True, "pdf_sha256": digest(process_pdf)}, "workspace": workspace.name, "scope": "Actual public package installation, downloadable project, and documentation snippet; reviewed output hashes."} (out / "verification.json").write_text(json.dumps(report, indent=2) + "\n", encoding="utf8") print(json.dumps({"ok": True, "node": report["node"], "files": report["download_files"], "commands": len(commands)}))