1import * as NodeFS from "node:fs";
2import * as NodeModule from "node:module";
3import * as NodeOS from "node:os";
4import * as NodePath from "node:path";
5import * as NodeURL from "node:url";
6import * as NodeChildProcess from "node:child_process";
7
8const require = NodeModule.createRequire(import.meta.url);
9// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone repair script has no Effect runtime.
10const hostPlatform = NodeOS.platform();
11// oxlint-disable-next-line t3code/no-global-process-runtime -- Standalone repair script has no Effect runtime.
12const hostArch = NodeOS.arch();
13
14function getPlatformPath() {
15 switch (hostPlatform) {
16 case "darwin":
17 return "Electron.app/Contents/MacOS/Electron";
18 case "freebsd":
19 case "openbsd":
20 case "linux":
21 return "electron";
22 case "win32":
23 return "electron.exe";
24 default:
25 throw new Error(`Electron builds are not available on platform: ${hostPlatform}`);
26 }
27}
28
29function ensureExecutable(filePath) {
30 if (hostPlatform !== "win32") {
31 NodeFS.chmodSync(filePath, 0o755);
32 }
33}
34
35function repairPathFile(electronDir, platformPath) {
36 const pathFile = NodePath.join(electronDir, "path.txt");
37 const currentPath = NodeFS.existsSync(pathFile)
38 ? NodeFS.readFileSync(pathFile, "utf8")
39 : undefined;
40
41 if (currentPath !== platformPath) {
42 NodeFS.writeFileSync(pathFile, platformPath);
43 }
44}
45
46function getRequiredRuntimePaths(electronDir, platformPath) {
47 const paths = [NodePath.join(electronDir, "dist", platformPath)];
48
49 if (hostPlatform === "darwin") {
50 paths.push(
51 NodePath.join(electronDir, "dist", "Electron.app", "Contents", "Info.plist"),
52 NodePath.join(
53 electronDir,
54 "dist",
55 "Electron.app",
56 "Contents",
57 "Frameworks",
58 "Electron Framework.framework",
59 "Electron Framework",
60 ),
61 );
62 }
63
64 return paths;
65}
66
67function isMachO(filePath) {
68 if (hostPlatform !== "darwin") {
69 return true;
70 }
71
72 const result = NodeChildProcess.spawnSync("file", ["-b", filePath], {
73 encoding: "utf8",
74 });
75
76 return result.status === 0 && result.stdout.includes("Mach-O");
77}
78
79function missingRuntimePaths(electronDir, platformPath) {
80 return getRequiredRuntimePaths(electronDir, platformPath).filter((runtimePath) => {
81 return !NodeFS.existsSync(runtimePath);
82 });
83}
84
85function invalidRuntimePaths(electronDir, platformPath) {
86 if (hostPlatform !== "darwin") {
87 return [];
88 }
89
90 return [
91 NodePath.join(electronDir, "dist", platformPath),
92 NodePath.join(
93 electronDir,
94 "dist",
95 "Electron.app",
96 "Contents",
97 "Frameworks",
98 "Electron Framework.framework",
99 "Electron Framework",
100 ),
101 ].filter((runtimePath) => NodeFS.existsSync(runtimePath) && !isMachO(runtimePath));
102}
103
104function runChecked(command, args) {
105 const result = NodeChildProcess.spawnSync(command, args, {
106 encoding: "utf8",
107 stdio: "inherit",
108 });
109
110 if (result.status === 0) {
111 return;
112 }
113
114 throw new Error(
115 `${command} ${args.join(" ")} failed with exit code ${result.status ?? "unknown"}`,
116 );
117}
118
119function installElectronRuntime(electronDir, version) {
120 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-electron-"));
121 const zipPath = NodePath.join(tempDir, `electron-v${version}-${hostPlatform}-${hostArch}.zip`);
122
123 try {
124 runChecked("curl", [
125 "-fsSL",
126 `https://github.com/electron/electron/releases/download/v${version}/electron-v${version}-${hostPlatform}-${hostArch}.zip`,
127 "-o",
128 zipPath,
129 ]);
130 if (hostPlatform === "darwin") {
131 runChecked("ditto", ["-x", "-k", zipPath, NodePath.join(electronDir, "dist")]);
132 } else {
133 runChecked("python3", [
134 "-c",
135 "import os, sys, zipfile; os.makedirs(sys.argv[2], exist_ok=True); zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])",
136 zipPath,
137 NodePath.join(electronDir, "dist"),
138 ]);
139 }
140 } finally {
141 NodeFS.rmSync(tempDir, { recursive: true, force: true });
142 }
143}
144
145export function ensureElectronRuntime() {
146 const electronPackageJsonPath = require.resolve("electron/package.json");
147 const electronPackageJson = JSON.parse(NodeFS.readFileSync(electronPackageJsonPath, "utf8"));
148 const electronDir = NodePath.dirname(electronPackageJsonPath);
149 const platformPath = getPlatformPath();
150 const electronPath = NodePath.join(electronDir, "dist", platformPath);
151 const missingBeforeInstall = missingRuntimePaths(electronDir, platformPath);
152 const invalidBeforeInstall = invalidRuntimePaths(electronDir, platformPath);
153
154 if (missingBeforeInstall.length > 0 || invalidBeforeInstall.length > 0) {
155 if (NodeFS.existsSync(NodePath.join(electronDir, "dist"))) {
156 NodeFS.rmSync(NodePath.join(electronDir, "dist"), { recursive: true, force: true });
157 }
158 NodeFS.rmSync(NodePath.join(electronDir, "path.txt"), { force: true });
159 installElectronRuntime(electronDir, electronPackageJson.version);
160 }
161
162 const missingAfterInstall = missingRuntimePaths(electronDir, platformPath);
163 const invalidAfterInstall = invalidRuntimePaths(electronDir, platformPath);
164 if (missingAfterInstall.length > 0 || invalidAfterInstall.length > 0) {
165 throw new Error(
166 `Electron runtime is incomplete after install.\nMissing:\n${missingAfterInstall
167 .map((runtimePath) => `- ${runtimePath}`)
168 .join("\n")}\nInvalid:\n${invalidAfterInstall
169 .map((runtimePath) => `- ${runtimePath}`)
170 .join("\n")}`,
171 );
172 }
173
174 ensureExecutable(electronPath);
175 repairPathFile(electronDir, platformPath);
176
177 return electronPath;
178}
179
180// `file://${argv[1]}` never matches on Windows (drive letters need `file:///C:/`).
181if (process.argv[1] && NodeURL.pathToFileURL(process.argv[1]).href === import.meta.url) {
182 const electronPath = ensureElectronRuntime();
183 process.stdout.write(`${electronPath}\n`);
184}