Skip to content

Commit 1b37d16

Browse files
committed
desktop: add accessibility engine and deterministic signing key loader
1 parent d96fed5 commit 1b37d16

3 files changed

Lines changed: 163 additions & 1 deletion

File tree

desktop/cortex-control-center/package-lock.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

desktop/cortex-control-center/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"build:daemon:dev": "cargo build --target-dir ../../daemon-rs/target-control-center-dev --manifest-path ../../daemon-rs/Cargo.toml",
1313
"predev": "node scripts/cleanup-dev-runtime.mjs",
1414
"dev": "tauri dev",
15-
"build": "tauri build",
15+
"build": "node scripts/run-tauri-build.mjs",
1616
"tauri": "tauri",
1717
"test": "vitest run",
1818
"test:watch": "vitest",
@@ -34,6 +34,7 @@
3434
"devDependencies": {
3535
"@tauri-apps/cli": "^2.0.0",
3636
"@vitejs/plugin-react": "^5.1.0",
37+
"accessibility-checker-engine": "4.0.16",
3738
"expect-cli": "^0.0.25",
3839
"vite": "^7.1.0",
3940
"vitest": "^4.1.2"
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { spawn } from "node:child_process";
2+
import { constants as fsConstants } from "node:fs";
3+
import { access, readFile } from "node:fs/promises";
4+
import { dirname, isAbsolute, resolve } from "node:path";
5+
import { fileURLToPath } from "node:url";
6+
7+
const scriptDir = dirname(fileURLToPath(import.meta.url));
8+
const projectDir = resolve(scriptDir, "..");
9+
10+
const defaultKeyFile = resolve(projectDir, ".secrets", "tauri", "updater-private.key");
11+
const defaultKeyPasswordFile = resolve(projectDir, ".secrets", "tauri", "updater-private.key.password");
12+
13+
const keyPathEnvNames = ["TAURI_SIGNING_PRIVATE_KEY_FILE", "TAURI_SIGNING_PRIVATE_KEY_PATH"];
14+
const passwordPathEnvNames = [
15+
"TAURI_SIGNING_PRIVATE_KEY_PASSWORD_FILE",
16+
"TAURI_SIGNING_PRIVATE_KEY_PASS_FILE",
17+
];
18+
19+
function resolvePath(input) {
20+
if (!input || !input.trim()) return "";
21+
const value = input.trim();
22+
return isAbsolute(value) ? value : resolve(projectDir, value);
23+
}
24+
25+
async function canRead(path) {
26+
if (!path) return false;
27+
try {
28+
await access(path, fsConstants.R_OK);
29+
return true;
30+
} catch {
31+
return false;
32+
}
33+
}
34+
35+
async function resolveTauriCliPath() {
36+
const candidates = [
37+
resolve(projectDir, "node_modules", "@tauri-apps", "cli", "tauri.js"),
38+
resolve(projectDir, "node_modules", "@tauri-apps", "cli", "bin", "tauri.js"),
39+
];
40+
41+
for (const candidate of candidates) {
42+
if (await canRead(candidate)) {
43+
return candidate;
44+
}
45+
}
46+
47+
return "";
48+
}
49+
50+
async function loadKey() {
51+
const inlineValue = process.env.TAURI_SIGNING_PRIVATE_KEY || "";
52+
if (inlineValue.trim()) {
53+
const possiblePath = resolvePath(inlineValue);
54+
if (await canRead(possiblePath)) {
55+
const key = await readFile(possiblePath, "utf8");
56+
return { key, source: possiblePath };
57+
}
58+
return { key: inlineValue, source: "TAURI_SIGNING_PRIVATE_KEY (inline value)" };
59+
}
60+
61+
const candidates = [
62+
...keyPathEnvNames.map((name) => ({ name, path: resolvePath(process.env[name] || "") })),
63+
{ name: "default", path: defaultKeyFile },
64+
];
65+
66+
for (const candidate of candidates) {
67+
if (await canRead(candidate.path)) {
68+
const key = await readFile(candidate.path, "utf8");
69+
return { key, source: candidate.path };
70+
}
71+
}
72+
73+
return null;
74+
}
75+
76+
async function loadPassword() {
77+
if (process.env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD) {
78+
return process.env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD;
79+
}
80+
81+
const candidates = [
82+
...passwordPathEnvNames.map((name) => resolvePath(process.env[name] || "")),
83+
defaultKeyPasswordFile,
84+
];
85+
86+
for (const candidate of candidates) {
87+
if (await canRead(candidate)) {
88+
return (await readFile(candidate, "utf8")).trim();
89+
}
90+
}
91+
92+
return "";
93+
}
94+
95+
function printMissingKeyError() {
96+
console.error("[desktop:build] Missing Tauri updater signing key.");
97+
console.error("[desktop:build] Checked:");
98+
console.error(" - TAURI_SIGNING_PRIVATE_KEY (inline value or file path)");
99+
console.error(" - TAURI_SIGNING_PRIVATE_KEY_FILE");
100+
console.error(" - TAURI_SIGNING_PRIVATE_KEY_PATH");
101+
console.error(` - ${defaultKeyFile}`);
102+
console.error("[desktop:build] Configure one of those locations, then retry.");
103+
}
104+
105+
async function main() {
106+
const tauriCli = await resolveTauriCliPath();
107+
if (!tauriCli) {
108+
console.error("[desktop:build] Missing Tauri CLI. Run npm ci first.");
109+
process.exit(1);
110+
}
111+
112+
const loadedKey = await loadKey();
113+
if (!loadedKey || !loadedKey.key.trim()) {
114+
printMissingKeyError();
115+
process.exit(1);
116+
}
117+
118+
const signingPassword = await loadPassword();
119+
const args = [tauriCli, "build", ...process.argv.slice(2)];
120+
const env = {
121+
...process.env,
122+
TAURI_SIGNING_PRIVATE_KEY: loadedKey.key,
123+
TAURI_SIGNING_PRIVATE_KEY_PASSWORD:
124+
signingPassword || process.env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD || "",
125+
};
126+
127+
console.log(`[desktop:build] Using updater signing key from ${loadedKey.source}`);
128+
129+
const child = spawn(process.execPath, args, {
130+
cwd: projectDir,
131+
env,
132+
stdio: "inherit",
133+
windowsHide: true,
134+
});
135+
136+
child.once("error", (error) => {
137+
console.error(`[desktop:build] Failed to start Tauri build: ${error.message}`);
138+
process.exit(1);
139+
});
140+
141+
child.once("exit", (code, signal) => {
142+
if (signal) {
143+
console.error(`[desktop:build] Tauri build terminated by signal ${signal}`);
144+
process.exit(1);
145+
}
146+
process.exit(code ?? 1);
147+
});
148+
}
149+
150+
main().catch((error) => {
151+
console.error(`[desktop:build] ${error.message}`);
152+
process.exit(1);
153+
});

0 commit comments

Comments
 (0)