@napplet/conformance-cli 0.3.3 → 0.4.0
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 +17 -1
- package/dist/cli.js +46 -9
- package/dist/cli.js.map +1 -1
- package/dist/host-bundle.global.js +126 -18
- package/dist/ui/assets/index-CQX23owf.js +5450 -0
- package/dist/ui/index.html +1 -1
- package/package.json +7 -5
- package/dist/ui/assets/index-CS2zxBsY.js +0 -109
package/README.md
CHANGED
|
@@ -65,4 +65,20 @@ Exit codes: `0` conformant, `1` non-conformant, `2` usage/runtime error.
|
|
|
65
65
|
|
|
66
66
|
## Capture a preview screenshot
|
|
67
67
|
|
|
68
|
-
The CLI also provides the browser runner for `napplet screenshot`. Run `npx @napplet/conformance-cli screenshot --output preview.png` from a built project; it prefers `dist/index.html` and starts a temporary Kehto Paja preview automatically. Supply a project directory or an HTTP(S) shell URL to override the target. Use `--selector` to select one iframe, `--ready-selector` to wait for an element inside it, and `--width`, `--height`, or `--delay` to adjust the default 1200 × 750 capture after 1500 ms. Existing output files are preserved. Chromium is installed automatically on first use. Local previews use Paja's development identity and live relays, and close after capture or failure.
|
|
68
|
+
The CLI also provides the browser runner for `napplet screenshot`. Run `npx @napplet/conformance-cli@0.3.3 screenshot --output preview.png` from a built project; it prefers `dist/index.html` and starts a temporary Kehto Paja preview automatically. Supply a project directory or an HTTP(S) shell URL to override the target. Use `--selector` to select one iframe, `--ready-selector` to wait for an element inside it, and `--width`, `--height`, or `--delay` to adjust the default 1200 × 750 capture after 1500 ms. Existing output files are preserved. Chromium is installed automatically on first use. Local previews use Paja's development identity and live relays, and close after capture or failure.
|
|
69
|
+
|
|
70
|
+
### GAMEPAD integration
|
|
71
|
+
|
|
72
|
+
Controller artifacts use the engine’s GAMEPAD envelope validation and injected cached API. The reference mock reports unavailable controllers; validate live focus isolation in a compatible runtime. See the proposed [NAP-GAMEPAD](https://github.com/napplet/naps/pull/108).
|
|
73
|
+
|
|
74
|
+
Conformance CLI 0.3.4 requires the published Paja 0.21.x line for GAMEPAD screenshots and compatibility with core/NAP 0.34.0. Paja 0.20.x is excluded because its peer range does not accept those core/NAP versions. Publish this package after its core/NAP dependencies; the standalone CLI runner update follows conformance CLI publication.
|
|
75
|
+
|
|
76
|
+
## Signed manifests and runtime bindings
|
|
77
|
+
|
|
78
|
+
Headless runs accept `--manifest ./signed-event.json` alongside a local directory or `--url`. Supply the signed Nostr event itself, not an unsigned build sidecar. The CLI checks its structure, signature, event id and exact raw HTML SHA-256 before starting the browser. It boots the same loaded snapshot in both passes, so a changed response cannot replace the verified artifact. Missing or mismatched manifest inputs exit with code 2; omitting the option leaves manifest checks skipped. UI mode rejects this headless-only option; use a NIP-5D pointer in the web runtime instead.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
napplet-conformance ./dist --manifest ./signed-event.json
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The CLI and web runtime inject the maintained `@napplet/shim` bindings before subject code. Installation is observed separately from app errors, and private harness observations are excluded from the envelope log. The normal sandbox remains `allow-scripts`; `--allow-same-origin` is a debug setting and correctly fails the sandbox conformance check. Neither domain presence nor manifest declarations grant production permissions. This is implementation guidance; the living [NIP-5D proposal](https://github.com/nostr-protocol/nips/pull/2303) and [NAPs](https://github.com/napplet/naps) define the protocol.
|
package/dist/cli.js
CHANGED
|
@@ -54,7 +54,7 @@ async function sendFile(res, absPath) {
|
|
|
54
54
|
var HOST_HTML_ROUTE = "/__conformance__/host.html";
|
|
55
55
|
var HOST_BUNDLE_ROUTE = "/__conformance__/host-bundle.js";
|
|
56
56
|
function renderHostPage(boot) {
|
|
57
|
-
const cfg = JSON.stringify(boot);
|
|
57
|
+
const cfg = JSON.stringify(boot).replace(/</g, "\\u003c");
|
|
58
58
|
return `<!doctype html>
|
|
59
59
|
<html lang="en">
|
|
60
60
|
<head><meta charset="utf-8"><title>napplet conformance host</title></head>
|
|
@@ -452,6 +452,28 @@ function integer(value, flag, min, max) {
|
|
|
452
452
|
return number;
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
// src/manifest.ts
|
|
456
|
+
import { createHash } from "crypto";
|
|
457
|
+
import { validateEvent, verifyEvent } from "nostr-tools/pure";
|
|
458
|
+
import { validateManifestEvent } from "@napplet/conformance";
|
|
459
|
+
function verifyManifestArtifact(input, artifact) {
|
|
460
|
+
if (!validateEvent(input)) throw new Error("Manifest is not a Nostr event");
|
|
461
|
+
const { id, pubkey, created_at, kind, tags, content, sig } = input;
|
|
462
|
+
const event = { id, pubkey, created_at, kind, tags, content, sig };
|
|
463
|
+
let validSignature = false;
|
|
464
|
+
try {
|
|
465
|
+
validSignature = verifyEvent(event);
|
|
466
|
+
} catch {
|
|
467
|
+
}
|
|
468
|
+
if (!validSignature) throw new Error("Manifest signature or event id is invalid");
|
|
469
|
+
const verdict = validateManifestEvent(event);
|
|
470
|
+
if (!verdict.ok) throw new Error(`Invalid NIP-5D manifest: ${verdict.errors.map((error) => error.message).join("; ")}`);
|
|
471
|
+
const expected = event.tags.find((tag) => tag[0] === "x")?.[1];
|
|
472
|
+
const actual = createHash("sha256").update(artifact).digest("hex");
|
|
473
|
+
if (actual !== expected) throw new Error(`Artifact SHA-256 mismatch: expected ${expected}, got ${actual}`);
|
|
474
|
+
return event;
|
|
475
|
+
}
|
|
476
|
+
|
|
455
477
|
// src/cli.ts
|
|
456
478
|
var HELP = `napplet-conformance \u2014 run NAP protocol conformance against a napplet
|
|
457
479
|
|
|
@@ -466,6 +488,7 @@ Arguments:
|
|
|
466
488
|
|
|
467
489
|
Options:
|
|
468
490
|
--url <url> Test a remotely-served napplet instead of a local dir (headless)
|
|
491
|
+
--manifest <file> Signed NIP-5D event JSON; verify its signature and raw artifact hash before boot (headless)
|
|
469
492
|
--reporter <fmt> pretty | json | junit (default: pretty)
|
|
470
493
|
--out <file> Write the report to a file instead of stdout
|
|
471
494
|
--ready-timeout <ms> Boot timeout waiting for iframe load (default: 5000)
|
|
@@ -509,6 +532,9 @@ function parseArgs(argv) {
|
|
|
509
532
|
case "--url":
|
|
510
533
|
opts.url = value();
|
|
511
534
|
break;
|
|
535
|
+
case "--manifest":
|
|
536
|
+
opts.manifest = value();
|
|
537
|
+
break;
|
|
512
538
|
case "--reporter": {
|
|
513
539
|
const r = value();
|
|
514
540
|
if (r !== "pretty" && r !== "json" && r !== "junit") throw new Error(`Invalid reporter: ${r}`);
|
|
@@ -550,7 +576,7 @@ function parseArgs(argv) {
|
|
|
550
576
|
}
|
|
551
577
|
return opts;
|
|
552
578
|
}
|
|
553
|
-
async function collectBoot(hostUrl,
|
|
579
|
+
async function collectBoot(hostUrl, totalTimeoutMs) {
|
|
554
580
|
const browser = await chromium2.launch({ args: ["--no-sandbox"] });
|
|
555
581
|
try {
|
|
556
582
|
const page = await browser.newPage();
|
|
@@ -566,7 +592,6 @@ async function collectBoot(hostUrl, allowSameOrigin, totalTimeoutMs) {
|
|
|
566
592
|
if (error) throw new Error(`Host harness failed: ${error}`);
|
|
567
593
|
const boot = await page.evaluate(() => window.__conformanceBoot__);
|
|
568
594
|
if (!boot) throw new Error("Host harness produced no result");
|
|
569
|
-
void allowSameOrigin;
|
|
570
595
|
return boot;
|
|
571
596
|
} finally {
|
|
572
597
|
await browser.close();
|
|
@@ -588,6 +613,10 @@ function openBrowser(url) {
|
|
|
588
613
|
}
|
|
589
614
|
}
|
|
590
615
|
async function runUiMode(opts) {
|
|
616
|
+
if (opts.manifest) {
|
|
617
|
+
process.stderr.write("--manifest is supported in headless mode; use a NIP-5D pointer in the web runtime for signed manifests.\n");
|
|
618
|
+
return 2;
|
|
619
|
+
}
|
|
591
620
|
if (opts.url) {
|
|
592
621
|
process.stderr.write("--ui serves a local napplet directory; --url is not supported in UI mode.\n");
|
|
593
622
|
return 2;
|
|
@@ -648,12 +677,14 @@ ${HELP}`);
|
|
|
648
677
|
return runUiMode(opts);
|
|
649
678
|
}
|
|
650
679
|
let nappletDir = null;
|
|
651
|
-
let
|
|
680
|
+
let artifact;
|
|
652
681
|
let forbiddenGlobals = [];
|
|
653
682
|
let bootUrl;
|
|
654
683
|
if (opts.url) {
|
|
655
684
|
bootUrl = opts.url;
|
|
656
|
-
|
|
685
|
+
const response = await fetch(opts.url);
|
|
686
|
+
if (!response.ok) throw new Error(`Failed to fetch napplet HTML: ${response.status} ${response.statusText}`);
|
|
687
|
+
artifact = new Uint8Array(await response.arrayBuffer());
|
|
657
688
|
} else {
|
|
658
689
|
if (!opts.target) {
|
|
659
690
|
process.stderr.write(`Provide a napplet directory or --url.
|
|
@@ -663,30 +694,36 @@ ${HELP}`);
|
|
|
663
694
|
}
|
|
664
695
|
const resolved = await resolveNappletDir(opts.target);
|
|
665
696
|
nappletDir = resolved.dir;
|
|
666
|
-
|
|
697
|
+
artifact = await readFile5(resolved.indexHtml);
|
|
667
698
|
forbiddenGlobals = await scanForbiddenGlobals(resolved.dir);
|
|
668
699
|
bootUrl = "/";
|
|
669
700
|
}
|
|
701
|
+
const manifestEvent = opts.manifest ? verifyManifestArtifact(JSON.parse(await readFile5(opts.manifest, "utf8")), artifact) : null;
|
|
702
|
+
const manifestHtml = new TextDecoder().decode(artifact);
|
|
670
703
|
const here = dirname3(fileURLToPath(import.meta.url));
|
|
671
704
|
const hostBundle = await readFile5(join6(here, "host-bundle.global.js"), "utf8");
|
|
705
|
+
const runtimePreludeSource = await readFile5(fileURLToPath(import.meta.resolve("@napplet/shim/prelude.global")), "utf8");
|
|
672
706
|
const server = await startHarnessServer({
|
|
673
707
|
nappletDir,
|
|
674
708
|
hostBundle,
|
|
675
709
|
bootConfig: {
|
|
676
710
|
url: bootUrl,
|
|
711
|
+
html: manifestHtml,
|
|
677
712
|
readyTimeoutMs: opts.readyTimeoutMs,
|
|
678
713
|
settleMs: opts.settleMs,
|
|
679
|
-
runDegraded: opts.runDegraded
|
|
714
|
+
runDegraded: opts.runDegraded,
|
|
715
|
+
runtimePreludeSource,
|
|
716
|
+
allowSameOrigin: opts.allowSameOrigin
|
|
680
717
|
}
|
|
681
718
|
});
|
|
682
719
|
let boot;
|
|
683
720
|
try {
|
|
684
721
|
const totalTimeout = (opts.readyTimeoutMs + opts.settleMs) * 2 + 3e4;
|
|
685
|
-
boot = await collectBoot(server.hostUrl,
|
|
722
|
+
boot = await collectBoot(server.hostUrl, totalTimeout);
|
|
686
723
|
} finally {
|
|
687
724
|
await server.close();
|
|
688
725
|
}
|
|
689
|
-
const context = buildContext({ manifestHtml, boot, forbiddenGlobals });
|
|
726
|
+
const context = buildContext({ manifestHtml, manifestEvent, boot, forbiddenGlobals, sandbox: { allowScripts: true, allowSameOrigin: opts.allowSameOrigin } });
|
|
690
727
|
const run2 = runConformance(context);
|
|
691
728
|
const output = report(run2, opts.reporter);
|
|
692
729
|
if (opts.out) {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/server.ts","../src/static.ts","../src/ui-server.ts","../src/scan.ts","../src/resolve-napplet.ts","../src/screenshot.ts","../src/screenshot-preview.ts"],"sourcesContent":["/**\n * @napplet/conformance-cli -- `napplet-conformance` entry point.\n *\n * Runs the @napplet/conformance engine against a napplet in real headless Chromium:\n *\n * 1. Resolve the napplet's built directory (or a remote `--url`).\n * 2. Serve it on loopback alongside a host harness page + the engine bundle.\n * 3. Drive Playwright Chromium to `bootAndCollect` inside the host page.\n * 4. Assemble a ConformanceContext on the node side (manifest HTML + static scan),\n * run the check catalog, print the chosen report, and set the exit code.\n *\n * Exit codes: `0` conformant, `1` non-conformant, `2` usage/runtime error.\n *\n * @packageDocumentation\n */\n\nimport { readFile, writeFile, stat } from 'node:fs/promises';\nimport { join, dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { spawn } from 'node:child_process';\nimport { chromium } from 'playwright';\nimport {\n buildContext,\n runConformance,\n report,\n type BootCollection,\n type ReporterFormat,\n} from '@napplet/conformance';\nimport { startHarnessServer } from './server.js';\nimport { startUiServer } from './ui-server.js';\nimport { scanForbiddenGlobals } from './scan.js';\nimport { resolveNappletDir } from './resolve-napplet.js';\nimport { captureScreenshot, parseScreenshotArgs } from './screenshot.js';\n\ninterface CliOptions {\n target?: string;\n url?: string;\n reporter: ReporterFormat;\n out?: string;\n readyTimeoutMs: number;\n settleMs: number;\n runDegraded: boolean;\n allowSameOrigin: boolean;\n ui: boolean;\n port?: number;\n open: boolean;\n exec?: string;\n help: boolean;\n}\n\nconst HELP = `napplet-conformance — run NAP protocol conformance against a napplet\n\nUsage:\n napplet-conformance [dir] [options] # headless, one-shot, CI exit code\n napplet-conformance --ui [dir] [options] # open the live web runtime (like vitest --ui)\n napplet-conformance --url https://my.napplet/ [options]\n napplet-conformance screenshot [directory|preview-url] [--output preview.png]\n\nArguments:\n dir Built napplet directory (prefers ./dist/index.html, falls back to ./index.html)\n\nOptions:\n --url <url> Test a remotely-served napplet instead of a local dir (headless)\n --reporter <fmt> pretty | json | junit (default: pretty)\n --out <file> Write the report to a file instead of stdout\n --ready-timeout <ms> Boot timeout waiting for iframe load (default: 5000)\n --settle <ms> Envelope-collection window after boot (default: 600)\n --no-degraded Skip the no-capability graceful-degradation pass\n --allow-same-origin Debug: also grant allow-same-origin (a conformant napplet must not need it)\n\nUI / watch mode (--ui):\n --port <n> Port for the UI server (default: random)\n --no-open Do not open a browser automatically\n --exec <cmd> Run a command (e.g. \"vite build --watch\") so edits rebuild the served dir;\n the watcher then re-runs conformance live on every change\n -h, --help Show this help\n\nExit codes (headless): 0 conformant, 1 non-conformant, 2 usage/runtime error.\n`;\n\nfunction parseArgs(argv: string[]): CliOptions {\n const opts: CliOptions = {\n reporter: 'pretty',\n readyTimeoutMs: 5000,\n settleMs: 600,\n runDegraded: true,\n allowSameOrigin: false,\n ui: false,\n open: true,\n help: false,\n };\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n const value = (): string => {\n const v = argv[i + 1];\n if (v === undefined || v.startsWith('--')) throw new Error(`Missing value for ${arg}`);\n i += 1;\n return v;\n };\n switch (arg) {\n case '-h':\n case '--help':\n opts.help = true;\n break;\n case '--url':\n opts.url = value();\n break;\n case '--reporter': {\n const r = value();\n if (r !== 'pretty' && r !== 'json' && r !== 'junit') throw new Error(`Invalid reporter: ${r}`);\n opts.reporter = r;\n break;\n }\n case '--out':\n opts.out = value();\n break;\n case '--ready-timeout':\n opts.readyTimeoutMs = Number(value());\n break;\n case '--settle':\n opts.settleMs = Number(value());\n break;\n case '--no-degraded':\n opts.runDegraded = false;\n break;\n case '--allow-same-origin':\n opts.allowSameOrigin = true;\n break;\n case '--ui':\n opts.ui = true;\n break;\n case '--port':\n opts.port = Number(value());\n break;\n case '--no-open':\n opts.open = false;\n break;\n case '--exec':\n opts.exec = value();\n break;\n default:\n if (arg.startsWith('--')) throw new Error(`Unknown option: ${arg}`);\n if (opts.target) throw new Error(`Unexpected extra argument: ${arg}`);\n opts.target = arg;\n }\n }\n return opts;\n}\n\nasync function collectBoot(hostUrl: string, allowSameOrigin: boolean, totalTimeoutMs: number): Promise<BootCollection> {\n const browser = await chromium.launch({ args: ['--no-sandbox'] });\n try {\n const page = await browser.newPage();\n page.on('pageerror', (e) => process.stderr.write(`[host page error] ${e.message}\\n`));\n await page.goto(hostUrl, { waitUntil: 'load' });\n await page.waitForFunction(\n () => window.__conformanceBoot__ !== undefined || window.__conformanceError__ !== undefined,\n undefined,\n { timeout: totalTimeoutMs },\n );\n const error = await page.evaluate(() => window.__conformanceError__);\n if (error) throw new Error(`Host harness failed: ${error}`);\n const boot = await page.evaluate(() => window.__conformanceBoot__);\n if (!boot) throw new Error('Host harness produced no result');\n void allowSameOrigin;\n return boot;\n } finally {\n await browser.close();\n }\n}\n\nasync function isDir(path: string): Promise<boolean> {\n return (await stat(path).catch(() => null))?.isDirectory() ?? false;\n}\n\n/** Best-effort: open a URL in the default browser. */\nfunction openBrowser(url: string): void {\n try {\n if (process.platform === 'darwin') {\n spawn('open', [url], { stdio: 'ignore', detached: true }).unref();\n } else if (process.platform === 'win32') {\n spawn('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true }).unref();\n } else {\n spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();\n }\n } catch {\n /* best-effort — the URL is printed regardless */\n }\n}\n\n/** `--ui`: serve the live web runtime + the napplet, watch, and re-run on change. */\nasync function runUiMode(opts: CliOptions): Promise<number> {\n if (opts.url) {\n process.stderr.write('--ui serves a local napplet directory; --url is not supported in UI mode.\\n');\n return 2;\n }\n if (!opts.target) {\n process.stderr.write(`Provide a napplet directory.\\n\\n${HELP}`);\n return 2;\n }\n const resolved = await resolveNappletDir(opts.target);\n const here = dirname(fileURLToPath(import.meta.url));\n const appDir = join(here, 'ui');\n if (!(await isDir(appDir))) {\n process.stderr.write(`Conformance UI assets are missing at ${appDir}. Rebuild @napplet/conformance-cli.\\n`);\n return 2;\n }\n\n const server = await startUiServer({ appDir, nappletDir: resolved.dir, port: opts.port });\n\n const child = opts.exec ? spawn(opts.exec, { cwd: resolve(opts.target), shell: true, stdio: 'inherit' }) : null;\n\n process.stdout.write(\n `\\n napplet conformance UI → ${server.appUrl}\\n` +\n ` watching ${resolved.dir}${opts.exec ? ` (running: ${opts.exec})` : ''}\\n` +\n ` Ctrl-C to stop\\n\\n`,\n );\n if (opts.open) openBrowser(server.appUrl);\n\n await new Promise<void>((resolveWait) => {\n let closing = false;\n const shutdown = (): void => {\n if (closing) return;\n closing = true;\n child?.kill();\n void server.close().finally(resolveWait);\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n });\n return 0;\n}\n\nasync function main(argv: string[]): Promise<number> {\n let opts: CliOptions;\n try {\n opts = parseArgs(argv);\n } catch (err) {\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n\\n${HELP}`);\n return 2;\n }\n if (opts.help) {\n process.stdout.write(HELP);\n return 0;\n }\n\n if (opts.ui) {\n return runUiMode(opts);\n }\n\n // Resolve the subject: local dir or remote URL.\n let nappletDir: string | null = null;\n let manifestHtml: string;\n let forbiddenGlobals: string[] = [];\n let bootUrl: string;\n\n if (opts.url) {\n bootUrl = opts.url;\n manifestHtml = await (await fetch(opts.url)).text();\n } else {\n if (!opts.target) {\n process.stderr.write(`Provide a napplet directory or --url.\\n\\n${HELP}`);\n return 2;\n }\n const resolved = await resolveNappletDir(opts.target);\n nappletDir = resolved.dir;\n manifestHtml = await readFile(resolved.indexHtml, 'utf8');\n forbiddenGlobals = await scanForbiddenGlobals(resolved.dir);\n bootUrl = '/';\n }\n\n const here = dirname(fileURLToPath(import.meta.url));\n const hostBundle = await readFile(join(here, 'host-bundle.global.js'), 'utf8');\n\n const server = await startHarnessServer({\n nappletDir,\n hostBundle,\n bootConfig: {\n url: bootUrl,\n readyTimeoutMs: opts.readyTimeoutMs,\n settleMs: opts.settleMs,\n runDegraded: opts.runDegraded,\n },\n });\n\n let boot: BootCollection;\n try {\n // Generous overall budget: two boots (primary + degraded), each up to\n // readyTimeout + settle, plus browser startup.\n const totalTimeout = (opts.readyTimeoutMs + opts.settleMs) * 2 + 30000;\n boot = await collectBoot(server.hostUrl, opts.allowSameOrigin, totalTimeout);\n } finally {\n await server.close();\n }\n\n const context = buildContext({ manifestHtml, boot, forbiddenGlobals });\n const run = runConformance(context);\n const output = report(run, opts.reporter);\n\n if (opts.out) {\n await writeFile(opts.out, `${output}\\n`, 'utf8');\n process.stdout.write(`Report written to ${opts.out}\\n`);\n } else {\n process.stdout.write(`${output}\\n`);\n }\n\n return run.ok ? 0 : 1;\n}\n\nasync function run(args: string[]): Promise<number> {\n if (args[0] !== 'screenshot') return main(args);\n if (args.includes('--help') || args.includes('-h')) {\n console.log('Usage: napplet screenshot [directory|preview-url] [--output preview.png] [--selector iframe] [--ready-selector <css>] [--width 1200] [--height 750] [--delay 1500]');\n return 0;\n }\n const options = parseScreenshotArgs(args.slice(1));\n await captureScreenshot(options);\n console.log(`Screenshot saved to ${options.output}`);\n return 0;\n}\n\nrun(process.argv.slice(2))\n .then((code) => {\n process.exitCode = code;\n })\n .catch((err: unknown) => {\n process.stderr.write(`napplet-conformance: ${err instanceof Error ? err.stack ?? err.message : String(err)}\\n`);\n process.exitCode = 2;\n });\n","/**\n * @napplet/conformance-cli -- Loopback harness server.\n *\n * Serves the napplet's files plus two reserved harness routes (the host page and\n * the browser bundle). Every response carries `Access-Control-Allow-Origin: *`:\n * the napplet iframe runs at an opaque origin (sandbox without `allow-same-origin`),\n * so its `<script type=\"module\">` subresources are fetched in CORS mode and would\n * otherwise be blocked.\n *\n * @packageDocumentation\n */\n\nimport { createServer, type Server, type ServerResponse } from 'node:http';\nimport { readFile, stat } from 'node:fs/promises';\nimport { join, normalize, extname, resolve } from 'node:path';\nimport type { AddressInfo } from 'node:net';\nimport { MIME } from './static.js';\n\n/** The napplet URL + timing knobs the host page passes to `bootAndCollect`. */\nexport interface BootConfig {\n url: string;\n readyTimeoutMs: number;\n settleMs: number;\n runDegraded: boolean;\n}\n\n/** Options for {@link startHarnessServer}. */\nexport interface HarnessServerOptions {\n /** Directory of napplet files to serve, or `null` in remote-URL mode. */\n nappletDir: string | null;\n /** JavaScript source of the host browser bundle. */\n hostBundle: string;\n /** Boot configuration injected into the host page. */\n bootConfig: BootConfig;\n}\n\n/** A running harness server. */\nexport interface HarnessServer {\n /** Base origin, e.g. `http://127.0.0.1:54213`. */\n origin: string;\n /** URL of the host harness page. */\n hostUrl: string;\n /** Shut the server down. */\n close(): Promise<void>;\n}\n\nconst HOST_HTML_ROUTE = '/__conformance__/host.html';\nconst HOST_BUNDLE_ROUTE = '/__conformance__/host-bundle.js';\n\nfunction renderHostPage(boot: BootConfig): string {\n // The inline script lives on the harness page (full privilege, our origin), not\n // in the sandboxed napplet — so it is not subject to the napplet CSP.\n const cfg = JSON.stringify(boot);\n return `<!doctype html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\"><title>napplet conformance host</title></head>\n<body>\n<script src=\"${HOST_BUNDLE_ROUTE}\"></script>\n<script>\n (function () {\n var cfg = ${cfg};\n window.NappletConformanceHost.run(cfg).then(function (boot) {\n window.__conformanceBoot__ = boot;\n }).catch(function (err) {\n window.__conformanceError__ = String((err && err.stack) || err);\n });\n })();\n</script>\n</body>\n</html>`;\n}\n\n/** Start the loopback harness server on a random port. */\nexport async function startHarnessServer(options: HarnessServerOptions): Promise<HarnessServer> {\n const dir = options.nappletDir ? resolve(options.nappletDir) : null;\n const hostHtml = renderHostPage(options.bootConfig);\n\n const server: Server = createServer((req, res) => {\n void serve(req.url ?? '/', res);\n });\n\n async function serve(rawUrl: string, res: ServerResponse): Promise<void> {\n res.setHeader('Access-Control-Allow-Origin', '*');\n const path = decodeURIComponent(rawUrl.split('?')[0]);\n\n if (path === HOST_HTML_ROUTE) {\n res.writeHead(200, { 'Content-Type': MIME['.html'] });\n res.end(hostHtml);\n return;\n }\n if (path === HOST_BUNDLE_ROUTE) {\n res.writeHead(200, { 'Content-Type': MIME['.js'] });\n res.end(options.hostBundle);\n return;\n }\n\n if (!dir) {\n res.writeHead(404).end('not found');\n return;\n }\n\n // Resolve a safe path under the napplet dir.\n const relative = normalize(path).replace(/^(\\.\\.[/\\\\])+/, '').replace(/^[/\\\\]+/, '');\n let filePath = join(dir, relative);\n if (!filePath.startsWith(dir)) {\n res.writeHead(403).end('forbidden');\n return;\n }\n try {\n let info = await stat(filePath).catch(() => null);\n if (info?.isDirectory()) {\n filePath = join(filePath, 'index.html');\n info = await stat(filePath).catch(() => null);\n }\n if (path === '/' && !info) {\n filePath = join(dir, 'index.html');\n info = await stat(filePath).catch(() => null);\n }\n if (!info?.isFile()) {\n res.writeHead(404).end('not found');\n return;\n }\n const body = await readFile(filePath);\n res.writeHead(200, { 'Content-Type': MIME[extname(filePath).toLowerCase()] ?? 'application/octet-stream' });\n res.end(body);\n } catch {\n res.writeHead(500).end('server error');\n }\n }\n\n await new Promise<void>((resolveListen) => server.listen(0, '127.0.0.1', resolveListen));\n const address = server.address() as AddressInfo;\n const origin = `http://127.0.0.1:${address.port}`;\n\n return {\n origin,\n hostUrl: `${origin}${HOST_HTML_ROUTE}`,\n close: () => new Promise<void>((res, rej) => server.close((err) => (err ? rej(err) : res()))),\n };\n}\n","/**\n * @napplet/conformance-cli -- Shared static-serving helpers used by both the\n * one-shot harness server and the `--ui` watch server.\n *\n * @packageDocumentation\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport { extname } from 'node:path';\nimport type { ServerResponse } from 'node:http';\n\n/** Content types by extension. */\nexport const MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.ico': 'image/x-icon',\n '.wasm': 'application/wasm',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.map': 'application/json; charset=utf-8',\n '.txt': 'text/plain; charset=utf-8',\n};\n\n/** The napplet iframe runs at an opaque origin; allow its module subresources. */\nexport function setCors(res: ServerResponse): void {\n res.setHeader('Access-Control-Allow-Origin', '*');\n}\n\n/** Send a file if it exists; return false (without writing) when it does not. */\nexport async function sendFile(res: ServerResponse, absPath: string): Promise<boolean> {\n const info = await stat(absPath).catch(() => null);\n if (!info?.isFile()) return false;\n res.writeHead(200, { 'Content-Type': MIME[extname(absPath).toLowerCase()] ?? 'application/octet-stream' });\n res.end(await readFile(absPath));\n return true;\n}\n","/**\n * @napplet/conformance-cli -- The `--ui` watch server.\n *\n * Serves the standalone conformance web app (bundled with the CLI), the napplet\n * under test (same origin, with permissive CORS for the sandboxed iframe), and a\n * Server-Sent-Events stream. It watches the napplet's served directory and pushes\n * a `rerun` event on every change, so the open app re-runs conformance live — the\n * `vitest --ui` model: edit, save, see the verdict update.\n *\n * @packageDocumentation\n */\n\nimport { createServer, type Server, type ServerResponse } from 'node:http';\nimport { watch, type FSWatcher } from 'node:fs';\nimport { join, normalize, resolve } from 'node:path';\nimport type { AddressInfo } from 'node:net';\nimport { setCors, sendFile } from './static.js';\n\n/** Path the app under `?live=1` is served the napplet from. */\nconst NAPPLET_PREFIX = '/__napplet__/';\n/** SSE endpoint the live app subscribes to. */\nconst EVENTS_ROUTE = '/__conformance__/events';\n\n/** Options for {@link startUiServer}. */\nexport interface UiServerOptions {\n /** Directory of the built conformance web app (served at `/`). */\n appDir: string;\n /** Directory of the built napplet under test (served at `/__napplet__/`). */\n nappletDir: string;\n /** Preferred port (0 = random). */\n port?: number;\n /** Debounce window (ms) for coalescing file-change bursts. Default 120. */\n debounceMs?: number;\n}\n\n/** A running `--ui` server. */\nexport interface UiServer {\n /** Base origin, e.g. `http://127.0.0.1:5219`. */\n origin: string;\n /** The app URL to open (already pointed at the napplet, live mode on). */\n appUrl: string;\n /** Force a `rerun` broadcast (used for the manual/initial trigger). */\n notify(): void;\n /** Shut the server + watcher down. */\n close(): Promise<void>;\n}\n\nfunction safeJoin(dir: string, urlPath: string): string | null {\n const relative = normalize(urlPath).replace(/^(\\.\\.[/\\\\])+/, '').replace(/^[/\\\\]+/, '');\n const full = join(dir, relative);\n return full.startsWith(dir) ? full : null;\n}\n\n/** Register an SSE subscriber and keep the connection open. */\nfunction handleSse(res: ServerResponse, clients: Set<ServerResponse>): void {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache, no-transform',\n Connection: 'keep-alive',\n });\n res.write('retry: 1000\\n\\n');\n clients.add(res);\n res.on('close', () => clients.delete(res));\n}\n\n/** Serve a file under `/__napplet__/`. */\nasync function handleNapplet(res: ServerResponse, nappletDir: string, path: string): Promise<void> {\n const rel = path.slice(NAPPLET_PREFIX.length) || 'index.html';\n const abs = safeJoin(nappletDir, rel);\n if (abs && (await sendFile(res, abs))) return;\n res.writeHead(404).end('napplet asset not found');\n}\n\n/** Serve the conformance web app, with an SPA fallback to its index. */\nasync function handleApp(res: ServerResponse, appDir: string, path: string): Promise<void> {\n const abs = safeJoin(appDir, path === '/' ? 'index.html' : path);\n if (abs && (await sendFile(res, abs))) return;\n if (await sendFile(res, join(appDir, 'index.html'))) return;\n res.writeHead(404).end('not found');\n}\n\n/** Watch a directory (recursively when supported) and call `fire` debounced. */\nfunction startWatcher(dir: string, debounceMs: number, fire: () => void): { close(): void } {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const onEvent = (): void => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(fire, debounceMs);\n };\n let watcher: FSWatcher;\n try {\n watcher = watch(dir, { recursive: true }, onEvent);\n } catch {\n // Recursive watch may be unsupported on some platforms.\n watcher = watch(dir, onEvent);\n }\n return {\n close() {\n if (timer) clearTimeout(timer);\n watcher.close();\n },\n };\n}\n\n/** Start the `--ui` watch server. */\nexport async function startUiServer(options: UiServerOptions): Promise<UiServer> {\n const appDir = resolve(options.appDir);\n const nappletDir = resolve(options.nappletDir);\n const clients = new Set<ServerResponse>();\n const broadcast = (): void => {\n for (const res of clients) res.write(`event: rerun\\ndata: ${Date.now()}\\n\\n`);\n };\n\n const server: Server = createServer((req, res) => {\n setCors(res);\n res.setHeader('Cache-Control', 'no-store'); // never serve stale napplet code\n const path = decodeURIComponent((req.url ?? '/').split('?')[0]);\n if (path === EVENTS_ROUTE) {\n handleSse(res, clients);\n } else if (path === NAPPLET_PREFIX.slice(0, -1) || path.startsWith(NAPPLET_PREFIX)) {\n void handleNapplet(res, nappletDir, path);\n } else {\n void handleApp(res, appDir, path);\n }\n });\n\n await new Promise<void>((r) => server.listen(options.port ?? 0, '127.0.0.1', r));\n const { port } = server.address() as AddressInfo;\n const origin = `http://127.0.0.1:${port}`;\n const appUrl = `${origin}/?url=${encodeURIComponent(NAPPLET_PREFIX)}&live=1`;\n const watcher = startWatcher(nappletDir, options.debounceMs ?? 120, broadcast);\n\n return {\n origin,\n appUrl,\n notify: broadcast,\n close: () =>\n new Promise<void>((res, rej) => {\n watcher.close();\n for (const c of clients) c.end();\n clients.clear();\n server.close((err) => (err ? rej(err) : res()));\n }),\n };\n}\n","/**\n * @napplet/conformance-cli -- Static scan for forbidden napplet authority.\n *\n * A napplet runs in a `sandbox=\"allow-scripts\"` iframe at an opaque origin, so the\n * host cannot observe every forbidden browser surface at runtime. Instead we scan\n * served source for references to authority the shell owns: signer globals,\n * browser storage, direct network primitives, and external network-loaded assets.\n * This is a heuristic, but a true positive is meaningful: NIP-5D and the NAPs\n * route these capabilities through `window.napplet` domains.\n *\n * @packageDocumentation\n */\n\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { join, extname } from 'node:path';\n\n/** Patterns considered forbidden, keyed by the label reported in the verdict. */\nconst FORBIDDEN: Array<{ label: string; re: RegExp }> = [\n { label: 'window.nostr', re: /\\bwindow\\s*\\.\\s*nostr\\b/ },\n { label: 'globalThis.nostr', re: /\\bglobalThis\\s*\\.\\s*nostr\\b/ },\n { label: 'fetch', re: /\\b(?:window\\s*\\.\\s*|globalThis\\s*\\.\\s*)?fetch\\s*\\(/ },\n { label: 'XMLHttpRequest', re: /\\b(?:new\\s+)?XMLHttpRequest\\s*\\(/ },\n { label: 'WebSocket', re: /\\b(?:new\\s+)?WebSocket\\s*\\(/ },\n { label: 'localStorage', re: /\\b(?:window\\s*\\.\\s*)?localStorage\\b/ },\n { label: 'sessionStorage', re: /\\b(?:window\\s*\\.\\s*)?sessionStorage\\b/ },\n { label: 'indexedDB', re: /\\b(?:window\\s*\\.\\s*)?indexedDB\\b/ },\n { label: 'document.cookie', re: /\\bdocument\\s*\\.\\s*cookie\\b/ },\n { label: 'external img src', re: /<img\\b[^>]*\\bsrc\\s*=\\s*[\"']?https?:/i },\n { label: 'external script src', re: /<script\\b[^>]*\\bsrc\\s*=\\s*[\"']?https?:/i },\n { label: 'external stylesheet href', re: /<link\\b[^>]*\\brel\\s*=\\s*[\"']?stylesheet[\"']?[^>]*\\bhref\\s*=\\s*[\"']?https?:/i },\n { label: 'external stylesheet href', re: /<link\\b[^>]*\\bhref\\s*=\\s*[\"']?https?:[^>]*\\brel\\s*=\\s*[\"']?stylesheet[\"']?/i },\n];\n\nconst SCANNABLE = new Set(['.js', '.mjs', '.cjs', '.html', '.htm']);\nconst SKIP_DIRS = new Set(['node_modules', '.git', '.turbo', 'coverage']);\n\nasync function collectFiles(dir: string, acc: string[]): Promise<void> {\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n await collectFiles(join(dir, entry.name), acc);\n } else if (entry.isFile() && SCANNABLE.has(extname(entry.name).toLowerCase())) {\n acc.push(join(dir, entry.name));\n }\n }\n}\n\n/**\n * Scan a napplet directory for forbidden browser authority references.\n *\n * @param dir - The directory of served napplet files.\n * @returns The distinct forbidden labels found (e.g. `['window.nostr']`).\n */\nexport async function scanForbiddenGlobals(dir: string): Promise<string[]> {\n const info = await stat(dir).catch(() => null);\n if (!info?.isDirectory()) return [];\n const files: string[] = [];\n await collectFiles(dir, files);\n const found = new Set<string>();\n for (const file of files) {\n const text = await readFile(file, 'utf8').catch(() => '');\n for (const { label, re } of FORBIDDEN) {\n if (re.test(text)) found.add(label);\n }\n }\n return [...found];\n}\n","/**\n * @napplet/conformance-cli -- Resolve which local artifact to conformance-test.\n *\n * Kept in its own module (not `cli.ts`, whose top level runs `main()`) so tests\n * can import the resolver without launching the CLI.\n */\n\nimport { stat } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nasync function isFile(path: string): Promise<boolean> {\n return (await stat(path).catch(() => null))?.isFile() ?? false;\n}\n\n/**\n * Resolve the directory + index.html for a local napplet target.\n *\n * A built napplet is the artifact we test, so `dist/index.html` is preferred over\n * a sibling `index.html`. When `target` is a project root, its source\n * `index.html` (a Vite entry referencing `/src/*.ts` that cannot run in the\n * opaque-origin `srcdoc` sandbox) would otherwise shadow the built single-file\n * output and make conformance test the wrong, never-booting artifact.\n */\nexport async function resolveNappletDir(target: string): Promise<{ dir: string; indexHtml: string }> {\n const base = resolve(target);\n const dist = join(base, 'dist');\n if (await isFile(join(dist, 'index.html'))) return { dir: dist, indexHtml: join(dist, 'index.html') };\n if (await isFile(join(base, 'index.html'))) return { dir: base, indexHtml: join(base, 'index.html') };\n throw new Error(`No index.html found in ${join(dist, 'index.html')} or ${join(base, 'index.html')}. Build the napplet first.`);\n}\n","/** Capture a local build or an existing shell preview through its iframe. */\nimport { access, writeFile } from 'node:fs/promises';\nimport { execFile } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { chromium } from 'playwright';\nimport { startScreenshotPreview } from './screenshot-preview.js';\nimport { resolveNappletDir } from './resolve-napplet.js';\n\n/** Options for capturing a shell-hosted napplet. */\nexport interface ScreenshotOptions {\n url?: string;\n directory?: string;\n artifact?: string;\n output: string;\n selector: string;\n readySelector?: string;\n width: number;\n height: number;\n delay: number;\n}\n\n/**\n * Parse screenshot arguments without invoking a browser.\n * @param args CLI arguments after the screenshot command.\n * @returns Validated capture options.\n * @example parseScreenshotArgs(['http://localhost:5173', '--output', 'preview.png'])\n */\nexport function parseScreenshotArgs(args: readonly string[]): ScreenshotOptions {\n const hasTarget = args[0] && !args[0].startsWith('--');\n const target = hasTarget ? args[0] : '.';\n const rest = hasTarget ? args.slice(1) : args;\n const remote = /^https?:\\/\\//.test(target);\n if (remote) new URL(target);\n else if (/^[a-z][a-z0-9+.-]*:/i.test(target) && !/^[a-z]:[\\\\/]/i.test(target)) throw new Error('Preview URLs must use HTTP(S)');\n const options: ScreenshotOptions = { ...(remote ? { url: target } : { directory: target }), output: 'preview.png', selector: 'iframe', width: 1200, height: 750, delay: 1500 };\n for (let index = 0; index < rest.length; index += 2) {\n const flag = rest[index];\n const value = rest[index + 1];\n if (!value || value.startsWith('--')) throw new Error(`Missing value for ${flag}`);\n switch (flag) {\n case '--output': options.output = value; break;\n case '--artifact': options.artifact = value; break;\n case '--selector': options.selector = value; break;\n case '--ready-selector': options.readySelector = value; break;\n case '--width': options.width = integer(value, flag, 1, 4096); break;\n case '--height': options.height = integer(value, flag, 1, 4096); break;\n case '--delay': options.delay = integer(value, flag, 0, 30000); break;\n default: throw new Error(`Unknown screenshot option: ${flag}`);\n }\n }\n if (!options.output.toLowerCase().endsWith('.png')) throw new Error('--output must end in .png');\n if (options.artifact && hasTarget) throw new Error('Use a target or --artifact, not both');\n return options;\n}\n\n/**\n * Save a PNG of a napplet iframe, starting a local preview when needed.\n * @param options Local build or URL, output, dimensions and readiness settings.\n * @returns Resolves after capture and cleanup of Chromium and any local preview.\n * @example await captureScreenshot(parseScreenshotArgs(['http://localhost:5173']))\n */\nexport async function captureScreenshot(options: ScreenshotOptions): Promise<void> {\n await ensureChromium();\n const preview = options.url ? undefined : await startScreenshotPreview(\n options.artifact ?? (await resolveNappletDir(options.directory ?? '.')).indexHtml,\n );\n let browser;\n try {\n browser = await chromium.launch({ channel: 'chromium' });\n const page = await browser.newPage({ viewport: { width: options.width, height: options.height } });\n page.setDefaultTimeout(30000);\n await page.goto(options.url ?? preview!.url, { waitUntil: 'domcontentloaded' });\n if (preview) {\n await page.waitForFunction(() => {\n const iframe = document.querySelector<HTMLIFrameElement>('#napplet-frame');\n return Boolean(iframe?.srcdoc);\n });\n }\n const frame = page.locator(options.selector);\n await frame.waitFor({ state: 'visible' });\n await frame.evaluate((element, size) => {\n if (!(element instanceof HTMLIFrameElement)) throw new Error('Screenshot selector must select one iframe');\n Object.assign(element.style, { width: `${size.width}px`, height: `${size.height}px`, maxWidth: 'none', maxHeight: 'none', boxSizing: 'border-box' });\n }, { width: options.width, height: options.height });\n const content = frame.contentFrame();\n await content.locator('body').waitFor({ state: 'visible' });\n if (options.readySelector) await content.locator(options.readySelector).waitFor({ state: 'visible' });\n await content.locator('body').evaluate(async () => { await document.fonts.ready; });\n await page.waitForTimeout(options.delay);\n const png = await frame.screenshot({ type: 'png', timeout: 30000 });\n await writeFile(options.output, png, { flag: 'wx' });\n } finally {\n try { await browser?.close(); } finally { await preview?.close(); }\n }\n}\n\nasync function ensureChromium(): Promise<void> {\n try { await access(chromium.executablePath()); return; } catch { /* Install the matching browser on first use. */ }\n console.error('Installing Chromium for screenshot capture...');\n const require = createRequire(import.meta.url);\n const cli = join(dirname(require.resolve('playwright/package.json')), 'cli.js');\n await promisify(execFile)(process.execPath, [cli, 'install', 'chromium'], { timeout: 180000 });\n}\n\nfunction integer(value: string, flag: string, min: number, max: number): number {\n const number = Number(value);\n if (!Number.isInteger(number) || number < min || number > max) throw new Error(`${flag} must be an integer from ${min} to ${max}`);\n return number;\n}\n","/** Disposable Kehto Paja preview for a local build. */\nimport { createServer } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { readFile, realpath } from 'node:fs/promises';\nimport { dirname, resolve, sep } from 'node:path';\nimport { startPajaServer } from '@kehto/paja';\nimport { sendFile, setCors } from './static.js';\n\n/**\n * Host a built artifact in Paja on ephemeral loopback ports.\n * @param artifact Exact index.html selected by deployment.\n * @returns Shell URL and an async cleanup handle for both servers.\n * @example const preview = await startScreenshotPreview('/project/dist/index.html');\n */\nexport async function startScreenshotPreview(artifact: string): Promise<{ url: string; close(): Promise<void> }> {\n const index = await realpath(artifact);\n const root = dirname(index);\n const html = await readFile(index);\n const server = createServer((request, response) => {\n setCors(response);\n void (async () => {\n const pathname = decodeURIComponent(new URL(request.url ?? '/', 'http://localhost').pathname);\n if (pathname === '/' || pathname === '/index.html') {\n response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });\n response.end(html);\n return;\n }\n const file = await realpath(resolve(root, `.${pathname}`));\n if (!file.startsWith(`${root}${sep}`)) { response.writeHead(403).end(); return; }\n if (!await sendFile(response, file)) response.writeHead(404).end();\n })().catch(() => { if (!response.headersSent) response.writeHead(404); response.end(); });\n });\n await new Promise<void>((done, reject) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', () => { server.off('error', reject); done(); });\n });\n const closeTarget = () => new Promise<void>((done, reject) => {\n server.closeAllConnections();\n server.close((error) => error ? reject(error) : done());\n });\n try {\n const targetUrl = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;\n const paja = await startPajaServer({ options: {\n host: '127.0.0.1', port: 0, targetUrl,\n } });\n return { url: paja.url, close: async () => { try { await paja.close(); } finally { await closeTarget(); } } };\n } catch (error) {\n await closeTarget();\n throw error;\n }\n}\n"],"mappings":";;;AAgBA,SAAS,YAAAA,WAAU,aAAAC,YAAW,QAAAC,aAAY;AAC1C,SAAS,QAAAC,OAAM,WAAAC,UAAS,WAAAC,gBAAe;AACvC,SAAS,qBAAqB;AAC9B,SAAS,aAAa;AACtB,SAAS,YAAAC,iBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;;;ACfP,SAAS,oBAAsD;AAC/D,SAAS,YAAAC,WAAU,QAAAC,aAAY;AAC/B,SAAS,MAAM,WAAW,WAAAC,UAAS,eAAe;;;ACPlD,SAAS,UAAU,YAAY;AAC/B,SAAS,eAAe;AAIjB,IAAM,OAA+B;AAAA,EAC1C,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,QAAQ;AACV;AAGO,SAAS,QAAQ,KAA2B;AACjD,MAAI,UAAU,+BAA+B,GAAG;AAClD;AAGA,eAAsB,SAAS,KAAqB,SAAmC;AACrF,QAAM,OAAO,MAAM,KAAK,OAAO,EAAE,MAAM,MAAM,IAAI;AACjD,MAAI,CAAC,MAAM,OAAO,EAAG,QAAO;AAC5B,MAAI,UAAU,KAAK,EAAE,gBAAgB,KAAK,QAAQ,OAAO,EAAE,YAAY,CAAC,KAAK,2BAA2B,CAAC;AACzG,MAAI,IAAI,MAAM,SAAS,OAAO,CAAC;AAC/B,SAAO;AACT;;;ADEA,IAAM,kBAAkB;AACxB,IAAM,oBAAoB;AAE1B,SAAS,eAAe,MAA0B;AAGhD,QAAM,MAAM,KAAK,UAAU,IAAI;AAC/B,SAAO;AAAA;AAAA;AAAA;AAAA,eAIM,iBAAiB;AAAA;AAAA;AAAA,gBAGhB,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUnB;AAGA,eAAsB,mBAAmB,SAAuD;AAC9F,QAAM,MAAM,QAAQ,aAAa,QAAQ,QAAQ,UAAU,IAAI;AAC/D,QAAM,WAAW,eAAe,QAAQ,UAAU;AAElD,QAAM,SAAiB,aAAa,CAAC,KAAK,QAAQ;AAChD,SAAK,MAAM,IAAI,OAAO,KAAK,GAAG;AAAA,EAChC,CAAC;AAED,iBAAe,MAAM,QAAgB,KAAoC;AACvE,QAAI,UAAU,+BAA+B,GAAG;AAChD,UAAM,OAAO,mBAAmB,OAAO,MAAM,GAAG,EAAE,CAAC,CAAC;AAEpD,QAAI,SAAS,iBAAiB;AAC5B,UAAI,UAAU,KAAK,EAAE,gBAAgB,KAAK,OAAO,EAAE,CAAC;AACpD,UAAI,IAAI,QAAQ;AAChB;AAAA,IACF;AACA,QAAI,SAAS,mBAAmB;AAC9B,UAAI,UAAU,KAAK,EAAE,gBAAgB,KAAK,KAAK,EAAE,CAAC;AAClD,UAAI,IAAI,QAAQ,UAAU;AAC1B;AAAA,IACF;AAEA,QAAI,CAAC,KAAK;AACR,UAAI,UAAU,GAAG,EAAE,IAAI,WAAW;AAClC;AAAA,IACF;AAGA,UAAM,WAAW,UAAU,IAAI,EAAE,QAAQ,iBAAiB,EAAE,EAAE,QAAQ,WAAW,EAAE;AACnF,QAAI,WAAW,KAAK,KAAK,QAAQ;AACjC,QAAI,CAAC,SAAS,WAAW,GAAG,GAAG;AAC7B,UAAI,UAAU,GAAG,EAAE,IAAI,WAAW;AAClC;AAAA,IACF;AACA,QAAI;AACF,UAAI,OAAO,MAAMC,MAAK,QAAQ,EAAE,MAAM,MAAM,IAAI;AAChD,UAAI,MAAM,YAAY,GAAG;AACvB,mBAAW,KAAK,UAAU,YAAY;AACtC,eAAO,MAAMA,MAAK,QAAQ,EAAE,MAAM,MAAM,IAAI;AAAA,MAC9C;AACA,UAAI,SAAS,OAAO,CAAC,MAAM;AACzB,mBAAW,KAAK,KAAK,YAAY;AACjC,eAAO,MAAMA,MAAK,QAAQ,EAAE,MAAM,MAAM,IAAI;AAAA,MAC9C;AACA,UAAI,CAAC,MAAM,OAAO,GAAG;AACnB,YAAI,UAAU,GAAG,EAAE,IAAI,WAAW;AAClC;AAAA,MACF;AACA,YAAM,OAAO,MAAMC,UAAS,QAAQ;AACpC,UAAI,UAAU,KAAK,EAAE,gBAAgB,KAAKC,SAAQ,QAAQ,EAAE,YAAY,CAAC,KAAK,2BAA2B,CAAC;AAC1G,UAAI,IAAI,IAAI;AAAA,IACd,QAAQ;AACN,UAAI,UAAU,GAAG,EAAE,IAAI,cAAc;AAAA,IACvC;AAAA,EACF;AAEA,QAAM,IAAI,QAAc,CAAC,kBAAkB,OAAO,OAAO,GAAG,aAAa,aAAa,CAAC;AACvF,QAAM,UAAU,OAAO,QAAQ;AAC/B,QAAM,SAAS,oBAAoB,QAAQ,IAAI;AAE/C,SAAO;AAAA,IACL;AAAA,IACA,SAAS,GAAG,MAAM,GAAG,eAAe;AAAA,IACpC,OAAO,MAAM,IAAI,QAAc,CAAC,KAAK,QAAQ,OAAO,MAAM,CAAC,QAAS,MAAM,IAAI,GAAG,IAAI,IAAI,CAAE,CAAC;AAAA,EAC9F;AACF;;;AE/HA,SAAS,gBAAAC,qBAAsD;AAC/D,SAAS,aAA6B;AACtC,SAAS,QAAAC,OAAM,aAAAC,YAAW,WAAAC,gBAAe;AAKzC,IAAM,iBAAiB;AAEvB,IAAM,eAAe;AA0BrB,SAAS,SAAS,KAAa,SAAgC;AAC7D,QAAM,WAAWC,WAAU,OAAO,EAAE,QAAQ,iBAAiB,EAAE,EAAE,QAAQ,WAAW,EAAE;AACtF,QAAM,OAAOC,MAAK,KAAK,QAAQ;AAC/B,SAAO,KAAK,WAAW,GAAG,IAAI,OAAO;AACvC;AAGA,SAAS,UAAU,KAAqB,SAAoC;AAC1E,MAAI,UAAU,KAAK;AAAA,IACjB,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,YAAY;AAAA,EACd,CAAC;AACD,MAAI,MAAM,iBAAiB;AAC3B,UAAQ,IAAI,GAAG;AACf,MAAI,GAAG,SAAS,MAAM,QAAQ,OAAO,GAAG,CAAC;AAC3C;AAGA,eAAe,cAAc,KAAqB,YAAoB,MAA6B;AACjG,QAAM,MAAM,KAAK,MAAM,eAAe,MAAM,KAAK;AACjD,QAAM,MAAM,SAAS,YAAY,GAAG;AACpC,MAAI,OAAQ,MAAM,SAAS,KAAK,GAAG,EAAI;AACvC,MAAI,UAAU,GAAG,EAAE,IAAI,yBAAyB;AAClD;AAGA,eAAe,UAAU,KAAqB,QAAgB,MAA6B;AACzF,QAAM,MAAM,SAAS,QAAQ,SAAS,MAAM,eAAe,IAAI;AAC/D,MAAI,OAAQ,MAAM,SAAS,KAAK,GAAG,EAAI;AACvC,MAAI,MAAM,SAAS,KAAKA,MAAK,QAAQ,YAAY,CAAC,EAAG;AACrD,MAAI,UAAU,GAAG,EAAE,IAAI,WAAW;AACpC;AAGA,SAAS,aAAa,KAAa,YAAoB,MAAqC;AAC1F,MAAI,QAA8C;AAClD,QAAM,UAAU,MAAY;AAC1B,QAAI,MAAO,cAAa,KAAK;AAC7B,YAAQ,WAAW,MAAM,UAAU;AAAA,EACrC;AACA,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,KAAK,EAAE,WAAW,KAAK,GAAG,OAAO;AAAA,EACnD,QAAQ;AAEN,cAAU,MAAM,KAAK,OAAO;AAAA,EAC9B;AACA,SAAO;AAAA,IACL,QAAQ;AACN,UAAI,MAAO,cAAa,KAAK;AAC7B,cAAQ,MAAM;AAAA,IAChB;AAAA,EACF;AACF;AAGA,eAAsB,cAAc,SAA6C;AAC/E,QAAM,SAASC,SAAQ,QAAQ,MAAM;AACrC,QAAM,aAAaA,SAAQ,QAAQ,UAAU;AAC7C,QAAM,UAAU,oBAAI,IAAoB;AACxC,QAAM,YAAY,MAAY;AAC5B,eAAW,OAAO,QAAS,KAAI,MAAM;AAAA,QAAuB,KAAK,IAAI,CAAC;AAAA;AAAA,CAAM;AAAA,EAC9E;AAEA,QAAM,SAAiBC,cAAa,CAAC,KAAK,QAAQ;AAChD,YAAQ,GAAG;AACX,QAAI,UAAU,iBAAiB,UAAU;AACzC,UAAM,OAAO,oBAAoB,IAAI,OAAO,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC;AAC9D,QAAI,SAAS,cAAc;AACzB,gBAAU,KAAK,OAAO;AAAA,IACxB,WAAW,SAAS,eAAe,MAAM,GAAG,EAAE,KAAK,KAAK,WAAW,cAAc,GAAG;AAClF,WAAK,cAAc,KAAK,YAAY,IAAI;AAAA,IAC1C,OAAO;AACL,WAAK,UAAU,KAAK,QAAQ,IAAI;AAAA,IAClC;AAAA,EACF,CAAC;AAED,QAAM,IAAI,QAAc,CAAC,MAAM,OAAO,OAAO,QAAQ,QAAQ,GAAG,aAAa,CAAC,CAAC;AAC/E,QAAM,EAAE,KAAK,IAAI,OAAO,QAAQ;AAChC,QAAM,SAAS,oBAAoB,IAAI;AACvC,QAAM,SAAS,GAAG,MAAM,SAAS,mBAAmB,cAAc,CAAC;AACnE,QAAM,UAAU,aAAa,YAAY,QAAQ,cAAc,KAAK,SAAS;AAE7E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,OAAO,MACL,IAAI,QAAc,CAAC,KAAK,QAAQ;AAC9B,cAAQ,MAAM;AACd,iBAAW,KAAK,QAAS,GAAE,IAAI;AAC/B,cAAQ,MAAM;AACd,aAAO,MAAM,CAAC,QAAS,MAAM,IAAI,GAAG,IAAI,IAAI,CAAE;AAAA,IAChD,CAAC;AAAA,EACL;AACF;;;AClIA,SAAS,SAAS,YAAAC,WAAU,QAAAC,aAAY;AACxC,SAAS,QAAAC,OAAM,WAAAC,gBAAe;AAG9B,IAAM,YAAkD;AAAA,EACtD,EAAE,OAAO,gBAAgB,IAAI,0BAA0B;AAAA,EACvD,EAAE,OAAO,oBAAoB,IAAI,8BAA8B;AAAA,EAC/D,EAAE,OAAO,SAAS,IAAI,qDAAqD;AAAA,EAC3E,EAAE,OAAO,kBAAkB,IAAI,mCAAmC;AAAA,EAClE,EAAE,OAAO,aAAa,IAAI,8BAA8B;AAAA,EACxD,EAAE,OAAO,gBAAgB,IAAI,sCAAsC;AAAA,EACnE,EAAE,OAAO,kBAAkB,IAAI,wCAAwC;AAAA,EACvE,EAAE,OAAO,aAAa,IAAI,mCAAmC;AAAA,EAC7D,EAAE,OAAO,mBAAmB,IAAI,6BAA6B;AAAA,EAC7D,EAAE,OAAO,oBAAoB,IAAI,uCAAuC;AAAA,EACxE,EAAE,OAAO,uBAAuB,IAAI,0CAA0C;AAAA,EAC9E,EAAE,OAAO,4BAA4B,IAAI,8EAA8E;AAAA,EACvH,EAAE,OAAO,4BAA4B,IAAI,8EAA8E;AACzH;AAEA,IAAM,YAAY,oBAAI,IAAI,CAAC,OAAO,QAAQ,QAAQ,SAAS,MAAM,CAAC;AAClE,IAAM,YAAY,oBAAI,IAAI,CAAC,gBAAgB,QAAQ,UAAU,UAAU,CAAC;AAExE,eAAe,aAAa,KAAa,KAA8B;AACrE,aAAW,SAAS,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC,GAAG;AAC/D,QAAI,MAAM,YAAY,GAAG;AACvB,UAAI,UAAU,IAAI,MAAM,IAAI,EAAG;AAC/B,YAAM,aAAaD,MAAK,KAAK,MAAM,IAAI,GAAG,GAAG;AAAA,IAC/C,WAAW,MAAM,OAAO,KAAK,UAAU,IAAIC,SAAQ,MAAM,IAAI,EAAE,YAAY,CAAC,GAAG;AAC7E,UAAI,KAAKD,MAAK,KAAK,MAAM,IAAI,CAAC;AAAA,IAChC;AAAA,EACF;AACF;AAQA,eAAsB,qBAAqB,KAAgC;AACzE,QAAM,OAAO,MAAMD,MAAK,GAAG,EAAE,MAAM,MAAM,IAAI;AAC7C,MAAI,CAAC,MAAM,YAAY,EAAG,QAAO,CAAC;AAClC,QAAM,QAAkB,CAAC;AACzB,QAAM,aAAa,KAAK,KAAK;AAC7B,QAAM,QAAQ,oBAAI,IAAY;AAC9B,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,MAAMD,UAAS,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE;AACxD,eAAW,EAAE,OAAO,GAAG,KAAK,WAAW;AACrC,UAAI,GAAG,KAAK,IAAI,EAAG,OAAM,IAAI,KAAK;AAAA,IACpC;AAAA,EACF;AACA,SAAO,CAAC,GAAG,KAAK;AAClB;;;AC3DA,SAAS,QAAAI,aAAY;AACrB,SAAS,QAAAC,OAAM,WAAAC,gBAAe;AAE9B,eAAe,OAAO,MAAgC;AACpD,UAAQ,MAAMF,MAAK,IAAI,EAAE,MAAM,MAAM,IAAI,IAAI,OAAO,KAAK;AAC3D;AAWA,eAAsB,kBAAkB,QAA6D;AACnG,QAAM,OAAOE,SAAQ,MAAM;AAC3B,QAAM,OAAOD,MAAK,MAAM,MAAM;AAC9B,MAAI,MAAM,OAAOA,MAAK,MAAM,YAAY,CAAC,EAAG,QAAO,EAAE,KAAK,MAAM,WAAWA,MAAK,MAAM,YAAY,EAAE;AACpG,MAAI,MAAM,OAAOA,MAAK,MAAM,YAAY,CAAC,EAAG,QAAO,EAAE,KAAK,MAAM,WAAWA,MAAK,MAAM,YAAY,EAAE;AACpG,QAAM,IAAI,MAAM,0BAA0BA,MAAK,MAAM,YAAY,CAAC,OAAOA,MAAK,MAAM,YAAY,CAAC,4BAA4B;AAC/H;;;AC5BA,SAAS,QAAQ,iBAAiB;AAClC,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAC9B,SAAS,WAAAE,UAAS,QAAAC,aAAY;AAC9B,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;;;ACLzB,SAAS,gBAAAC,qBAAoB;AAE7B,SAAS,YAAAC,WAAU,gBAAgB;AACnC,SAAS,SAAS,WAAAC,UAAS,WAAW;AACtC,SAAS,uBAAuB;AAShC,eAAsB,uBAAuB,UAAoE;AAC/G,QAAM,QAAQ,MAAM,SAAS,QAAQ;AACrC,QAAM,OAAO,QAAQ,KAAK;AAC1B,QAAM,OAAO,MAAMC,UAAS,KAAK;AACjC,QAAM,SAASC,cAAa,CAAC,SAAS,aAAa;AACjD,YAAQ,QAAQ;AAChB,UAAM,YAAY;AAChB,YAAM,WAAW,mBAAmB,IAAI,IAAI,QAAQ,OAAO,KAAK,kBAAkB,EAAE,QAAQ;AAC5F,UAAI,aAAa,OAAO,aAAa,eAAe;AAClD,iBAAS,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACtE,iBAAS,IAAI,IAAI;AACjB;AAAA,MACF;AACA,YAAM,OAAO,MAAM,SAASC,SAAQ,MAAM,IAAI,QAAQ,EAAE,CAAC;AACzD,UAAI,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,GAAG,EAAE,GAAG;AAAE,iBAAS,UAAU,GAAG,EAAE,IAAI;AAAG;AAAA,MAAQ;AAChF,UAAI,CAAC,MAAM,SAAS,UAAU,IAAI,EAAG,UAAS,UAAU,GAAG,EAAE,IAAI;AAAA,IACnE,GAAG,EAAE,MAAM,MAAM;AAAE,UAAI,CAAC,SAAS,YAAa,UAAS,UAAU,GAAG;AAAG,eAAS,IAAI;AAAA,IAAG,CAAC;AAAA,EAC1F,CAAC;AACD,QAAM,IAAI,QAAc,CAAC,MAAM,WAAW;AACxC,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,GAAG,aAAa,MAAM;AAAE,aAAO,IAAI,SAAS,MAAM;AAAG,WAAK;AAAA,IAAG,CAAC;AAAA,EAC9E,CAAC;AACD,QAAM,cAAc,MAAM,IAAI,QAAc,CAAC,MAAM,WAAW;AAC5D,WAAO,oBAAoB;AAC3B,WAAO,MAAM,CAAC,UAAU,QAAQ,OAAO,KAAK,IAAI,KAAK,CAAC;AAAA,EACxD,CAAC;AACD,MAAI;AACF,UAAM,YAAY,oBAAqB,OAAO,QAAQ,EAAkB,IAAI;AAC5E,UAAM,OAAO,MAAM,gBAAgB,EAAE,SAAS;AAAA,MAC5C,MAAM;AAAA,MAAa,MAAM;AAAA,MAAG;AAAA,IAC9B,EAAE,CAAC;AACH,WAAO,EAAE,KAAK,KAAK,KAAK,OAAO,YAAY;AAAE,UAAI;AAAE,cAAM,KAAK,MAAM;AAAA,MAAG,UAAE;AAAU,cAAM,YAAY;AAAA,MAAG;AAAA,IAAE,EAAE;AAAA,EAC9G,SAAS,OAAO;AACd,UAAM,YAAY;AAClB,UAAM;AAAA,EACR;AACF;;;ADrBO,SAAS,oBAAoB,MAA4C;AAC9E,QAAM,YAAY,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,WAAW,IAAI;AACrD,QAAM,SAAS,YAAY,KAAK,CAAC,IAAI;AACrC,QAAM,OAAO,YAAY,KAAK,MAAM,CAAC,IAAI;AACzC,QAAM,SAAS,eAAe,KAAK,MAAM;AACzC,MAAI,OAAQ,KAAI,IAAI,MAAM;AAAA,WACjB,uBAAuB,KAAK,MAAM,KAAK,CAAC,gBAAgB,KAAK,MAAM,EAAG,OAAM,IAAI,MAAM,+BAA+B;AAC9H,QAAM,UAA6B,EAAE,GAAI,SAAS,EAAE,KAAK,OAAO,IAAI,EAAE,WAAW,OAAO,GAAI,QAAQ,eAAe,UAAU,UAAU,OAAO,MAAM,QAAQ,KAAK,OAAO,KAAK;AAC7K,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,OAAO,KAAK,KAAK;AACvB,UAAM,QAAQ,KAAK,QAAQ,CAAC;AAC5B,QAAI,CAAC,SAAS,MAAM,WAAW,IAAI,EAAG,OAAM,IAAI,MAAM,qBAAqB,IAAI,EAAE;AACjF,YAAQ,MAAM;AAAA,MACZ,KAAK;AAAY,gBAAQ,SAAS;AAAO;AAAA,MACzC,KAAK;AAAc,gBAAQ,WAAW;AAAO;AAAA,MAC7C,KAAK;AAAc,gBAAQ,WAAW;AAAO;AAAA,MAC7C,KAAK;AAAoB,gBAAQ,gBAAgB;AAAO;AAAA,MACxD,KAAK;AAAW,gBAAQ,QAAQ,QAAQ,OAAO,MAAM,GAAG,IAAI;AAAG;AAAA,MAC/D,KAAK;AAAY,gBAAQ,SAAS,QAAQ,OAAO,MAAM,GAAG,IAAI;AAAG;AAAA,MACjE,KAAK;AAAW,gBAAQ,QAAQ,QAAQ,OAAO,MAAM,GAAG,GAAK;AAAG;AAAA,MAChE;AAAS,cAAM,IAAI,MAAM,8BAA8B,IAAI,EAAE;AAAA,IAC/D;AAAA,EACF;AACA,MAAI,CAAC,QAAQ,OAAO,YAAY,EAAE,SAAS,MAAM,EAAG,OAAM,IAAI,MAAM,2BAA2B;AAC/F,MAAI,QAAQ,YAAY,UAAW,OAAM,IAAI,MAAM,sCAAsC;AACzF,SAAO;AACT;AAQA,eAAsB,kBAAkB,SAA2C;AACjF,QAAM,eAAe;AACrB,QAAM,UAAU,QAAQ,MAAM,SAAY,MAAM;AAAA,IAC9C,QAAQ,aAAa,MAAM,kBAAkB,QAAQ,aAAa,GAAG,GAAG;AAAA,EAC1E;AACA,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,SAAS,OAAO,EAAE,SAAS,WAAW,CAAC;AACvD,UAAM,OAAO,MAAM,QAAQ,QAAQ,EAAE,UAAU,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,EAAE,CAAC;AACjG,SAAK,kBAAkB,GAAK;AAC5B,UAAM,KAAK,KAAK,QAAQ,OAAO,QAAS,KAAK,EAAE,WAAW,mBAAmB,CAAC;AAC9E,QAAI,SAAS;AACX,YAAM,KAAK,gBAAgB,MAAM;AAC/B,cAAM,SAAS,SAAS,cAAiC,gBAAgB;AACzE,eAAO,QAAQ,QAAQ,MAAM;AAAA,MAC/B,CAAC;AAAA,IACH;AACA,UAAM,QAAQ,KAAK,QAAQ,QAAQ,QAAQ;AAC3C,UAAM,MAAM,QAAQ,EAAE,OAAO,UAAU,CAAC;AACxC,UAAM,MAAM,SAAS,CAAC,SAAS,SAAS;AACtC,UAAI,EAAE,mBAAmB,mBAAoB,OAAM,IAAI,MAAM,4CAA4C;AACzG,aAAO,OAAO,QAAQ,OAAO,EAAE,OAAO,GAAG,KAAK,KAAK,MAAM,QAAQ,GAAG,KAAK,MAAM,MAAM,UAAU,QAAQ,WAAW,QAAQ,WAAW,aAAa,CAAC;AAAA,IACrJ,GAAG,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AACnD,UAAM,UAAU,MAAM,aAAa;AACnC,UAAM,QAAQ,QAAQ,MAAM,EAAE,QAAQ,EAAE,OAAO,UAAU,CAAC;AAC1D,QAAI,QAAQ,cAAe,OAAM,QAAQ,QAAQ,QAAQ,aAAa,EAAE,QAAQ,EAAE,OAAO,UAAU,CAAC;AACpG,UAAM,QAAQ,QAAQ,MAAM,EAAE,SAAS,YAAY;AAAE,YAAM,SAAS,MAAM;AAAA,IAAO,CAAC;AAClF,UAAM,KAAK,eAAe,QAAQ,KAAK;AACvC,UAAM,MAAM,MAAM,MAAM,WAAW,EAAE,MAAM,OAAO,SAAS,IAAM,CAAC;AAClE,UAAM,UAAU,QAAQ,QAAQ,KAAK,EAAE,MAAM,KAAK,CAAC;AAAA,EACrD,UAAE;AACA,QAAI;AAAE,YAAM,SAAS,MAAM;AAAA,IAAG,UAAE;AAAU,YAAM,SAAS,MAAM;AAAA,IAAG;AAAA,EACpE;AACF;AAEA,eAAe,iBAAgC;AAC7C,MAAI;AAAE,UAAM,OAAO,SAAS,eAAe,CAAC;AAAG;AAAA,EAAQ,QAAQ;AAAA,EAAmD;AAClH,UAAQ,MAAM,+CAA+C;AAC7D,QAAMC,WAAU,cAAc,YAAY,GAAG;AAC7C,QAAM,MAAMC,MAAKC,SAAQF,SAAQ,QAAQ,yBAAyB,CAAC,GAAG,QAAQ;AAC9E,QAAM,UAAU,QAAQ,EAAE,QAAQ,UAAU,CAAC,KAAK,WAAW,UAAU,GAAG,EAAE,SAAS,KAAO,CAAC;AAC/F;AAEA,SAAS,QAAQ,OAAe,MAAc,KAAa,KAAqB;AAC9E,QAAM,SAAS,OAAO,KAAK;AAC3B,MAAI,CAAC,OAAO,UAAU,MAAM,KAAK,SAAS,OAAO,SAAS,IAAK,OAAM,IAAI,MAAM,GAAG,IAAI,4BAA4B,GAAG,OAAO,GAAG,EAAE;AACjI,SAAO;AACT;;;AN5DA,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8Bb,SAAS,UAAU,MAA4B;AAC7C,QAAM,OAAmB;AAAA,IACvB,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,UAAU;AAAA,IACV,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AACA,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,UAAM,MAAM,KAAK,CAAC;AAClB,UAAM,QAAQ,MAAc;AAC1B,YAAM,IAAI,KAAK,IAAI,CAAC;AACpB,UAAI,MAAM,UAAa,EAAE,WAAW,IAAI,EAAG,OAAM,IAAI,MAAM,qBAAqB,GAAG,EAAE;AACrF,WAAK;AACL,aAAO;AAAA,IACT;AACA,YAAQ,KAAK;AAAA,MACX,KAAK;AAAA,MACL,KAAK;AACH,aAAK,OAAO;AACZ;AAAA,MACF,KAAK;AACH,aAAK,MAAM,MAAM;AACjB;AAAA,MACF,KAAK,cAAc;AACjB,cAAM,IAAI,MAAM;AAChB,YAAI,MAAM,YAAY,MAAM,UAAU,MAAM,QAAS,OAAM,IAAI,MAAM,qBAAqB,CAAC,EAAE;AAC7F,aAAK,WAAW;AAChB;AAAA,MACF;AAAA,MACA,KAAK;AACH,aAAK,MAAM,MAAM;AACjB;AAAA,MACF,KAAK;AACH,aAAK,iBAAiB,OAAO,MAAM,CAAC;AACpC;AAAA,MACF,KAAK;AACH,aAAK,WAAW,OAAO,MAAM,CAAC;AAC9B;AAAA,MACF,KAAK;AACH,aAAK,cAAc;AACnB;AAAA,MACF,KAAK;AACH,aAAK,kBAAkB;AACvB;AAAA,MACF,KAAK;AACH,aAAK,KAAK;AACV;AAAA,MACF,KAAK;AACH,aAAK,OAAO,OAAO,MAAM,CAAC;AAC1B;AAAA,MACF,KAAK;AACH,aAAK,OAAO;AACZ;AAAA,MACF,KAAK;AACH,aAAK,OAAO,MAAM;AAClB;AAAA,MACF;AACE,YAAI,IAAI,WAAW,IAAI,EAAG,OAAM,IAAI,MAAM,mBAAmB,GAAG,EAAE;AAClE,YAAI,KAAK,OAAQ,OAAM,IAAI,MAAM,8BAA8B,GAAG,EAAE;AACpE,aAAK,SAAS;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,YAAY,SAAiB,iBAA0B,gBAAiD;AACrH,QAAM,UAAU,MAAMG,UAAS,OAAO,EAAE,MAAM,CAAC,cAAc,EAAE,CAAC;AAChE,MAAI;AACF,UAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,SAAK,GAAG,aAAa,CAAC,MAAM,QAAQ,OAAO,MAAM,qBAAqB,EAAE,OAAO;AAAA,CAAI,CAAC;AACpF,UAAM,KAAK,KAAK,SAAS,EAAE,WAAW,OAAO,CAAC;AAC9C,UAAM,KAAK;AAAA,MACT,MAAM,OAAO,wBAAwB,UAAa,OAAO,yBAAyB;AAAA,MAClF;AAAA,MACA,EAAE,SAAS,eAAe;AAAA,IAC5B;AACA,UAAM,QAAQ,MAAM,KAAK,SAAS,MAAM,OAAO,oBAAoB;AACnE,QAAI,MAAO,OAAM,IAAI,MAAM,wBAAwB,KAAK,EAAE;AAC1D,UAAM,OAAO,MAAM,KAAK,SAAS,MAAM,OAAO,mBAAmB;AACjE,QAAI,CAAC,KAAM,OAAM,IAAI,MAAM,iCAAiC;AAC5D,SAAK;AACL,WAAO;AAAA,EACT,UAAE;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AACF;AAEA,eAAe,MAAM,MAAgC;AACnD,UAAQ,MAAMC,MAAK,IAAI,EAAE,MAAM,MAAM,IAAI,IAAI,YAAY,KAAK;AAChE;AAGA,SAAS,YAAY,KAAmB;AACtC,MAAI;AACF,QAAI,QAAQ,aAAa,UAAU;AACjC,YAAM,QAAQ,CAAC,GAAG,GAAG,EAAE,OAAO,UAAU,UAAU,KAAK,CAAC,EAAE,MAAM;AAAA,IAClE,WAAW,QAAQ,aAAa,SAAS;AACvC,YAAM,OAAO,CAAC,MAAM,SAAS,IAAI,GAAG,GAAG,EAAE,OAAO,UAAU,UAAU,KAAK,CAAC,EAAE,MAAM;AAAA,IACpF,OAAO;AACL,YAAM,YAAY,CAAC,GAAG,GAAG,EAAE,OAAO,UAAU,UAAU,KAAK,CAAC,EAAE,MAAM;AAAA,IACtE;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAGA,eAAe,UAAU,MAAmC;AAC1D,MAAI,KAAK,KAAK;AACZ,YAAQ,OAAO,MAAM,6EAA6E;AAClG,WAAO;AAAA,EACT;AACA,MAAI,CAAC,KAAK,QAAQ;AAChB,YAAQ,OAAO,MAAM;AAAA;AAAA,EAAmC,IAAI,EAAE;AAC9D,WAAO;AAAA,EACT;AACA,QAAM,WAAW,MAAM,kBAAkB,KAAK,MAAM;AACpD,QAAM,OAAOC,SAAQ,cAAc,YAAY,GAAG,CAAC;AACnD,QAAM,SAASC,MAAK,MAAM,IAAI;AAC9B,MAAI,CAAE,MAAM,MAAM,MAAM,GAAI;AAC1B,YAAQ,OAAO,MAAM,wCAAwC,MAAM;AAAA,CAAuC;AAC1G,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,MAAM,cAAc,EAAE,QAAQ,YAAY,SAAS,KAAK,MAAM,KAAK,KAAK,CAAC;AAExF,QAAM,QAAQ,KAAK,OAAO,MAAM,KAAK,MAAM,EAAE,KAAKC,SAAQ,KAAK,MAAM,GAAG,OAAO,MAAM,OAAO,UAAU,CAAC,IAAI;AAE3G,UAAQ,OAAO;AAAA,IACb;AAAA,oCAAkC,OAAO,MAAM;AAAA,cAC9B,SAAS,GAAG,GAAG,KAAK,OAAO,eAAe,KAAK,IAAI,MAAM,EAAE;AAAA;AAAA;AAAA;AAAA,EAE9E;AACA,MAAI,KAAK,KAAM,aAAY,OAAO,MAAM;AAExC,QAAM,IAAI,QAAc,CAAC,gBAAgB;AACvC,QAAI,UAAU;AACd,UAAM,WAAW,MAAY;AAC3B,UAAI,QAAS;AACb,gBAAU;AACV,aAAO,KAAK;AACZ,WAAK,OAAO,MAAM,EAAE,QAAQ,WAAW;AAAA,IACzC;AACA,YAAQ,GAAG,UAAU,QAAQ;AAC7B,YAAQ,GAAG,WAAW,QAAQ;AAAA,EAChC,CAAC;AACD,SAAO;AACT;AAEA,eAAe,KAAK,MAAiC;AACnD,MAAI;AACJ,MAAI;AACF,WAAO,UAAU,IAAI;AAAA,EACvB,SAAS,KAAK;AACZ,YAAQ,OAAO,MAAM,GAAG,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA;AAAA,EAAO,IAAI,EAAE;AACrF,WAAO;AAAA,EACT;AACA,MAAI,KAAK,MAAM;AACb,YAAQ,OAAO,MAAM,IAAI;AACzB,WAAO;AAAA,EACT;AAEA,MAAI,KAAK,IAAI;AACX,WAAO,UAAU,IAAI;AAAA,EACvB;AAGA,MAAI,aAA4B;AAChC,MAAI;AACJ,MAAI,mBAA6B,CAAC;AAClC,MAAI;AAEJ,MAAI,KAAK,KAAK;AACZ,cAAU,KAAK;AACf,mBAAe,OAAO,MAAM,MAAM,KAAK,GAAG,GAAG,KAAK;AAAA,EACpD,OAAO;AACL,QAAI,CAAC,KAAK,QAAQ;AAChB,cAAQ,OAAO,MAAM;AAAA;AAAA,EAA4C,IAAI,EAAE;AACvE,aAAO;AAAA,IACT;AACA,UAAM,WAAW,MAAM,kBAAkB,KAAK,MAAM;AACpD,iBAAa,SAAS;AACtB,mBAAe,MAAMC,UAAS,SAAS,WAAW,MAAM;AACxD,uBAAmB,MAAM,qBAAqB,SAAS,GAAG;AAC1D,cAAU;AAAA,EACZ;AAEA,QAAM,OAAOH,SAAQ,cAAc,YAAY,GAAG,CAAC;AACnD,QAAM,aAAa,MAAMG,UAASF,MAAK,MAAM,uBAAuB,GAAG,MAAM;AAE7E,QAAM,SAAS,MAAM,mBAAmB;AAAA,IACtC;AAAA,IACA;AAAA,IACA,YAAY;AAAA,MACV,KAAK;AAAA,MACL,gBAAgB,KAAK;AAAA,MACrB,UAAU,KAAK;AAAA,MACf,aAAa,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,MAAI;AACJ,MAAI;AAGF,UAAM,gBAAgB,KAAK,iBAAiB,KAAK,YAAY,IAAI;AACjE,WAAO,MAAM,YAAY,OAAO,SAAS,KAAK,iBAAiB,YAAY;AAAA,EAC7E,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AAEA,QAAM,UAAU,aAAa,EAAE,cAAc,MAAM,iBAAiB,CAAC;AACrE,QAAMG,OAAM,eAAe,OAAO;AAClC,QAAM,SAAS,OAAOA,MAAK,KAAK,QAAQ;AAExC,MAAI,KAAK,KAAK;AACZ,UAAMC,WAAU,KAAK,KAAK,GAAG,MAAM;AAAA,GAAM,MAAM;AAC/C,YAAQ,OAAO,MAAM,qBAAqB,KAAK,GAAG;AAAA,CAAI;AAAA,EACxD,OAAO;AACL,YAAQ,OAAO,MAAM,GAAG,MAAM;AAAA,CAAI;AAAA,EACpC;AAEA,SAAOD,KAAI,KAAK,IAAI;AACtB;AAEA,eAAe,IAAI,MAAiC;AAClD,MAAI,KAAK,CAAC,MAAM,aAAc,QAAO,KAAK,IAAI;AAC9C,MAAI,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,IAAI,GAAG;AAClD,YAAQ,IAAI,oKAAoK;AAChL,WAAO;AAAA,EACT;AACA,QAAM,UAAU,oBAAoB,KAAK,MAAM,CAAC,CAAC;AACjD,QAAM,kBAAkB,OAAO;AAC/B,UAAQ,IAAI,uBAAuB,QAAQ,MAAM,EAAE;AACnD,SAAO;AACT;AAEA,IAAI,QAAQ,KAAK,MAAM,CAAC,CAAC,EACtB,KAAK,CAAC,SAAS;AACd,UAAQ,WAAW;AACrB,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,UAAQ,OAAO,MAAM,wBAAwB,eAAe,QAAQ,IAAI,SAAS,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AAC9G,UAAQ,WAAW;AACrB,CAAC;","names":["readFile","writeFile","stat","join","dirname","resolve","chromium","readFile","stat","extname","stat","readFile","extname","createServer","join","normalize","resolve","normalize","join","resolve","createServer","readFile","stat","join","extname","stat","join","resolve","dirname","join","createServer","readFile","resolve","readFile","createServer","resolve","require","join","dirname","chromium","stat","dirname","join","resolve","readFile","run","writeFile"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/server.ts","../src/static.ts","../src/ui-server.ts","../src/scan.ts","../src/resolve-napplet.ts","../src/screenshot.ts","../src/screenshot-preview.ts","../src/manifest.ts"],"sourcesContent":["/**\n * @napplet/conformance-cli -- `napplet-conformance` entry point.\n *\n * Runs the @napplet/conformance engine against a napplet in real headless Chromium:\n *\n * 1. Resolve the napplet's built directory (or a remote `--url`).\n * 2. Serve it on loopback alongside a host harness page + the engine bundle.\n * 3. Drive Playwright Chromium to `bootAndCollect` inside the host page.\n * 4. Assemble a ConformanceContext on the node side (manifest HTML + static scan),\n * run the check catalog, print the chosen report, and set the exit code.\n *\n * Exit codes: `0` conformant, `1` non-conformant, `2` usage/runtime error.\n *\n * @packageDocumentation\n */\n\nimport { readFile, writeFile, stat } from 'node:fs/promises';\nimport { join, dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { spawn } from 'node:child_process';\nimport { chromium } from 'playwright';\nimport {\n buildContext,\n runConformance,\n report,\n type BootCollection,\n type ReporterFormat,\n} from '@napplet/conformance';\nimport { startHarnessServer } from './server.js';\nimport { startUiServer } from './ui-server.js';\nimport { scanForbiddenGlobals } from './scan.js';\nimport { resolveNappletDir } from './resolve-napplet.js';\nimport { captureScreenshot, parseScreenshotArgs } from './screenshot.js';\nimport { verifyManifestArtifact } from './manifest.js';\n\ninterface CliOptions {\n target?: string;\n url?: string;\n manifest?: string;\n reporter: ReporterFormat;\n out?: string;\n readyTimeoutMs: number;\n settleMs: number;\n runDegraded: boolean;\n allowSameOrigin: boolean;\n ui: boolean;\n port?: number;\n open: boolean;\n exec?: string;\n help: boolean;\n}\n\nconst HELP = `napplet-conformance — run NAP protocol conformance against a napplet\n\nUsage:\n napplet-conformance [dir] [options] # headless, one-shot, CI exit code\n napplet-conformance --ui [dir] [options] # open the live web runtime (like vitest --ui)\n napplet-conformance --url https://my.napplet/ [options]\n napplet-conformance screenshot [directory|preview-url] [--output preview.png]\n\nArguments:\n dir Built napplet directory (prefers ./dist/index.html, falls back to ./index.html)\n\nOptions:\n --url <url> Test a remotely-served napplet instead of a local dir (headless)\n --manifest <file> Signed NIP-5D event JSON; verify its signature and raw artifact hash before boot (headless)\n --reporter <fmt> pretty | json | junit (default: pretty)\n --out <file> Write the report to a file instead of stdout\n --ready-timeout <ms> Boot timeout waiting for iframe load (default: 5000)\n --settle <ms> Envelope-collection window after boot (default: 600)\n --no-degraded Skip the no-capability graceful-degradation pass\n --allow-same-origin Debug: also grant allow-same-origin (a conformant napplet must not need it)\n\nUI / watch mode (--ui):\n --port <n> Port for the UI server (default: random)\n --no-open Do not open a browser automatically\n --exec <cmd> Run a command (e.g. \"vite build --watch\") so edits rebuild the served dir;\n the watcher then re-runs conformance live on every change\n -h, --help Show this help\n\nExit codes (headless): 0 conformant, 1 non-conformant, 2 usage/runtime error.\n`;\n\nfunction parseArgs(argv: string[]): CliOptions {\n const opts: CliOptions = {\n reporter: 'pretty',\n readyTimeoutMs: 5000,\n settleMs: 600,\n runDegraded: true,\n allowSameOrigin: false,\n ui: false,\n open: true,\n help: false,\n };\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n const value = (): string => {\n const v = argv[i + 1];\n if (v === undefined || v.startsWith('--')) throw new Error(`Missing value for ${arg}`);\n i += 1;\n return v;\n };\n switch (arg) {\n case '-h':\n case '--help':\n opts.help = true;\n break;\n case '--url':\n opts.url = value();\n break;\n case '--manifest':\n opts.manifest = value();\n break;\n case '--reporter': {\n const r = value();\n if (r !== 'pretty' && r !== 'json' && r !== 'junit') throw new Error(`Invalid reporter: ${r}`);\n opts.reporter = r;\n break;\n }\n case '--out':\n opts.out = value();\n break;\n case '--ready-timeout':\n opts.readyTimeoutMs = Number(value());\n break;\n case '--settle':\n opts.settleMs = Number(value());\n break;\n case '--no-degraded':\n opts.runDegraded = false;\n break;\n case '--allow-same-origin':\n opts.allowSameOrigin = true;\n break;\n case '--ui':\n opts.ui = true;\n break;\n case '--port':\n opts.port = Number(value());\n break;\n case '--no-open':\n opts.open = false;\n break;\n case '--exec':\n opts.exec = value();\n break;\n default:\n if (arg.startsWith('--')) throw new Error(`Unknown option: ${arg}`);\n if (opts.target) throw new Error(`Unexpected extra argument: ${arg}`);\n opts.target = arg;\n }\n }\n return opts;\n}\n\nasync function collectBoot(hostUrl: string, totalTimeoutMs: number): Promise<BootCollection> {\n const browser = await chromium.launch({ args: ['--no-sandbox'] });\n try {\n const page = await browser.newPage();\n page.on('pageerror', (e) => process.stderr.write(`[host page error] ${e.message}\\n`));\n await page.goto(hostUrl, { waitUntil: 'load' });\n await page.waitForFunction(\n () => window.__conformanceBoot__ !== undefined || window.__conformanceError__ !== undefined,\n undefined,\n { timeout: totalTimeoutMs },\n );\n const error = await page.evaluate(() => window.__conformanceError__);\n if (error) throw new Error(`Host harness failed: ${error}`);\n const boot = await page.evaluate(() => window.__conformanceBoot__);\n if (!boot) throw new Error('Host harness produced no result');\n return boot;\n } finally {\n await browser.close();\n }\n}\n\nasync function isDir(path: string): Promise<boolean> {\n return (await stat(path).catch(() => null))?.isDirectory() ?? false;\n}\n\n/** Best-effort: open a URL in the default browser. */\nfunction openBrowser(url: string): void {\n try {\n if (process.platform === 'darwin') {\n spawn('open', [url], { stdio: 'ignore', detached: true }).unref();\n } else if (process.platform === 'win32') {\n spawn('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true }).unref();\n } else {\n spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();\n }\n } catch {\n /* best-effort — the URL is printed regardless */\n }\n}\n\n/** `--ui`: serve the live web runtime + the napplet, watch, and re-run on change. */\nasync function runUiMode(opts: CliOptions): Promise<number> {\n if (opts.manifest) {\n process.stderr.write('--manifest is supported in headless mode; use a NIP-5D pointer in the web runtime for signed manifests.\\n');\n return 2;\n }\n if (opts.url) {\n process.stderr.write('--ui serves a local napplet directory; --url is not supported in UI mode.\\n');\n return 2;\n }\n if (!opts.target) {\n process.stderr.write(`Provide a napplet directory.\\n\\n${HELP}`);\n return 2;\n }\n const resolved = await resolveNappletDir(opts.target);\n const here = dirname(fileURLToPath(import.meta.url));\n const appDir = join(here, 'ui');\n if (!(await isDir(appDir))) {\n process.stderr.write(`Conformance UI assets are missing at ${appDir}. Rebuild @napplet/conformance-cli.\\n`);\n return 2;\n }\n\n const server = await startUiServer({ appDir, nappletDir: resolved.dir, port: opts.port });\n\n const child = opts.exec ? spawn(opts.exec, { cwd: resolve(opts.target), shell: true, stdio: 'inherit' }) : null;\n\n process.stdout.write(\n `\\n napplet conformance UI → ${server.appUrl}\\n` +\n ` watching ${resolved.dir}${opts.exec ? ` (running: ${opts.exec})` : ''}\\n` +\n ` Ctrl-C to stop\\n\\n`,\n );\n if (opts.open) openBrowser(server.appUrl);\n\n await new Promise<void>((resolveWait) => {\n let closing = false;\n const shutdown = (): void => {\n if (closing) return;\n closing = true;\n child?.kill();\n void server.close().finally(resolveWait);\n };\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n });\n return 0;\n}\n\nasync function main(argv: string[]): Promise<number> {\n let opts: CliOptions;\n try {\n opts = parseArgs(argv);\n } catch (err) {\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n\\n${HELP}`);\n return 2;\n }\n if (opts.help) {\n process.stdout.write(HELP);\n return 0;\n }\n\n if (opts.ui) {\n return runUiMode(opts);\n }\n\n // Resolve the subject: local dir or remote URL.\n let nappletDir: string | null = null;\n let artifact: Uint8Array;\n let forbiddenGlobals: string[] = [];\n let bootUrl: string;\n\n if (opts.url) {\n bootUrl = opts.url;\n const response = await fetch(opts.url);\n if (!response.ok) throw new Error(`Failed to fetch napplet HTML: ${response.status} ${response.statusText}`);\n artifact = new Uint8Array(await response.arrayBuffer());\n } else {\n if (!opts.target) {\n process.stderr.write(`Provide a napplet directory or --url.\\n\\n${HELP}`);\n return 2;\n }\n const resolved = await resolveNappletDir(opts.target);\n nappletDir = resolved.dir;\n artifact = await readFile(resolved.indexHtml);\n forbiddenGlobals = await scanForbiddenGlobals(resolved.dir);\n bootUrl = '/';\n }\n\n const manifestEvent = opts.manifest\n ? verifyManifestArtifact(JSON.parse(await readFile(opts.manifest, 'utf8')) as unknown, artifact)\n : null;\n const manifestHtml = new TextDecoder().decode(artifact);\n\n const here = dirname(fileURLToPath(import.meta.url));\n const hostBundle = await readFile(join(here, 'host-bundle.global.js'), 'utf8');\n const runtimePreludeSource = await readFile(fileURLToPath(import.meta.resolve('@napplet/shim/prelude.global')), 'utf8');\n\n const server = await startHarnessServer({\n nappletDir,\n hostBundle,\n bootConfig: {\n url: bootUrl,\n html: manifestHtml,\n readyTimeoutMs: opts.readyTimeoutMs,\n settleMs: opts.settleMs,\n runDegraded: opts.runDegraded,\n runtimePreludeSource,\n allowSameOrigin: opts.allowSameOrigin,\n },\n });\n\n let boot: BootCollection;\n try {\n // Generous overall budget: two boots (primary + degraded), each up to\n // readyTimeout + settle, plus browser startup.\n const totalTimeout = (opts.readyTimeoutMs + opts.settleMs) * 2 + 30000;\n boot = await collectBoot(server.hostUrl, totalTimeout);\n } finally {\n await server.close();\n }\n\n const context = buildContext({ manifestHtml, manifestEvent, boot, forbiddenGlobals, sandbox: { allowScripts: true, allowSameOrigin: opts.allowSameOrigin } });\n const run = runConformance(context);\n const output = report(run, opts.reporter);\n\n if (opts.out) {\n await writeFile(opts.out, `${output}\\n`, 'utf8');\n process.stdout.write(`Report written to ${opts.out}\\n`);\n } else {\n process.stdout.write(`${output}\\n`);\n }\n\n return run.ok ? 0 : 1;\n}\n\nasync function run(args: string[]): Promise<number> {\n if (args[0] !== 'screenshot') return main(args);\n if (args.includes('--help') || args.includes('-h')) {\n console.log('Usage: napplet screenshot [directory|preview-url] [--output preview.png] [--selector iframe] [--ready-selector <css>] [--width 1200] [--height 750] [--delay 1500]');\n return 0;\n }\n const options = parseScreenshotArgs(args.slice(1));\n await captureScreenshot(options);\n console.log(`Screenshot saved to ${options.output}`);\n return 0;\n}\n\nrun(process.argv.slice(2))\n .then((code) => {\n process.exitCode = code;\n })\n .catch((err: unknown) => {\n process.stderr.write(`napplet-conformance: ${err instanceof Error ? err.stack ?? err.message : String(err)}\\n`);\n process.exitCode = 2;\n });\n","/**\n * @napplet/conformance-cli -- Loopback harness server.\n *\n * Serves the napplet's files plus two reserved harness routes (the host page and\n * the browser bundle). Every response carries `Access-Control-Allow-Origin: *`:\n * the napplet iframe runs at an opaque origin (sandbox without `allow-same-origin`),\n * so its `<script type=\"module\">` subresources are fetched in CORS mode and would\n * otherwise be blocked.\n *\n * @packageDocumentation\n */\n\nimport { createServer, type Server, type ServerResponse } from 'node:http';\nimport { readFile, stat } from 'node:fs/promises';\nimport { join, normalize, extname, resolve } from 'node:path';\nimport type { AddressInfo } from 'node:net';\nimport { MIME } from './static.js';\n\n/** The napplet URL + timing knobs the host page passes to `bootAndCollect`. */\nexport interface BootConfig {\n url: string;\n html?: string;\n readyTimeoutMs: number;\n settleMs: number;\n runDegraded: boolean;\n runtimePreludeSource?: string;\n allowSameOrigin?: boolean;\n}\n\n/** Options for {@link startHarnessServer}. */\nexport interface HarnessServerOptions {\n /** Directory of napplet files to serve, or `null` in remote-URL mode. */\n nappletDir: string | null;\n /** JavaScript source of the host browser bundle. */\n hostBundle: string;\n /** Boot configuration injected into the host page. */\n bootConfig: BootConfig;\n}\n\n/** A running harness server. */\nexport interface HarnessServer {\n /** Base origin, e.g. `http://127.0.0.1:54213`. */\n origin: string;\n /** URL of the host harness page. */\n hostUrl: string;\n /** Shut the server down. */\n close(): Promise<void>;\n}\n\nconst HOST_HTML_ROUTE = '/__conformance__/host.html';\nconst HOST_BUNDLE_ROUTE = '/__conformance__/host-bundle.js';\n\nfunction renderHostPage(boot: BootConfig): string {\n // The inline script lives on the harness page (full privilege, our origin), not\n // in the sandboxed napplet — so it is not subject to the napplet CSP.\n const cfg = JSON.stringify(boot).replace(/</g, '\\\\u003c');\n return `<!doctype html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\"><title>napplet conformance host</title></head>\n<body>\n<script src=\"${HOST_BUNDLE_ROUTE}\"></script>\n<script>\n (function () {\n var cfg = ${cfg};\n window.NappletConformanceHost.run(cfg).then(function (boot) {\n window.__conformanceBoot__ = boot;\n }).catch(function (err) {\n window.__conformanceError__ = String((err && err.stack) || err);\n });\n })();\n</script>\n</body>\n</html>`;\n}\n\n/** Start the loopback harness server on a random port. */\nexport async function startHarnessServer(options: HarnessServerOptions): Promise<HarnessServer> {\n const dir = options.nappletDir ? resolve(options.nappletDir) : null;\n const hostHtml = renderHostPage(options.bootConfig);\n\n const server: Server = createServer((req, res) => {\n void serve(req.url ?? '/', res);\n });\n\n async function serve(rawUrl: string, res: ServerResponse): Promise<void> {\n res.setHeader('Access-Control-Allow-Origin', '*');\n const path = decodeURIComponent(rawUrl.split('?')[0]);\n\n if (path === HOST_HTML_ROUTE) {\n res.writeHead(200, { 'Content-Type': MIME['.html'] });\n res.end(hostHtml);\n return;\n }\n if (path === HOST_BUNDLE_ROUTE) {\n res.writeHead(200, { 'Content-Type': MIME['.js'] });\n res.end(options.hostBundle);\n return;\n }\n\n if (!dir) {\n res.writeHead(404).end('not found');\n return;\n }\n\n // Resolve a safe path under the napplet dir.\n const relative = normalize(path).replace(/^(\\.\\.[/\\\\])+/, '').replace(/^[/\\\\]+/, '');\n let filePath = join(dir, relative);\n if (!filePath.startsWith(dir)) {\n res.writeHead(403).end('forbidden');\n return;\n }\n try {\n let info = await stat(filePath).catch(() => null);\n if (info?.isDirectory()) {\n filePath = join(filePath, 'index.html');\n info = await stat(filePath).catch(() => null);\n }\n if (path === '/' && !info) {\n filePath = join(dir, 'index.html');\n info = await stat(filePath).catch(() => null);\n }\n if (!info?.isFile()) {\n res.writeHead(404).end('not found');\n return;\n }\n const body = await readFile(filePath);\n res.writeHead(200, { 'Content-Type': MIME[extname(filePath).toLowerCase()] ?? 'application/octet-stream' });\n res.end(body);\n } catch {\n res.writeHead(500).end('server error');\n }\n }\n\n await new Promise<void>((resolveListen) => server.listen(0, '127.0.0.1', resolveListen));\n const address = server.address() as AddressInfo;\n const origin = `http://127.0.0.1:${address.port}`;\n\n return {\n origin,\n hostUrl: `${origin}${HOST_HTML_ROUTE}`,\n close: () => new Promise<void>((res, rej) => server.close((err) => (err ? rej(err) : res()))),\n };\n}\n","/**\n * @napplet/conformance-cli -- Shared static-serving helpers used by both the\n * one-shot harness server and the `--ui` watch server.\n *\n * @packageDocumentation\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport { extname } from 'node:path';\nimport type { ServerResponse } from 'node:http';\n\n/** Content types by extension. */\nexport const MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.ico': 'image/x-icon',\n '.wasm': 'application/wasm',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.map': 'application/json; charset=utf-8',\n '.txt': 'text/plain; charset=utf-8',\n};\n\n/** The napplet iframe runs at an opaque origin; allow its module subresources. */\nexport function setCors(res: ServerResponse): void {\n res.setHeader('Access-Control-Allow-Origin', '*');\n}\n\n/** Send a file if it exists; return false (without writing) when it does not. */\nexport async function sendFile(res: ServerResponse, absPath: string): Promise<boolean> {\n const info = await stat(absPath).catch(() => null);\n if (!info?.isFile()) return false;\n res.writeHead(200, { 'Content-Type': MIME[extname(absPath).toLowerCase()] ?? 'application/octet-stream' });\n res.end(await readFile(absPath));\n return true;\n}\n","/**\n * @napplet/conformance-cli -- The `--ui` watch server.\n *\n * Serves the standalone conformance web app (bundled with the CLI), the napplet\n * under test (same origin, with permissive CORS for the sandboxed iframe), and a\n * Server-Sent-Events stream. It watches the napplet's served directory and pushes\n * a `rerun` event on every change, so the open app re-runs conformance live — the\n * `vitest --ui` model: edit, save, see the verdict update.\n *\n * @packageDocumentation\n */\n\nimport { createServer, type Server, type ServerResponse } from 'node:http';\nimport { watch, type FSWatcher } from 'node:fs';\nimport { join, normalize, resolve } from 'node:path';\nimport type { AddressInfo } from 'node:net';\nimport { setCors, sendFile } from './static.js';\n\n/** Path the app under `?live=1` is served the napplet from. */\nconst NAPPLET_PREFIX = '/__napplet__/';\n/** SSE endpoint the live app subscribes to. */\nconst EVENTS_ROUTE = '/__conformance__/events';\n\n/** Options for {@link startUiServer}. */\nexport interface UiServerOptions {\n /** Directory of the built conformance web app (served at `/`). */\n appDir: string;\n /** Directory of the built napplet under test (served at `/__napplet__/`). */\n nappletDir: string;\n /** Preferred port (0 = random). */\n port?: number;\n /** Debounce window (ms) for coalescing file-change bursts. Default 120. */\n debounceMs?: number;\n}\n\n/** A running `--ui` server. */\nexport interface UiServer {\n /** Base origin, e.g. `http://127.0.0.1:5219`. */\n origin: string;\n /** The app URL to open (already pointed at the napplet, live mode on). */\n appUrl: string;\n /** Force a `rerun` broadcast (used for the manual/initial trigger). */\n notify(): void;\n /** Shut the server + watcher down. */\n close(): Promise<void>;\n}\n\nfunction safeJoin(dir: string, urlPath: string): string | null {\n const relative = normalize(urlPath).replace(/^(\\.\\.[/\\\\])+/, '').replace(/^[/\\\\]+/, '');\n const full = join(dir, relative);\n return full.startsWith(dir) ? full : null;\n}\n\n/** Register an SSE subscriber and keep the connection open. */\nfunction handleSse(res: ServerResponse, clients: Set<ServerResponse>): void {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache, no-transform',\n Connection: 'keep-alive',\n });\n res.write('retry: 1000\\n\\n');\n clients.add(res);\n res.on('close', () => clients.delete(res));\n}\n\n/** Serve a file under `/__napplet__/`. */\nasync function handleNapplet(res: ServerResponse, nappletDir: string, path: string): Promise<void> {\n const rel = path.slice(NAPPLET_PREFIX.length) || 'index.html';\n const abs = safeJoin(nappletDir, rel);\n if (abs && (await sendFile(res, abs))) return;\n res.writeHead(404).end('napplet asset not found');\n}\n\n/** Serve the conformance web app, with an SPA fallback to its index. */\nasync function handleApp(res: ServerResponse, appDir: string, path: string): Promise<void> {\n const abs = safeJoin(appDir, path === '/' ? 'index.html' : path);\n if (abs && (await sendFile(res, abs))) return;\n if (await sendFile(res, join(appDir, 'index.html'))) return;\n res.writeHead(404).end('not found');\n}\n\n/** Watch a directory (recursively when supported) and call `fire` debounced. */\nfunction startWatcher(dir: string, debounceMs: number, fire: () => void): { close(): void } {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const onEvent = (): void => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(fire, debounceMs);\n };\n let watcher: FSWatcher;\n try {\n watcher = watch(dir, { recursive: true }, onEvent);\n } catch {\n // Recursive watch may be unsupported on some platforms.\n watcher = watch(dir, onEvent);\n }\n return {\n close() {\n if (timer) clearTimeout(timer);\n watcher.close();\n },\n };\n}\n\n/** Start the `--ui` watch server. */\nexport async function startUiServer(options: UiServerOptions): Promise<UiServer> {\n const appDir = resolve(options.appDir);\n const nappletDir = resolve(options.nappletDir);\n const clients = new Set<ServerResponse>();\n const broadcast = (): void => {\n for (const res of clients) res.write(`event: rerun\\ndata: ${Date.now()}\\n\\n`);\n };\n\n const server: Server = createServer((req, res) => {\n setCors(res);\n res.setHeader('Cache-Control', 'no-store'); // never serve stale napplet code\n const path = decodeURIComponent((req.url ?? '/').split('?')[0]);\n if (path === EVENTS_ROUTE) {\n handleSse(res, clients);\n } else if (path === NAPPLET_PREFIX.slice(0, -1) || path.startsWith(NAPPLET_PREFIX)) {\n void handleNapplet(res, nappletDir, path);\n } else {\n void handleApp(res, appDir, path);\n }\n });\n\n await new Promise<void>((r) => server.listen(options.port ?? 0, '127.0.0.1', r));\n const { port } = server.address() as AddressInfo;\n const origin = `http://127.0.0.1:${port}`;\n const appUrl = `${origin}/?url=${encodeURIComponent(NAPPLET_PREFIX)}&live=1`;\n const watcher = startWatcher(nappletDir, options.debounceMs ?? 120, broadcast);\n\n return {\n origin,\n appUrl,\n notify: broadcast,\n close: () =>\n new Promise<void>((res, rej) => {\n watcher.close();\n for (const c of clients) c.end();\n clients.clear();\n server.close((err) => (err ? rej(err) : res()));\n }),\n };\n}\n","/**\n * @napplet/conformance-cli -- Static scan for forbidden napplet authority.\n *\n * A napplet runs in a `sandbox=\"allow-scripts\"` iframe at an opaque origin, so the\n * host cannot observe every forbidden browser surface at runtime. Instead we scan\n * served source for references to authority the shell owns: signer globals,\n * browser storage, direct network primitives, and external network-loaded assets.\n * This is a heuristic, but a true positive is meaningful: NIP-5D and the NAPs\n * route these capabilities through `window.napplet` domains.\n *\n * @packageDocumentation\n */\n\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { join, extname } from 'node:path';\n\n/** Patterns considered forbidden, keyed by the label reported in the verdict. */\nconst FORBIDDEN: Array<{ label: string; re: RegExp }> = [\n { label: 'window.nostr', re: /\\bwindow\\s*\\.\\s*nostr\\b/ },\n { label: 'globalThis.nostr', re: /\\bglobalThis\\s*\\.\\s*nostr\\b/ },\n { label: 'fetch', re: /\\b(?:window\\s*\\.\\s*|globalThis\\s*\\.\\s*)?fetch\\s*\\(/ },\n { label: 'XMLHttpRequest', re: /\\b(?:new\\s+)?XMLHttpRequest\\s*\\(/ },\n { label: 'WebSocket', re: /\\b(?:new\\s+)?WebSocket\\s*\\(/ },\n { label: 'localStorage', re: /\\b(?:window\\s*\\.\\s*)?localStorage\\b/ },\n { label: 'sessionStorage', re: /\\b(?:window\\s*\\.\\s*)?sessionStorage\\b/ },\n { label: 'indexedDB', re: /\\b(?:window\\s*\\.\\s*)?indexedDB\\b/ },\n { label: 'document.cookie', re: /\\bdocument\\s*\\.\\s*cookie\\b/ },\n { label: 'external img src', re: /<img\\b[^>]*\\bsrc\\s*=\\s*[\"']?https?:/i },\n { label: 'external script src', re: /<script\\b[^>]*\\bsrc\\s*=\\s*[\"']?https?:/i },\n { label: 'external stylesheet href', re: /<link\\b[^>]*\\brel\\s*=\\s*[\"']?stylesheet[\"']?[^>]*\\bhref\\s*=\\s*[\"']?https?:/i },\n { label: 'external stylesheet href', re: /<link\\b[^>]*\\bhref\\s*=\\s*[\"']?https?:[^>]*\\brel\\s*=\\s*[\"']?stylesheet[\"']?/i },\n];\n\nconst SCANNABLE = new Set(['.js', '.mjs', '.cjs', '.html', '.htm']);\nconst SKIP_DIRS = new Set(['node_modules', '.git', '.turbo', 'coverage']);\n\nasync function collectFiles(dir: string, acc: string[]): Promise<void> {\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n await collectFiles(join(dir, entry.name), acc);\n } else if (entry.isFile() && SCANNABLE.has(extname(entry.name).toLowerCase())) {\n acc.push(join(dir, entry.name));\n }\n }\n}\n\n/**\n * Scan a napplet directory for forbidden browser authority references.\n *\n * @param dir - The directory of served napplet files.\n * @returns The distinct forbidden labels found (e.g. `['window.nostr']`).\n */\nexport async function scanForbiddenGlobals(dir: string): Promise<string[]> {\n const info = await stat(dir).catch(() => null);\n if (!info?.isDirectory()) return [];\n const files: string[] = [];\n await collectFiles(dir, files);\n const found = new Set<string>();\n for (const file of files) {\n const text = await readFile(file, 'utf8').catch(() => '');\n for (const { label, re } of FORBIDDEN) {\n if (re.test(text)) found.add(label);\n }\n }\n return [...found];\n}\n","/**\n * @napplet/conformance-cli -- Resolve which local artifact to conformance-test.\n *\n * Kept in its own module (not `cli.ts`, whose top level runs `main()`) so tests\n * can import the resolver without launching the CLI.\n */\n\nimport { stat } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nasync function isFile(path: string): Promise<boolean> {\n return (await stat(path).catch(() => null))?.isFile() ?? false;\n}\n\n/**\n * Resolve the directory + index.html for a local napplet target.\n *\n * A built napplet is the artifact we test, so `dist/index.html` is preferred over\n * a sibling `index.html`. When `target` is a project root, its source\n * `index.html` (a Vite entry referencing `/src/*.ts` that cannot run in the\n * opaque-origin `srcdoc` sandbox) would otherwise shadow the built single-file\n * output and make conformance test the wrong, never-booting artifact.\n */\nexport async function resolveNappletDir(target: string): Promise<{ dir: string; indexHtml: string }> {\n const base = resolve(target);\n const dist = join(base, 'dist');\n if (await isFile(join(dist, 'index.html'))) return { dir: dist, indexHtml: join(dist, 'index.html') };\n if (await isFile(join(base, 'index.html'))) return { dir: base, indexHtml: join(base, 'index.html') };\n throw new Error(`No index.html found in ${join(dist, 'index.html')} or ${join(base, 'index.html')}. Build the napplet first.`);\n}\n","/** Capture a local build or an existing shell preview through its iframe. */\nimport { access, writeFile } from 'node:fs/promises';\nimport { execFile } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { chromium } from 'playwright';\nimport { startScreenshotPreview } from './screenshot-preview.js';\nimport { resolveNappletDir } from './resolve-napplet.js';\n\n/** Options for capturing a shell-hosted napplet. */\nexport interface ScreenshotOptions {\n url?: string;\n directory?: string;\n artifact?: string;\n output: string;\n selector: string;\n readySelector?: string;\n width: number;\n height: number;\n delay: number;\n}\n\n/**\n * Parse screenshot arguments without invoking a browser.\n * @param args CLI arguments after the screenshot command.\n * @returns Validated capture options.\n * @example parseScreenshotArgs(['http://localhost:5173', '--output', 'preview.png'])\n */\nexport function parseScreenshotArgs(args: readonly string[]): ScreenshotOptions {\n const hasTarget = args[0] && !args[0].startsWith('--');\n const target = hasTarget ? args[0] : '.';\n const rest = hasTarget ? args.slice(1) : args;\n const remote = /^https?:\\/\\//.test(target);\n if (remote) new URL(target);\n else if (/^[a-z][a-z0-9+.-]*:/i.test(target) && !/^[a-z]:[\\\\/]/i.test(target)) throw new Error('Preview URLs must use HTTP(S)');\n const options: ScreenshotOptions = { ...(remote ? { url: target } : { directory: target }), output: 'preview.png', selector: 'iframe', width: 1200, height: 750, delay: 1500 };\n for (let index = 0; index < rest.length; index += 2) {\n const flag = rest[index];\n const value = rest[index + 1];\n if (!value || value.startsWith('--')) throw new Error(`Missing value for ${flag}`);\n switch (flag) {\n case '--output': options.output = value; break;\n case '--artifact': options.artifact = value; break;\n case '--selector': options.selector = value; break;\n case '--ready-selector': options.readySelector = value; break;\n case '--width': options.width = integer(value, flag, 1, 4096); break;\n case '--height': options.height = integer(value, flag, 1, 4096); break;\n case '--delay': options.delay = integer(value, flag, 0, 30000); break;\n default: throw new Error(`Unknown screenshot option: ${flag}`);\n }\n }\n if (!options.output.toLowerCase().endsWith('.png')) throw new Error('--output must end in .png');\n if (options.artifact && hasTarget) throw new Error('Use a target or --artifact, not both');\n return options;\n}\n\n/**\n * Save a PNG of a napplet iframe, starting a local preview when needed.\n * @param options Local build or URL, output, dimensions and readiness settings.\n * @returns Resolves after capture and cleanup of Chromium and any local preview.\n * @example await captureScreenshot(parseScreenshotArgs(['http://localhost:5173']))\n */\nexport async function captureScreenshot(options: ScreenshotOptions): Promise<void> {\n await ensureChromium();\n const preview = options.url ? undefined : await startScreenshotPreview(\n options.artifact ?? (await resolveNappletDir(options.directory ?? '.')).indexHtml,\n );\n let browser;\n try {\n browser = await chromium.launch({ channel: 'chromium' });\n const page = await browser.newPage({ viewport: { width: options.width, height: options.height } });\n page.setDefaultTimeout(30000);\n await page.goto(options.url ?? preview!.url, { waitUntil: 'domcontentloaded' });\n if (preview) {\n await page.waitForFunction(() => {\n const iframe = document.querySelector<HTMLIFrameElement>('#napplet-frame');\n return Boolean(iframe?.srcdoc);\n });\n }\n const frame = page.locator(options.selector);\n await frame.waitFor({ state: 'visible' });\n await frame.evaluate((element, size) => {\n if (!(element instanceof HTMLIFrameElement)) throw new Error('Screenshot selector must select one iframe');\n Object.assign(element.style, { width: `${size.width}px`, height: `${size.height}px`, maxWidth: 'none', maxHeight: 'none', boxSizing: 'border-box' });\n }, { width: options.width, height: options.height });\n const content = frame.contentFrame();\n await content.locator('body').waitFor({ state: 'visible' });\n if (options.readySelector) await content.locator(options.readySelector).waitFor({ state: 'visible' });\n await content.locator('body').evaluate(async () => { await document.fonts.ready; });\n await page.waitForTimeout(options.delay);\n const png = await frame.screenshot({ type: 'png', timeout: 30000 });\n await writeFile(options.output, png, { flag: 'wx' });\n } finally {\n try { await browser?.close(); } finally { await preview?.close(); }\n }\n}\n\nasync function ensureChromium(): Promise<void> {\n try { await access(chromium.executablePath()); return; } catch { /* Install the matching browser on first use. */ }\n console.error('Installing Chromium for screenshot capture...');\n const require = createRequire(import.meta.url);\n const cli = join(dirname(require.resolve('playwright/package.json')), 'cli.js');\n await promisify(execFile)(process.execPath, [cli, 'install', 'chromium'], { timeout: 180000 });\n}\n\nfunction integer(value: string, flag: string, min: number, max: number): number {\n const number = Number(value);\n if (!Number.isInteger(number) || number < min || number > max) throw new Error(`${flag} must be an integer from ${min} to ${max}`);\n return number;\n}\n","/** Disposable Kehto Paja preview for a local build. */\nimport { createServer } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { readFile, realpath } from 'node:fs/promises';\nimport { dirname, resolve, sep } from 'node:path';\nimport { startPajaServer } from '@kehto/paja';\nimport { sendFile, setCors } from './static.js';\n\n/**\n * Host a built artifact in Paja on ephemeral loopback ports.\n * @param artifact Exact index.html selected by deployment.\n * @returns Shell URL and an async cleanup handle for both servers.\n * @example const preview = await startScreenshotPreview('/project/dist/index.html');\n */\nexport async function startScreenshotPreview(artifact: string): Promise<{ url: string; close(): Promise<void> }> {\n const index = await realpath(artifact);\n const root = dirname(index);\n const html = await readFile(index);\n const server = createServer((request, response) => {\n setCors(response);\n void (async () => {\n const pathname = decodeURIComponent(new URL(request.url ?? '/', 'http://localhost').pathname);\n if (pathname === '/' || pathname === '/index.html') {\n response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });\n response.end(html);\n return;\n }\n const file = await realpath(resolve(root, `.${pathname}`));\n if (!file.startsWith(`${root}${sep}`)) { response.writeHead(403).end(); return; }\n if (!await sendFile(response, file)) response.writeHead(404).end();\n })().catch(() => { if (!response.headersSent) response.writeHead(404); response.end(); });\n });\n await new Promise<void>((done, reject) => {\n server.once('error', reject);\n server.listen(0, '127.0.0.1', () => { server.off('error', reject); done(); });\n });\n const closeTarget = () => new Promise<void>((done, reject) => {\n server.closeAllConnections();\n server.close((error) => error ? reject(error) : done());\n });\n try {\n const targetUrl = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;\n const paja = await startPajaServer({ options: {\n host: '127.0.0.1', port: 0, targetUrl,\n } });\n return { url: paja.url, close: async () => { try { await paja.close(); } finally { await closeTarget(); } } };\n } catch (error) {\n await closeTarget();\n throw error;\n }\n}\n","import { createHash } from 'node:crypto';\nimport { validateEvent, verifyEvent, type Event } from 'nostr-tools/pure';\nimport { validateManifestEvent } from '@napplet/conformance';\n\n/** Verify the signed event and exact raw artifact before the browser executes it (NIP-5D §Security Considerations). */\nexport function verifyManifestArtifact(input: unknown, artifact: Uint8Array): Event {\n if (!validateEvent(input)) throw new Error('Manifest is not a Nostr event');\n // Verify a fresh object: nostr-tools caches verification on a symbol which a\n // caller could otherwise copy from a previously signed event and then mutate.\n const { id, pubkey, created_at, kind, tags, content, sig } = input as Event;\n const event: Event = { id, pubkey, created_at, kind, tags, content, sig };\n let validSignature = false;\n try {\n validSignature = verifyEvent(event);\n } catch {\n // Malformed signature fields are rejected along with cryptographic failures.\n }\n if (!validSignature) throw new Error('Manifest signature or event id is invalid');\n const verdict = validateManifestEvent(event);\n if (!verdict.ok) throw new Error(`Invalid NIP-5D manifest: ${verdict.errors.map((error) => error.message).join('; ')}`);\n const expected = event.tags.find((tag) => tag[0] === 'x')?.[1];\n const actual = createHash('sha256').update(artifact).digest('hex');\n if (actual !== expected) throw new Error(`Artifact SHA-256 mismatch: expected ${expected}, got ${actual}`);\n return event;\n}\n"],"mappings":"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","names":["readFile","writeFile","stat","join","dirname","resolve","chromium","readFile","stat","extname","stat","readFile","extname","createServer","join","normalize","resolve","normalize","join","resolve","createServer","readFile","stat","join","extname","stat","join","resolve","dirname","join","createServer","readFile","resolve","readFile","createServer","resolve","require","join","dirname","chromium","stat","dirname","join","resolve","readFile","run","writeFile"]}
|