Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions web/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
66 changes: 66 additions & 0 deletions web/test/importmap.test.js
Original file line number Diff line number Diff line change
@@ -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(/<script type="importmap"[^>]*>\s*([\s\S]*?)<\/script>/);
assert.ok(mapJSON, 'index.html must contain an importmap');
const imports = JSON.parse(mapJSON[1]).imports;
const mapped = new Set(Object.keys(imports));

const missing = [];
for (const file of walk(join(webRoot, 'src'))) {
for (const spec of bareSpecifiers(readFileSync(file, 'utf8'))) {
if (!mapped.has(spec)) missing.push(`${spec} (in ${file.replace(webRoot + '/', '')})`);
}
}
assert.deepEqual(missing, [], `bare specifiers missing from the importmap:\n ${missing.join('\n ')}`);
});

test('every importmap target resolves to a vendored file on disk', () => {
const html = readFileSync(join(webRoot, 'index.html'), 'utf8');
const imports = JSON.parse(html.match(/<script type="importmap"[^>]*>\s*([\s\S]*?)<\/script>/)[1]).imports;
for (const [spec, target] of Object.entries(imports)) {
assert.doesNotThrow(
() => readFileSync(join(webRoot, target.replace(/^\//, ''))),
`importmap target ${target} for ${spec} does not exist on disk`,
);
}
});
6 changes: 6 additions & 0 deletions web/vendor/noble-hashes-pbkdf2.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading