diff --git a/web/index.html b/web/index.html index 6ccda51..e25a163 100644 --- a/web/index.html +++ b/web/index.html @@ -19,6 +19,7 @@ "@noble/ciphers/chacha": "/vendor/noble-ciphers-chacha.js", "@noble/hashes/sha2": "/vendor/noble-hashes-sha2.js", "@noble/hashes/hmac": "/vendor/noble-hashes-hmac.js", + "@noble/hashes/pbkdf2": "/vendor/noble-hashes-pbkdf2.js", "@noble/hashes/hkdf": "/vendor/noble-hashes-hkdf.js", "@noble/hashes/utils": "/vendor/noble-hashes-utils.js", "@xterm/xterm": "/vendor/xterm.js", diff --git a/web/sw.js b/web/sw.js index 1ce79ef..3a4219e 100644 --- a/web/sw.js +++ b/web/sw.js @@ -53,6 +53,7 @@ const SHELL = [ '/vendor/noble-curves-ed25519.js', '/vendor/noble-hashes-hkdf.js', '/vendor/noble-hashes-hmac.js', + '/vendor/noble-hashes-pbkdf2.js', '/vendor/noble-hashes-sha2.js', '/vendor/noble-hashes-utils.js', '/vendor/xterm-addon-fit.js', diff --git a/web/test/importmap.test.js b/web/test/importmap.test.js new file mode 100644 index 0000000..63034c6 --- /dev/null +++ b/web/test/importmap.test.js @@ -0,0 +1,66 @@ +// web/test/importmap.test.js — guards the gap that node tests cannot see: the +// browser resolves bare module specifiers (`@noble/...`) through the importmap in +// index.html, NOT node_modules. A specifier imported by app code but missing from +// the importmap loads fine under `node --test` yet fails at boot in the browser +// ("Failed to resolve module specifier") — a black screen. This test asserts every +// bare specifier used under web/src/ is mapped. (Regression guard for the missing +// @noble/hashes/pbkdf2 mapping that broke the SPA after wallet derivation moved +// into the sign-in path.) +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync, readdirSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const here = dirname(fileURLToPath(import.meta.url)); +const webRoot = join(here, '..'); + +function walk(dir) { + const out = []; + for (const e of readdirSync(dir, { withFileTypes: true })) { + const p = join(dir, e.name); + if (e.isDirectory()) out.push(...walk(p)); + else if (e.name.endsWith('.js')) out.push(p); + } + return out; +} + +// A bare specifier does not start with '.' or '/'. Those are the ones the browser +// resolves via the importmap; relative paths resolve as URLs directly. +function bareSpecifiers(src) { + const out = new Set(); + const re = /(?:^|\W)(?:import|export)[^'"]*?from\s*['"]([^'"]+)['"]/g; + let m; + while ((m = re.exec(src)) !== null) { + const spec = m[1]; + if (!spec.startsWith('.') && !spec.startsWith('/')) out.add(spec); + } + return out; +} + +test('every bare import under web/src is in the index.html importmap', () => { + const html = readFileSync(join(webRoot, 'index.html'), 'utf8'); + const mapJSON = html.match(/