Open History to see the last commit
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
119// electron's own installer goes through @electron/get, which reuses its
120// download cache (~/.cache/electron, ~/Library/Caches/electron) and verifies
121// the bundled checksums. pnpm 11 never runs it because electron has no
122// install script, so a new worktree would otherwise download 130 MB again.
123function installFromElectronCache(electronDir) {
124 const result = NodeChildProcess.spawnSync(
125 process.execPath,
126 [NodePath.join(electronDir, "install.js")],
127 { stdio: ["ignore", "inherit", "inherit"] },
128 );
129 if (result.status === 0) {
130 return true;
131 }
132 process.stderr.write(
133 `electron install.js failed with exit code ${result.status ?? "unknown"}, downloading with curl instead\n`,
134 );
135 return false;
136}
137
138function downloadElectronRuntime(electronDir, version) {
139 const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-electron-"));
140 const zipPath = NodePath.join(tempDir, `electron-v${version}-${hostPlatform}-${hostArch}.zip`);
141
142 try {
143 runChecked("curl", [
144 "-fsSL",
145 "--retry",
146 "3",
147 "--retry-all-errors",
148 "--connect-timeout",
149 "30",
150 "--max-time",
151 "300",
152 `https://github.com/electron/electron/releases/download/v${version}/electron-v${version}-${hostPlatform}-${hostArch}.zip`,
153 "-o",
154 zipPath,
155 ]);
156 if (hostPlatform === "darwin") {
157 runChecked("ditto", ["-x", "-k", zipPath, NodePath.join(electronDir, "dist")]);
158 } else {
159 runChecked("python3", [
160 "-c",
161 "import os, sys, zipfile; os.makedirs(sys.argv[2], exist_ok=True); zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])",
162 zipPath,
163 NodePath.join(electronDir, "dist"),
164 ]);
165 }
166 } finally {
167 NodeFS.rmSync(tempDir, { recursive: true, force: true });
168 }
169}
170
171function resetRuntime(electronDir) {
172 NodeFS.rmSync(NodePath.join(electronDir, "dist"), { recursive: true, force: true });
173 NodeFS.rmSync(NodePath.join(electronDir, "path.txt"), { force: true });
174}
175
176export function ensureElectronRuntime() {
177 const electronPackageJsonPath = require.resolve("electron/package.json");
178 const electronPackageJson = JSON.parse(NodeFS.readFileSync(electronPackageJsonPath, "utf8"));
179 const electronDir = NodePath.dirname(electronPackageJsonPath);
180 const platformPath = getPlatformPath();
181 const electronPath = NodePath.join(electronDir, "dist", platformPath);
182 const needsInstall = () =>
183 missingRuntimePaths(electronDir, platformPath).length > 0 ||
184 invalidRuntimePaths(electronDir, platformPath).length > 0;
185
186 if (needsInstall()) {
187 resetRuntime(electronDir);
188 if (!installFromElectronCache(electronDir) || needsInstall()) {
189 resetRuntime(electronDir);
190 downloadElectronRuntime(electronDir, electronPackageJson.version);
191 }
192 }
193
194 const missingAfterInstall = missingRuntimePaths(electronDir, platformPath);
195 const invalidAfterInstall = invalidRuntimePaths(electronDir, platformPath);
196 if (missingAfterInstall.length > 0 || invalidAfterInstall.length > 0) {
197 throw new Error(
198 `Electron runtime is incomplete after install.\nMissing:\n${missingAfterInstall
199 .map((runtimePath) => `- ${runtimePath}`)
200 .join("\n")}\nInvalid:\n${invalidAfterInstall
201 .map((runtimePath) => `- ${runtimePath}`)
202 .join("\n")}`,
203 );
204 }
205
206 ensureExecutable(electronPath);
207 repairPathFile(electronDir, platformPath);
208
209 return electronPath;
210}
211
212// `file://${argv[1]}` never matches on Windows (drive letters need `file:///C:/`).
213if (process.argv[1] && NodeURL.pathToFileURL(process.argv[1]).href === import.meta.url) {
214 const electronPath = ensureElectronRuntime();
215 process.stdout.write(`${electronPath}\n`);
216}