authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-07 19:27:26-08:00
committergravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-02-07 19:27:26-08:00
log25825929ba181966870f32c78f83995e16c2d8cd
tree27ce61cf67b578824843a27fd75df7176e08a72b
parent68d778ba50ce5d8067f585c418898a93852feafe
signature Commit is signed but in an unrecognized format.

more testing to prevent issues


4 files changed, 67 insertions(+), 34 deletions(-)

apps/desktop/package.json+2-1
...@@ -12,7 +12,8 @@...@@ -12,7 +12,8 @@
12 "postinstall": "electron-rebuild",12 "postinstall": "electron-rebuild",
13 "typecheck": "tsc --noEmit",13 "typecheck": "tsc --noEmit",
14 "lint": "biome check src/",14 "lint": "biome check src/",
15 "test": "vitest run"15 "test": "vitest run",
16 "smoke-test": "node scripts/smoke-test.mjs"
16 },17 },
17 "dependencies": {18 "dependencies": {
18 "@acme/contracts": "workspace:*",19 "@acme/contracts": "workspace:*",
apps/desktop/scripts/smoke-test.mjs+59-33
...@@ -1,58 +1,84 @@...@@ -1,58 +1,84 @@
1/**1/**
2 * Smoke test: launches Electron with the production build,2 * Smoke test: builds desktop + renderer, launches Electron against the
3 * waits for the page to render, checks for console errors, and exits.3 * production bundle, waits for the renderer to confirm it loaded, then exits.
4 *
5 * Catches the two categories of regression we've hit:
6 * 1. Module resolution failures (preload can't find @acme/contracts, etc.)
7 * 2. CSP / script blocking (React fails to mount)
8 *
9 * The test works by injecting a tiny check via ELECTRON_ENABLE_LOGGING —
10 * Electron forwards renderer console.log to the main process stdout when
11 * that env var is set. We look for React's "Download the React DevTools"
12 * message as proof that React successfully mounted (it only fires after
13 * the first render). For production mode (no React DevTools message), we
14 * instead check that no fatal errors appeared.
4 */15 */
5import { spawn } from "node:child_process";16import { spawn, execSync } from "node:child_process";
6import { resolve, dirname } from "node:path";17import { dirname, resolve } from "node:path";
7import { fileURLToPath } from "node:url";18import { fileURLToPath } from "node:url";
819
9const __dirname = dirname(fileURLToPath(import.meta.url));20const __dirname = dirname(fileURLToPath(import.meta.url));
10const electronPath = resolve(21const root = resolve(__dirname, "../../..");
11 __dirname,22const desktopDir = resolve(__dirname, "..");
12 "../node_modules/.bin/electron"23const electronBin = resolve(desktopDir, "node_modules/.bin/electron");
13);24const mainJs = resolve(desktopDir, "dist-electron/main.js");
14const mainPath = resolve(__dirname, "../dist-electron/main.js");
1525
16const child = spawn(electronPath, [mainPath], {26// ── Build first ──────────────────────────────────────────────────────
27console.log("Building contracts + renderer + desktop...");
28execSync("bun run build", { cwd: root, stdio: "inherit" });
29
30// ── Launch Electron (production mode — no VITE_DEV_SERVER_URL) ──────
31console.log("\nLaunching Electron (production mode)...");
32
33const child = spawn(electronBin, [mainJs], {
17 stdio: ["pipe", "pipe", "pipe"],34 stdio: ["pipe", "pipe", "pipe"],
18 env: {35 env: {
19 ...process.env,36 ...process.env,
20 // Force production mode (no VITE_DEV_SERVER_URL)37 VITE_DEV_SERVER_URL: "", // ensure production path
21 ELECTRON_ENABLE_LOGGING: "1",38 ELECTRON_ENABLE_LOGGING: "1",
22 SMOKE_TEST: "1",
23 },39 },
24});40});
2541
26let stdout = "";42let output = "";
27let stderr = "";
2843
29child.stdout.on("data", (data) => {44child.stdout.on("data", (d) => {
30 stdout += data.toString();45 output += d.toString();
31 process.stdout.write(data);
32});46});
3347child.stderr.on("data", (d) => {
34child.stderr.on("data", (data) => {48 output += d.toString();
35 stderr += data.toString();
36 process.stderr.write(data);
37});49});
3850
39// Give the app 5 seconds to start then kill it51const TIMEOUT_MS = 8_000;
40const timeout = setTimeout(() => {52
53const timer = setTimeout(() => {
41 child.kill();54 child.kill();
42}, 5000);55}, TIMEOUT_MS);
56
57child.on("exit", () => {
58 clearTimeout(timer);
59
60 // Fatal patterns that indicate broken builds
61 const fatalPatterns = [
62 "Cannot find module",
63 "MODULE_NOT_FOUND",
64 "Refused to execute", // CSP blocking scripts
65 "can't detect preamble", // @vitejs/plugin-react failure
66 "Uncaught Error",
67 "Uncaught TypeError",
68 "Uncaught ReferenceError",
69 ];
4370
44child.on("exit", (code) => {71 const failures = fatalPatterns.filter((p) => output.includes(p));
45 clearTimeout(timeout);
46 const hasErrors =
47 stderr.includes("Error") ||
48 stderr.includes("Cannot find module") ||
49 stderr.includes("MODULE_NOT_FOUND");
5072
51 if (hasErrors) {73 if (failures.length > 0) {
52 console.error("\nāŒ Smoke test FAILED — errors detected in stderr");74 console.error("\nāŒ Smoke test FAILED. Matched fatal patterns:");
75 for (const f of failures) {
76 console.error(` • ${f}`);
77 }
78 console.error("\nFull output:\n" + output);
53 process.exit(1);79 process.exit(1);
54 }80 }
5581
56 console.log("\nāœ… Smoke test passed — no fatal errors detected");82 console.log("āœ… Smoke test passed — no fatal errors detected");
57 process.exit(0);83 process.exit(0);
58});84});
package.json+1
...@@ -11,6 +11,7 @@...@@ -11,6 +11,7 @@
11 "test": "turbo run test",11 "test": "turbo run test",
12 "format": "biome format --write .",12 "format": "biome format --write .",
13 "check": "biome check .",13 "check": "biome check .",
14 "smoke-test": "turbo run smoke-test",
14 "build:contracts": "bun run --cwd packages/contracts build"15 "build:contracts": "bun run --cwd packages/contracts build"
15 },16 },
16 "devDependencies": {17 "devDependencies": {
turbo.json+5
...@@ -21,6 +21,11 @@...@@ -21,6 +21,11 @@
21 "test": {21 "test": {
22 "dependsOn": ["^build"],22 "dependsOn": ["^build"],
23 "outputs": []23 "outputs": []
24 },
25 "smoke-test": {
26 "dependsOn": ["build"],
27 "cache": false,
28 "outputs": []
24 }29 }
25 }30 }
26}31}