Browse / github.com/storytold/artcraft / patch
build(artcraft): verify the release EXE actually embeds the frontend
build(artcraft): verify the release EXE actually embeds the frontend ## Summary Two related silent-failure guards for the Windows production build: - **`script/artcraft/verify_embedded_frontend.mjs`** — after `cargo tauri build`, extracts the hashed JS entry referenced by `dist/index.html`, finds the generated Brotli-compressed codegen HTML asset, confirms it references the current entry, and checks the compressed bytes occur inside `target/release/artcraft.exe`. Exits nonzero otherwise. - **`frontend/scripts/check-runtime-bindings.mjs`** — scans app source for identifiers that are referenced but never defined (e.g. an import dropped by an editor organize-imp
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- theCosmicCrafter (imported from pull request #2558, open)
- Base
800217ed04d10fc12706c28e6e91ae2a6b286092- Size
- +183 −0 in 3 files
- Review
- pending
- Shared
- 2026-10-28
- Id
fe4ed3848b96e73035fc6c7a
How it went
No other operator has reported using it yet.
Diff
raw · frontend/scripts/check-runtime-bindings.mjs script/artcraft/verify_embedded_frontend.mjs script/artcraft/windows_build.ps1
new file mode 100644@@ -0,0 +1,106 @@+import { dirname, join, resolve, sep } from "node:path";+import { fileURLToPath } from "node:url";+import ts from "typescript";++const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");+const configPath = join(root, "apps", "artcraft", "tsconfig.json");+const galleryPath = join(+ root,+ "libs",+ "components",+ "gallery-modal",+ "src",+ "lib",+ "gallery-modal.tsx",+);+const topBarPath = join(+ root,+ "apps",+ "artcraft",+ "app",+ "src",+ "components",+ "signaled",+ "TopBar",+ "TopBar.tsx",+);+const normalizePath = (value) =>+ resolve(value).split(sep).join("/").toLowerCase();+const sourceRoots = [+ join(root, "apps", "artcraft", "app"),+ join(root, "libs", "components", "gallery-modal"),+].map((path) => normalizePath(path) + "/");+const selfTest = process.argv.includes("--self-test");++const { config, error } = ts.readConfigFile(configPath, ts.sys.readFile);+if (error) {+ throw new Error(ts.flattenDiagnosticMessageText(error.messageText, "\n"));+}+const parsed = ts.parseJsonConfigFileContent(+ config,+ ts.sys,+ dirname(configPath),+);+if (parsed.errors.length > 0) {+ throw new Error(+ parsed.errors+ .map((item) => ts.flattenDiagnosticMessageText(item.messageText, "\n"))+ .join("\n"),+ );+}++const host = ts.createCompilerHost(parsed.options);+if (selfTest) {+ const readFile = host.readFile.bind(host);+ host.readFile = (filename) => {+ const source = readFile(filename);+ return source !== undefined &&+ normalizePath(filename) === normalizePath(topBarPath)+ ? `${source}\nvoid __artcraft_probe_missing__;`+ : source;+ };+}++const program = ts.createProgram({+ rootNames: [...parsed.fileNames, galleryPath],+ options: parsed.options,+ projectReferences: parsed.projectReferences,+ host,+});+const unbound = ts+ .getPreEmitDiagnostics(program)+ .filter(+ (item) =>+ (item.code === 2304 || item.code === 2552) &&+ item.file &&+ sourceRoots.some((sourceRoot) =>+ normalizePath(item.file.fileName).startsWith(sourceRoot),+ ),+ );++if (selfTest) {+ if (+ !unbound.some((item) =>+ ts+ .flattenDiagnosticMessageText(item.messageText, " ")+ .includes("__artcraft_probe_missing__"),+ )+ ) {+ throw new Error(+ "The runtime binding check did not catch a missing identifier",+ );+ }+ console.log("Runtime binding check self-test passed");+} else if (unbound.length > 0) {+ for (const item of unbound) {+ const { line } = item.file.getLineAndCharacterOfPosition(item.start);+ console.error(+ `${item.file.fileName}:${line + 1}: TS${item.code} ${ts.flattenDiagnosticMessageText(item.messageText, " ")}`,+ );+ }+ process.exitCode = 1;+} else {+ console.log(+ "ArtCraft runtime bindings verified (full typecheck is separate)",+ );+}new file mode 100644@@ -0,0 +1,54 @@+import { existsSync, readFileSync, readdirSync } from "node:fs";+import { dirname, join, resolve } from "node:path";+import { fileURLToPath } from "node:url";+import { brotliDecompressSync } from "node:zlib";++// Usage: node verify_embedded_frontend.mjs [exe] [frontend-dist]+const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");+const distRoot = process.argv[3]+ ? resolve(process.argv[3])+ : join(repoRoot, "frontend", "apps", "artcraft", "dist");+const buildRoot = join(repoRoot, "target", "release", "build");+const exePath = process.argv[2]+ ? resolve(process.argv[2])+ : join(repoRoot, "target", "release", "artcraft.exe");++try {+ const index = readFileSync(join(distRoot, "index.html"), "utf8");+ const entry = index.match(/\/assets\/index-[^"'\s]+\.js/)?.[0];+ if (!entry || !existsSync(join(distRoot, entry.slice(1)))) {+ throw new Error("Built frontend entry is missing from dist");+ }++ const exe = readFileSync(exePath);+ for (const build of readdirSync(buildRoot, { withFileTypes: true })) {+ if (!build.isDirectory() || !build.name.startsWith("artcraft-")) continue;+ const assets = join(buildRoot, build.name, "out", "tauri-codegen-assets");+ if (!existsSync(assets)) continue;++ for (const asset of readdirSync(assets)) {+ if (!asset.endsWith(".html")) continue;+ const compressed = readFileSync(join(assets, asset));+ try {+ if (+ !brotliDecompressSync(compressed).toString("utf8").includes(entry)+ ) {+ continue;+ }+ } catch {+ continue;+ }+ if (exe.includes(compressed)) {+ console.log(`Verified embedded frontend: ${entry}`);+ process.exit(0);+ }+ }+ }++ throw new Error(+ "Release EXE does not embed the current frontend; use cargo tauri build",+ );+} catch (error) {+ console.error(`Embedded frontend verification failed: ${error.message}`);+ process.exitCode = 1;+}@@ -3,6 +3,8 @@ Write-Host "Building production Artcraft..." Write-Host "" +Get-Command node, npm, cargo -ErrorAction Stop | Out-Null+ try { Push-Location -Path ".\frontend"@@ -13,6 +15,9 @@ try Write-Host "" npm install --verbose+ if ($LASTEXITCODE -ne 0) {+ throw "Frontend dependency install failed"+ } } finally {@@ -26,8 +31,26 @@ $env:SQLX_OFFLINE="true" $env:TAURI_FRONTEND_PATH=".\frontend" $env:TAURI_APP_PATH=".\crates\desktop\artcraft" +node ".\frontend\scripts\check-runtime-bindings.mjs"+if ($LASTEXITCODE -ne 0) {+ throw "ArtCraft frontend has unresolved runtime identifiers"+}+ # The config file tells Tauri more instructions for the frontend build.+# Force a fresh artcraft codegen so the embedded frontend cannot be stale.+cargo clean --release -p artcraft+if ($LASTEXITCODE -ne 0) {+ throw "cargo clean failed"+} cargo tauri build --config ".\crates\desktop\artcraft\tauri.conf.json"+if ($LASTEXITCODE -ne 0) {+ throw "ArtCraft Tauri build failed"+}++node ".\script\artcraft\verify_embedded_frontend.mjs"+if ($LASTEXITCODE -ne 0) {+ throw "ArtCraft release EXE does not contain the current frontend"+} Write-Host "" Write-Host "Production Build Done!" -ForegroundColor green -BackgroundColor white