Skip to content

Commit b4eb7f4

Browse files
committed
fix: preserve node:sqlite import in bundle; add dist boot smoke test
tsup's removeNodeProtocol stripped the node: prefix from the bundled node:sqlite import, crashing the server with ERR_MODULE_NOT_FOUND when DB_PATH was set (node:sqlite is a prefix-only builtin). Disable it via tsup.config.ts and add a build-time smoke that boots the bundled CLI with persistence so the regression fails the build. Release v0.2.1.
1 parent 8b90aec commit b4eb7f4

6 files changed

Lines changed: 74 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## 0.2.1 - 2026-06-04
9+
10+
### Fixed
11+
- Server crashed at startup with `ERR_MODULE_NOT_FOUND: Cannot find package 'sqlite'` whenever `DB_PATH` was set: tsup's `removeNodeProtocol` stripped the `node:` prefix from the bundled `node:sqlite` import, and `node:sqlite` is a prefix-only builtin (there is no bare `sqlite`). Disabled prefix stripping (`tsup.config.ts`) and added a build-time smoke test that boots the bundled CLI with persistence enabled, so a mangled builtin import fails the build/release instead of reaching production.
12+
813
## 0.2.0 - 2026-06-04
914

1015
### Added

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ RUN pnpm install --frozen-lockfile --prod=false
1111

1212
# Build
1313
FROM deps AS build
14-
COPY tsconfig.json tsconfig.ui.json vite.config.ts ./
14+
COPY tsconfig.json tsconfig.ui.json vite.config.ts tsup.config.ts ./
1515
COPY src/ ./src/
16+
COPY scripts/ ./scripts/
1617
RUN pnpm build
1718

1819
# Production

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@arkade-os/lnurl",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "LNURL service for amountless Lightning receives via Arkade swaps",
55
"type": "module",
66
"main": "dist/index.js",
@@ -12,7 +12,7 @@
1212
}
1313
},
1414
"scripts": {
15-
"build": "tsup src/index.ts src/cli.ts src/admin-server.ts --format esm --dts && vite build",
15+
"build": "tsup && vite build && node scripts/smoke-dist.mjs",
1616
"dev": "cross-env NODE_OPTIONS=--experimental-sqlite tsx watch src/cli.ts",
1717
"start": "node --experimental-sqlite dist/cli.js",
1818
"test": "cross-env NODE_OPTIONS=--experimental-sqlite vitest run",

scripts/smoke-dist.mjs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Build-time smoke test: boot the BUNDLED dist/cli.js with persistence enabled and
2+
// confirm the node:sqlite-backed DB layer loads. Tests run against TS source, so a
3+
// bundler mangling the `node:sqlite` import (e.g. stripping the `node:` prefix) only
4+
// surfaces at runtime in the built artifact — exactly what broke v0.2.0. Chaining this
5+
// into `pnpm build` makes such a regression fail the build (and the Docker release).
6+
import { spawn } from "node:child_process";
7+
import { mkdtempSync, rmSync } from "node:fs";
8+
import { tmpdir } from "node:os";
9+
import { join } from "node:path";
10+
import { randomBytes } from "node:crypto";
11+
12+
const dir = mkdtempSync(join(tmpdir(), "lnurl-smoke-"));
13+
const SUCCESS = "persistence: enabled"; // cli logs this only after the DB layer loads + migrates
14+
const TIMEOUT_MS = 20_000;
15+
16+
const child = spawn(process.execPath, ["--experimental-sqlite", "dist/cli.js"], {
17+
stdio: ["ignore", "pipe", "pipe"],
18+
env: {
19+
...process.env,
20+
DB_PATH: join(dir, "smoke.db"),
21+
TOKEN_ENCRYPTION_KEY: randomBytes(32).toString("hex"),
22+
BOOTSTRAP_DOMAIN: "smoke.test",
23+
// High, unlikely-to-conflict ports (PORT=0 would fall back to 3000 via `|| default`).
24+
PORT: "39080",
25+
ADMIN_PORT: "39081",
26+
ADMIN_BIND: "127.0.0.1",
27+
},
28+
});
29+
30+
let out = "";
31+
let settled = false;
32+
const finish = (ok, msg) => {
33+
if (settled) return;
34+
settled = true;
35+
clearTimeout(timer);
36+
try { child.kill("SIGKILL"); } catch { /* already gone */ }
37+
// Best-effort cleanup: on Windows the just-killed child may still hold the SQLite
38+
// file handle briefly, so retry and never let a cleanup failure change the result.
39+
try { rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }); } catch { /* temp dir; OS will reap */ }
40+
if (ok) {
41+
console.log("✓ dist smoke: bundled CLI initialized node:sqlite persistence");
42+
process.exit(0);
43+
}
44+
console.error(`✗ dist smoke FAILED: ${msg}\n--- cli output ---\n${out}`);
45+
process.exit(1);
46+
};
47+
48+
const timer = setTimeout(() => finish(false, "timed out waiting for startup"), TIMEOUT_MS);
49+
const onData = (d) => { out += d.toString(); if (out.includes(SUCCESS)) finish(true); };
50+
child.stdout.on("data", onData);
51+
child.stderr.on("data", onData); // capture errors (e.g. ERR_MODULE_NOT_FOUND)
52+
child.on("exit", (code) => finish(false, `CLI exited before startup (code ${code})`));
53+
child.on("error", (err) => finish(false, `failed to spawn CLI: ${err.message}`));

src/openapi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const openApiSpec = {
77
"Wallets open an SSE session to get an LNURL, and payers use " +
88
"standard LNURL-pay (LUD-06) to request invoices. The wallet " +
99
"creates reverse swaps on-the-fly and returns bolt11 invoices.",
10-
version: "0.2.0",
10+
version: "0.2.1",
1111
license: { name: "MIT" },
1212
},
1313
servers: [{ url: "/" }],

tsup.config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { defineConfig } from "tsup";
2+
3+
export default defineConfig({
4+
entry: ["src/index.ts", "src/cli.ts", "src/admin-server.ts"],
5+
format: ["esm"],
6+
dts: true,
7+
// tsup strips the `node:` protocol prefix by default (removeNodeProtocol: true),
8+
// rewriting e.g. `node:sqlite` → `sqlite`. That is fatal for prefix-only builtins
9+
// like node:sqlite (there is no bare `sqlite` builtin), so disable it.
10+
removeNodeProtocol: false,
11+
});

0 commit comments

Comments
 (0)