get-codex-lost-world 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -0
- package/bin/get-codex-lost-world.js +62 -0
- package/lib/ci/state.js +17 -0
- package/lib/get-codex-lost-world/args.js +124 -0
- package/lib/get-codex-lost-world/build.js +31 -0
- package/lib/get-codex-lost-world/cache-download.js +21 -0
- package/lib/get-codex-lost-world/local-builder.js +62 -0
- package/lib/get-codex-lost-world/main.js +496 -0
- package/lib/get-codex-lost-world/release.js +62 -0
- package/lib/get-codex-lost-world/sign.js +15 -0
- package/lib/get-codex-lost-world/targets.js +52 -0
- package/package.json +31 -0
- package/scripts/build-intel-dmg.js +508 -0
- package/scripts/build-windows-zip.js +341 -0
- package/scripts/ci/check-update.js +83 -0
- package/scripts/ci/extract-codex-payload.js +148 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// CLI argument parsing
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
let outputDmg = null;
|
|
14
|
+
let verbose = false;
|
|
15
|
+
const positional = [];
|
|
16
|
+
|
|
17
|
+
const argv = process.argv.slice(2);
|
|
18
|
+
for (let i = 0; i < argv.length; i++) {
|
|
19
|
+
const a = argv[i];
|
|
20
|
+
if (a === '-h' || a === '--help') {
|
|
21
|
+
printUsage();
|
|
22
|
+
process.exit(0);
|
|
23
|
+
} else if (a === '-V' || a === '--verbose') {
|
|
24
|
+
verbose = true;
|
|
25
|
+
} else if (a === '-o' || a === '--output') {
|
|
26
|
+
outputDmg = argv[++i];
|
|
27
|
+
if (!outputDmg) die('--output requires a <file> argument');
|
|
28
|
+
} else if (a.startsWith('--output=')) {
|
|
29
|
+
outputDmg = a.slice('--output='.length);
|
|
30
|
+
} else if (a.startsWith('-')) {
|
|
31
|
+
printUsage();
|
|
32
|
+
die(`Unknown option: ${a}`);
|
|
33
|
+
} else {
|
|
34
|
+
positional.push(a);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (positional.length > 1) {
|
|
39
|
+
printUsage();
|
|
40
|
+
die('Too many arguments');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Paths
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
const SCRIPT_DIR = path.resolve(__dirname, '..');
|
|
48
|
+
const SCRIPT_PARENT_DIR = path.dirname(SCRIPT_DIR);
|
|
49
|
+
const TMP_BASE = path.join(os.tmpdir(), 'codex-intel-build');
|
|
50
|
+
|
|
51
|
+
// RUN_ID: YYYYmmdd_HHMMss
|
|
52
|
+
const _now = new Date();
|
|
53
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
54
|
+
const RUN_ID =
|
|
55
|
+
`${_now.getFullYear()}${pad(_now.getMonth() + 1)}${pad(_now.getDate())}` +
|
|
56
|
+
`_${pad(_now.getHours())}${pad(_now.getMinutes())}${pad(_now.getSeconds())}`;
|
|
57
|
+
|
|
58
|
+
const WORK_DIR = path.join(TMP_BASE, `codex_intel_build_${RUN_ID}`);
|
|
59
|
+
const MOUNT_POINT = path.join(WORK_DIR, 'mount');
|
|
60
|
+
|
|
61
|
+
if (!outputDmg) {
|
|
62
|
+
outputDmg = path.join(SCRIPT_DIR, 'CodexIntelMac.dmg');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Logging
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
let cleanedUp = false;
|
|
70
|
+
|
|
71
|
+
function timestamp() {
|
|
72
|
+
const n = new Date();
|
|
73
|
+
return (
|
|
74
|
+
`${n.getFullYear()}-${pad(n.getMonth() + 1)}-${pad(n.getDate())} ` +
|
|
75
|
+
`${pad(n.getHours())}:${pad(n.getMinutes())}:${pad(n.getSeconds())}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function log(msg) {
|
|
80
|
+
const line = `[${timestamp()}] ${msg}`;
|
|
81
|
+
process.stdout.write(line + '\n');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function die(msg) {
|
|
85
|
+
log(`ERROR: ${msg}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function printUsage() {
|
|
90
|
+
process.stdout.write(`\
|
|
91
|
+
Usage:
|
|
92
|
+
node scripts/build-intel-dmg.js [options] [path/to/Codex.dmg]
|
|
93
|
+
|
|
94
|
+
Options:
|
|
95
|
+
-o, --output <file> Output DMG path (default: ./CodexIntelMac.dmg)
|
|
96
|
+
-V, --verbose Enable verbose logging
|
|
97
|
+
-h, --help Show this help
|
|
98
|
+
|
|
99
|
+
Behavior:
|
|
100
|
+
- Reads source DMG from ../Codex.dmg by default (or explicit path argument)
|
|
101
|
+
- Never modifies the original DMG
|
|
102
|
+
- Uses os.tmpdir() for all build steps
|
|
103
|
+
- Cleans temporary build files on completion
|
|
104
|
+
- Produces Intel DMG output path provided by caller
|
|
105
|
+
`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Shell helpers
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Run a shell command, streaming output to the terminal (and log via stdio:inherit).
|
|
114
|
+
* Throws on non-zero exit.
|
|
115
|
+
*/
|
|
116
|
+
function run(cmd, opts = {}) {
|
|
117
|
+
if (verbose) log(`$ ${cmd}`);
|
|
118
|
+
const result = spawnSync(cmd, {
|
|
119
|
+
shell: true,
|
|
120
|
+
stdio: opts.silent ? 'pipe' : 'inherit',
|
|
121
|
+
cwd: opts.cwd,
|
|
122
|
+
});
|
|
123
|
+
if (result.status !== 0) {
|
|
124
|
+
const stderr = result.stderr ? result.stderr.toString().trim() : '';
|
|
125
|
+
die(`Command failed (exit ${result.status}): ${cmd}${stderr ? '\n' + stderr : ''}`);
|
|
126
|
+
}
|
|
127
|
+
return result.stdout ? result.stdout.toString().trim() : '';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Run a command and capture its stdout. Throws on non-zero exit. */
|
|
131
|
+
function capture(cmd, opts = {}) {
|
|
132
|
+
if (verbose) log(`$ ${cmd}`);
|
|
133
|
+
const result = spawnSync(cmd, { shell: true, stdio: 'pipe', cwd: opts.cwd });
|
|
134
|
+
if (result.status !== 0) {
|
|
135
|
+
die(`Command failed (exit ${result.status}): ${cmd}\n${result.stderr?.toString().trim() ?? ''}`);
|
|
136
|
+
}
|
|
137
|
+
return result.stdout.toString().trim();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Run silently, ignoring errors. */
|
|
141
|
+
function runIgnore(cmd) {
|
|
142
|
+
spawnSync(cmd, { shell: true, stdio: 'pipe' });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Copy a file and set permissions to 0o755. */
|
|
146
|
+
function installBin(src, dst) {
|
|
147
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
148
|
+
fs.copyFileSync(src, dst);
|
|
149
|
+
fs.chmodSync(dst, 0o755);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Cleanup / signal handling
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
let attachedByScript = false;
|
|
157
|
+
|
|
158
|
+
function cleanup(exitCode) {
|
|
159
|
+
if (cleanedUp) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
cleanedUp = true;
|
|
163
|
+
|
|
164
|
+
if (attachedByScript && fs.existsSync(MOUNT_POINT)) {
|
|
165
|
+
runIgnore(`hdiutil detach "${MOUNT_POINT}"`);
|
|
166
|
+
runIgnore(`hdiutil detach -force "${MOUNT_POINT}"`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (fs.existsSync(WORK_DIR)) {
|
|
170
|
+
try {
|
|
171
|
+
fs.rmSync(WORK_DIR, { recursive: true, force: true });
|
|
172
|
+
} catch {
|
|
173
|
+
if (exitCode !== 0) {
|
|
174
|
+
log(`Warning: unable to remove temporary directory: ${WORK_DIR}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
process.on('exit', cleanup);
|
|
181
|
+
process.on('SIGINT', () => { cleanup(1); process.exit(1); });
|
|
182
|
+
process.on('SIGTERM', () => { cleanup(1); process.exit(1); });
|
|
183
|
+
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// Main
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
(function main() {
|
|
189
|
+
fs.mkdirSync(TMP_BASE, { recursive: true });
|
|
190
|
+
|
|
191
|
+
log('Starting Intel build pipeline');
|
|
192
|
+
log(`Script dir: ${SCRIPT_DIR}`);
|
|
193
|
+
log(`Default source location: ${path.join(SCRIPT_PARENT_DIR, 'Codex.dmg')}`);
|
|
194
|
+
log(`Work dir: ${WORK_DIR}`);
|
|
195
|
+
fs.mkdirSync(WORK_DIR, { recursive: true });
|
|
196
|
+
|
|
197
|
+
// Validate required tools early.
|
|
198
|
+
for (const cmd of ['hdiutil', 'ditto', 'npm', 'npx', 'node', 'file', 'codesign', 'xattr']) {
|
|
199
|
+
const check = spawnSync('command', ['-v', cmd], { shell: true, stdio: 'pipe' });
|
|
200
|
+
if (check.status !== 0) die(`Missing required command: ${cmd}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ------------------------------------------------------------------
|
|
204
|
+
// Resolve source DMG path:
|
|
205
|
+
// 1) explicit positional argument
|
|
206
|
+
// 2) ../Codex.dmg
|
|
207
|
+
// 3) single *.dmg in parent directory
|
|
208
|
+
// ------------------------------------------------------------------
|
|
209
|
+
let inputDmg;
|
|
210
|
+
if (positional.length === 1) {
|
|
211
|
+
inputDmg = path.resolve(positional[0]);
|
|
212
|
+
} else if (fs.existsSync(path.join(SCRIPT_PARENT_DIR, 'Codex.dmg'))) {
|
|
213
|
+
inputDmg = path.join(SCRIPT_PARENT_DIR, 'Codex.dmg');
|
|
214
|
+
} else {
|
|
215
|
+
const found = fs.readdirSync(SCRIPT_PARENT_DIR)
|
|
216
|
+
.filter((f) => f.endsWith('.dmg') && path.join(SCRIPT_PARENT_DIR, f) !== outputDmg)
|
|
217
|
+
.map((f) => path.join(SCRIPT_PARENT_DIR, f))
|
|
218
|
+
.sort();
|
|
219
|
+
if (found.length === 0) {
|
|
220
|
+
die('No source DMG found. Put Codex.dmg next to this repo folder (../Codex.dmg) or pass a path.');
|
|
221
|
+
}
|
|
222
|
+
if (found.length > 1) {
|
|
223
|
+
found.forEach((f) => process.stdout.write(f + '\n'));
|
|
224
|
+
die('Multiple DMGs found. Pass source DMG path explicitly.');
|
|
225
|
+
}
|
|
226
|
+
inputDmg = found[0];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!fs.existsSync(inputDmg)) die(`Source DMG not found: ${inputDmg}`);
|
|
230
|
+
log(`Source DMG: ${inputDmg}`);
|
|
231
|
+
|
|
232
|
+
// ------------------------------------------------------------------
|
|
233
|
+
// Mount source DMG in read-only mode.
|
|
234
|
+
// ------------------------------------------------------------------
|
|
235
|
+
log('Mounting source DMG in read-only mode');
|
|
236
|
+
fs.mkdirSync(MOUNT_POINT, { recursive: true });
|
|
237
|
+
|
|
238
|
+
let sourceApp;
|
|
239
|
+
const mountResult = spawnSync(
|
|
240
|
+
`hdiutil attach -readonly -nobrowse -mountpoint "${MOUNT_POINT}" "${inputDmg}"`,
|
|
241
|
+
{ shell: true, stdio: 'pipe' }
|
|
242
|
+
);
|
|
243
|
+
if (mountResult.status === 0) {
|
|
244
|
+
attachedByScript = true;
|
|
245
|
+
sourceApp = path.join(MOUNT_POINT, 'Codex.app');
|
|
246
|
+
} else if (fs.existsSync('/Volumes/Codex Installer/Codex.app')) {
|
|
247
|
+
sourceApp = '/Volumes/Codex Installer/Codex.app';
|
|
248
|
+
log(`Using existing mounted volume: ${sourceApp}`);
|
|
249
|
+
} else {
|
|
250
|
+
die('Failed to mount DMG and no fallback mounted Codex.app found');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (!fs.existsSync(sourceApp)) die('Codex.app not found inside DMG');
|
|
254
|
+
|
|
255
|
+
const ORIG_APP = path.join(WORK_DIR, 'CodexOriginal.app');
|
|
256
|
+
const TARGET_APP = path.join(WORK_DIR, 'Codex.app');
|
|
257
|
+
const BUILD_PROJECT = path.join(WORK_DIR, 'build-project');
|
|
258
|
+
const DMG_ROOT = path.join(WORK_DIR, 'dmg-root');
|
|
259
|
+
|
|
260
|
+
// ------------------------------------------------------------------
|
|
261
|
+
// Copy app bundle to a local writable work directory.
|
|
262
|
+
// ------------------------------------------------------------------
|
|
263
|
+
log('Copying source app bundle to work dir');
|
|
264
|
+
run(`ditto "${sourceApp}" "${ORIG_APP}"`);
|
|
265
|
+
|
|
266
|
+
const FRAMEWORK_INFO = path.join(
|
|
267
|
+
ORIG_APP,
|
|
268
|
+
'Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist'
|
|
269
|
+
);
|
|
270
|
+
if (!fs.existsSync(FRAMEWORK_INFO)) die('Cannot read Electron framework info plist');
|
|
271
|
+
|
|
272
|
+
const electronVersion = capture(
|
|
273
|
+
`/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "${FRAMEWORK_INFO}"`
|
|
274
|
+
);
|
|
275
|
+
if (!electronVersion) die('Cannot detect Electron version from source app');
|
|
276
|
+
|
|
277
|
+
const ASAR_FILE = path.join(ORIG_APP, 'Contents/Resources/app.asar');
|
|
278
|
+
if (!fs.existsSync(ASAR_FILE)) die('app.asar not found in source app');
|
|
279
|
+
|
|
280
|
+
// ------------------------------------------------------------------
|
|
281
|
+
// Extract dependency versions from app.asar metadata.
|
|
282
|
+
// ------------------------------------------------------------------
|
|
283
|
+
const ASAR_META_DIR = path.join(WORK_DIR, 'asar-meta');
|
|
284
|
+
fs.mkdirSync(ASAR_META_DIR, { recursive: true });
|
|
285
|
+
|
|
286
|
+
run(
|
|
287
|
+
`npx --yes @electron/asar extract-file "${ASAR_FILE}" "node_modules/better-sqlite3/package.json" && ` +
|
|
288
|
+
`mv package.json better-sqlite3.package.json`,
|
|
289
|
+
{ cwd: ASAR_META_DIR }
|
|
290
|
+
);
|
|
291
|
+
run(
|
|
292
|
+
`npx --yes @electron/asar extract-file "${ASAR_FILE}" "node_modules/node-pty/package.json" && ` +
|
|
293
|
+
`mv package.json node-pty.package.json`,
|
|
294
|
+
{ cwd: ASAR_META_DIR }
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const BS_PKG = path.join(ASAR_META_DIR, 'better-sqlite3.package.json');
|
|
298
|
+
const NP_PKG = path.join(ASAR_META_DIR, 'node-pty.package.json');
|
|
299
|
+
if (!fs.existsSync(BS_PKG)) die('Cannot extract better-sqlite3 package.json from app.asar');
|
|
300
|
+
if (!fs.existsSync(NP_PKG)) die('Cannot extract node-pty package.json from app.asar');
|
|
301
|
+
|
|
302
|
+
const bsVersion = capture(`node -p "require(process.argv[1]).version" "${BS_PKG}"`);
|
|
303
|
+
const npVersion = capture(`node -p "require(process.argv[1]).version" "${NP_PKG}"`);
|
|
304
|
+
|
|
305
|
+
log(`Detected Electron version: ${electronVersion}`);
|
|
306
|
+
log(`Detected better-sqlite3 version: ${bsVersion}`);
|
|
307
|
+
log(`Detected node-pty version: ${npVersion}`);
|
|
308
|
+
|
|
309
|
+
// ------------------------------------------------------------------
|
|
310
|
+
// Build a temporary project to fetch x64 Electron/runtime artifacts.
|
|
311
|
+
// ------------------------------------------------------------------
|
|
312
|
+
log('Preparing x64 build project');
|
|
313
|
+
fs.mkdirSync(BUILD_PROJECT, { recursive: true });
|
|
314
|
+
fs.writeFileSync(
|
|
315
|
+
path.join(BUILD_PROJECT, 'package.json'),
|
|
316
|
+
JSON.stringify(
|
|
317
|
+
{
|
|
318
|
+
name: 'codex-intel-rebuild',
|
|
319
|
+
private: true,
|
|
320
|
+
version: '1.0.0',
|
|
321
|
+
dependencies: {
|
|
322
|
+
'@openai/codex': 'latest',
|
|
323
|
+
'better-sqlite3': bsVersion,
|
|
324
|
+
electron: electronVersion,
|
|
325
|
+
'node-pty': npVersion,
|
|
326
|
+
},
|
|
327
|
+
devDependencies: {
|
|
328
|
+
'@electron/rebuild': '3.7.2',
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
null,
|
|
332
|
+
2
|
|
333
|
+
)
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
// Force x64 arch so Electron and native deps download x64 binaries,
|
|
337
|
+
// even when host runner is ARM64 (macos-latest = Apple Silicon).
|
|
338
|
+
run('npm_config_arch=x64 npm install --no-audit --no-fund', {
|
|
339
|
+
cwd: BUILD_PROJECT,
|
|
340
|
+
silent: true,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Force-install x64 platform package for Codex CLI binaries.
|
|
344
|
+
// npm only installs optional deps matching host arch (arm64 on macos-latest)
|
|
345
|
+
// and rejects cross-arch installs with EBADPLATFORM. Use npm pack + tar to
|
|
346
|
+
// bypass platform checks entirely.
|
|
347
|
+
const codexPkg = JSON.parse(
|
|
348
|
+
fs.readFileSync(path.join(BUILD_PROJECT, 'node_modules/@openai/codex/package.json'), 'utf8')
|
|
349
|
+
);
|
|
350
|
+
const x64Alias = (codexPkg.optionalDependencies || {})['@openai/codex-darwin-x64'] || '';
|
|
351
|
+
const x64Spec = x64Alias.startsWith('npm:') ? x64Alias.slice(4) : `@openai/codex@latest`;
|
|
352
|
+
const x64Dir = path.join(BUILD_PROJECT, 'node_modules/@openai/codex-darwin-x64');
|
|
353
|
+
if (!fs.existsSync(x64Dir)) {
|
|
354
|
+
log('x64 codex platform package missing; fetching via npm pack');
|
|
355
|
+
const tarball = capture(
|
|
356
|
+
`npm pack "${x64Spec}" --pack-destination .`,
|
|
357
|
+
{ cwd: BUILD_PROJECT }
|
|
358
|
+
);
|
|
359
|
+
fs.mkdirSync(x64Dir, { recursive: true });
|
|
360
|
+
run(`tar xzf "${tarball}" --strip-components=1 -C "${x64Dir}"`, { cwd: BUILD_PROJECT });
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ------------------------------------------------------------------
|
|
364
|
+
// Use Electron x64 app template as the destination runtime.
|
|
365
|
+
// ------------------------------------------------------------------
|
|
366
|
+
log('Creating Intel app bundle from Electron runtime');
|
|
367
|
+
run(`ditto "${path.join(BUILD_PROJECT, 'node_modules/electron/dist/Electron.app')}" "${TARGET_APP}"`);
|
|
368
|
+
|
|
369
|
+
// ------------------------------------------------------------------
|
|
370
|
+
// Inject original Codex resources into the x64 runtime shell.
|
|
371
|
+
// ------------------------------------------------------------------
|
|
372
|
+
log('Injecting Codex resources from original app');
|
|
373
|
+
run(`rm -rf "${path.join(TARGET_APP, 'Contents/Resources')}"`);
|
|
374
|
+
run(`ditto "${path.join(ORIG_APP, 'Contents/Resources')}" "${path.join(TARGET_APP, 'Contents/Resources')}"`);
|
|
375
|
+
run(`cp "${path.join(ORIG_APP, 'Contents/Info.plist')}" "${path.join(TARGET_APP, 'Contents/Info.plist')}"`);
|
|
376
|
+
|
|
377
|
+
const infoPlist = path.join(TARGET_APP, 'Contents/Info.plist');
|
|
378
|
+
run(`/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable Electron" "${infoPlist}"`);
|
|
379
|
+
|
|
380
|
+
// Codex main process treats isPackaged=false as dev and tries localhost:5175.
|
|
381
|
+
// Force renderer URL to bundled app protocol in this transplanted runtime.
|
|
382
|
+
const addEnv = spawnSync(
|
|
383
|
+
`/usr/libexec/PlistBuddy -c "Add :LSEnvironment:ELECTRON_RENDERER_URL string app://-/index.html" "${infoPlist}"`,
|
|
384
|
+
{ shell: true, stdio: 'pipe' }
|
|
385
|
+
);
|
|
386
|
+
if (addEnv.status !== 0) {
|
|
387
|
+
run(`/usr/libexec/PlistBuddy -c "Set :LSEnvironment:ELECTRON_RENDERER_URL app://-/index.html" "${infoPlist}"`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ------------------------------------------------------------------
|
|
391
|
+
// Rebuild native modules against Electron x64 ABI.
|
|
392
|
+
// ------------------------------------------------------------------
|
|
393
|
+
log(`Rebuilding native modules for Electron ${electronVersion} x64`);
|
|
394
|
+
run(
|
|
395
|
+
`npx --yes @electron/rebuild -f -w better-sqlite3,node-pty --arch=x64 --version "${electronVersion}" -m "${BUILD_PROJECT}"`,
|
|
396
|
+
{ cwd: BUILD_PROJECT }
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
const TARGET_UNPACKED = path.join(TARGET_APP, 'Contents/Resources/app.asar.unpacked');
|
|
400
|
+
if (!fs.existsSync(TARGET_UNPACKED)) die('Target app.asar.unpacked not found');
|
|
401
|
+
|
|
402
|
+
// ------------------------------------------------------------------
|
|
403
|
+
// Replace arm64 native artifacts with rebuilt x64 binaries.
|
|
404
|
+
// ------------------------------------------------------------------
|
|
405
|
+
log('Replacing native binaries inside app.asar.unpacked');
|
|
406
|
+
|
|
407
|
+
installBin(
|
|
408
|
+
path.join(BUILD_PROJECT, 'node_modules/better-sqlite3/build/Release/better_sqlite3.node'),
|
|
409
|
+
path.join(TARGET_UNPACKED, 'node_modules/better-sqlite3/build/Release/better_sqlite3.node')
|
|
410
|
+
);
|
|
411
|
+
installBin(
|
|
412
|
+
path.join(BUILD_PROJECT, 'node_modules/node-pty/build/Release/pty.node'),
|
|
413
|
+
path.join(TARGET_UNPACKED, 'node_modules/node-pty/build/Release/pty.node')
|
|
414
|
+
);
|
|
415
|
+
installBin(
|
|
416
|
+
path.join(BUILD_PROJECT, 'node_modules/node-pty/build/Release/spawn-helper'),
|
|
417
|
+
path.join(TARGET_UNPACKED, 'node_modules/node-pty/build/Release/spawn-helper')
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
// node-pty prebuilt bin/darwin-x64
|
|
421
|
+
const nodePtyBinDir = path.join(BUILD_PROJECT, 'node_modules/node-pty/bin');
|
|
422
|
+
let nodePtyBinSrc = null;
|
|
423
|
+
if (fs.existsSync(nodePtyBinDir)) {
|
|
424
|
+
const findRes = spawnSync(
|
|
425
|
+
`find "${nodePtyBinDir}" -type f -name "node-pty.node" | grep "darwin-x64" | head -n 1`,
|
|
426
|
+
{ shell: true, stdio: 'pipe' }
|
|
427
|
+
);
|
|
428
|
+
nodePtyBinSrc = findRes.stdout.toString().trim() || null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (nodePtyBinSrc) {
|
|
432
|
+
installBin(
|
|
433
|
+
nodePtyBinSrc,
|
|
434
|
+
path.join(TARGET_UNPACKED, 'node_modules/node-pty/bin/darwin-x64-143/node-pty.node')
|
|
435
|
+
);
|
|
436
|
+
const arm64Dst = path.join(TARGET_UNPACKED, 'node_modules/node-pty/bin/darwin-arm64-143/node-pty.node');
|
|
437
|
+
if (fs.existsSync(arm64Dst)) {
|
|
438
|
+
// Keep hardcoded/fallback load paths functional even if the app references arm64 folder.
|
|
439
|
+
installBin(nodePtyBinSrc, arm64Dst);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ------------------------------------------------------------------
|
|
444
|
+
// Replace bundled arm64 codex/rg command-line binaries.
|
|
445
|
+
// ------------------------------------------------------------------
|
|
446
|
+
const CLI_X64_ROOT = path.join(
|
|
447
|
+
BUILD_PROJECT,
|
|
448
|
+
'node_modules/@openai/codex-darwin-x64/vendor/x86_64-apple-darwin'
|
|
449
|
+
);
|
|
450
|
+
const CLI_X64_BIN = path.join(CLI_X64_ROOT, 'codex/codex');
|
|
451
|
+
const RG_X64_BIN = path.join(CLI_X64_ROOT, 'path/rg');
|
|
452
|
+
|
|
453
|
+
if (!fs.existsSync(CLI_X64_BIN)) die('x64 Codex CLI binary not found after npm install');
|
|
454
|
+
if (!fs.existsSync(RG_X64_BIN)) die('x64 rg binary not found after npm install');
|
|
455
|
+
|
|
456
|
+
log('Replacing bundled codex/rg binaries with x64 versions');
|
|
457
|
+
installBin(CLI_X64_BIN, path.join(TARGET_APP, 'Contents/Resources/codex'));
|
|
458
|
+
installBin(CLI_X64_BIN, path.join(TARGET_UNPACKED, 'codex'));
|
|
459
|
+
installBin(RG_X64_BIN, path.join(TARGET_APP, 'Contents/Resources/rg'));
|
|
460
|
+
|
|
461
|
+
// ------------------------------------------------------------------
|
|
462
|
+
// Sparkle native addon is arm64-only in this flow; disable it.
|
|
463
|
+
// ------------------------------------------------------------------
|
|
464
|
+
log('Disabling incompatible Sparkle native addon');
|
|
465
|
+
const sparkle1 = path.join(TARGET_APP, 'Contents/Resources/native/sparkle.node');
|
|
466
|
+
const sparkle2 = path.join(TARGET_UNPACKED, 'native/sparkle.node');
|
|
467
|
+
if (fs.existsSync(sparkle1)) fs.unlinkSync(sparkle1);
|
|
468
|
+
if (fs.existsSync(sparkle2)) fs.unlinkSync(sparkle2);
|
|
469
|
+
|
|
470
|
+
// ------------------------------------------------------------------
|
|
471
|
+
// Sanity-check key binaries before signing/packaging.
|
|
472
|
+
// ------------------------------------------------------------------
|
|
473
|
+
log('Validating key binaries are x86_64');
|
|
474
|
+
const binsToCheck = [
|
|
475
|
+
path.join(TARGET_APP, 'Contents/MacOS/Electron'),
|
|
476
|
+
path.join(TARGET_APP, 'Contents/Resources/codex'),
|
|
477
|
+
path.join(TARGET_APP, 'Contents/Resources/rg'),
|
|
478
|
+
path.join(TARGET_UNPACKED, 'node_modules/better-sqlite3/build/Release/better_sqlite3.node'),
|
|
479
|
+
path.join(TARGET_UNPACKED, 'node_modules/node-pty/build/Release/pty.node'),
|
|
480
|
+
];
|
|
481
|
+
for (const binary of binsToCheck) {
|
|
482
|
+
const fileOutput = capture(`file "${binary}"`);
|
|
483
|
+
process.stdout.write(fileOutput + '\n');
|
|
484
|
+
if (!fileOutput.includes('x86_64')) die(`Expected x86_64 binary: ${binary}`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ------------------------------------------------------------------
|
|
488
|
+
// Re-sign modified app ad-hoc to satisfy macOS code integrity checks.
|
|
489
|
+
// ------------------------------------------------------------------
|
|
490
|
+
log('Signing app ad-hoc');
|
|
491
|
+
runIgnore(`xattr -cr "${TARGET_APP}"`);
|
|
492
|
+
run(`codesign --force --deep --sign - --timestamp=none "${TARGET_APP}"`);
|
|
493
|
+
run(`codesign --verify --deep --strict "${TARGET_APP}"`);
|
|
494
|
+
|
|
495
|
+
// ------------------------------------------------------------------
|
|
496
|
+
// Build final distributable DMG.
|
|
497
|
+
// ------------------------------------------------------------------
|
|
498
|
+
log(`Building output DMG: ${outputDmg}`);
|
|
499
|
+
if (fs.existsSync(outputDmg)) fs.unlinkSync(outputDmg);
|
|
500
|
+
fs.mkdirSync(DMG_ROOT, { recursive: true });
|
|
501
|
+
run(`ditto "${TARGET_APP}" "${path.join(DMG_ROOT, 'Codex.app')}"`);
|
|
502
|
+
run(`ln -s /Applications "${path.join(DMG_ROOT, 'Applications')}"`);
|
|
503
|
+
run(`hdiutil create -volname "Codex App Mac Intel" -srcfolder "${DMG_ROOT}" -ov -format UDZO "${outputDmg}"`, { silent: true });
|
|
504
|
+
|
|
505
|
+
log('Done');
|
|
506
|
+
log(`Output DMG: ${outputDmg}`);
|
|
507
|
+
log(`Work dir (will be removed): ${WORK_DIR}`);
|
|
508
|
+
})();
|