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,496 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const https = require('node:https');
|
|
7
|
+
const { spawnSync } = require('node:child_process');
|
|
8
|
+
const readline = require('node:readline');
|
|
9
|
+
const { parseArgs } = require('./args');
|
|
10
|
+
const { summarizeRelease, pickLatestAssetForTarget } = require('./release');
|
|
11
|
+
const { createLocalBuilder } = require('./local-builder');
|
|
12
|
+
const { normalizeTarget } = require('./targets');
|
|
13
|
+
|
|
14
|
+
function parseGithubRepoFromUrl(value) {
|
|
15
|
+
const input = String(value || '').trim();
|
|
16
|
+
if (!input) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const patterns = [
|
|
21
|
+
/^https?:\/\/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/i,
|
|
22
|
+
/^git@github\.com:([^/]+\/[^/]+?)(?:\.git)?$/i,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
for (const pattern of patterns) {
|
|
26
|
+
const match = input.match(pattern);
|
|
27
|
+
if (match && match[1]) {
|
|
28
|
+
return match[1];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveGithubRepo() {
|
|
36
|
+
try {
|
|
37
|
+
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
|
38
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
39
|
+
const repository = packageJson && packageJson.repository;
|
|
40
|
+
|
|
41
|
+
if (typeof repository === 'string') {
|
|
42
|
+
return parseGithubRepoFromUrl(repository);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (repository && typeof repository.url === 'string') {
|
|
46
|
+
return parseGithubRepoFromUrl(repository.url);
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Ignore package.json read/parse failures and let normal validation run.
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function createDefaultIo() {
|
|
56
|
+
return {
|
|
57
|
+
log(message) {
|
|
58
|
+
process.stdout.write(`${message}\n`);
|
|
59
|
+
},
|
|
60
|
+
prompt(question, defaultValue) {
|
|
61
|
+
const suffix = typeof defaultValue === 'string' && defaultValue !== ''
|
|
62
|
+
? ` [${defaultValue}]`
|
|
63
|
+
: '';
|
|
64
|
+
const rl = readline.createInterface({
|
|
65
|
+
input: process.stdin,
|
|
66
|
+
output: process.stdout,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return new Promise((resolve) => {
|
|
70
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
71
|
+
rl.close();
|
|
72
|
+
resolve(answer);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isAffirmative(value) {
|
|
80
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
81
|
+
return normalized === 'y' || normalized === 'yes';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getJson(url, headers = {}) {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const req = https.get(url, { headers }, (res) => {
|
|
87
|
+
const chunks = [];
|
|
88
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
89
|
+
res.on('end', () => {
|
|
90
|
+
const body = Buffer.concat(chunks).toString('utf8');
|
|
91
|
+
const status = res.statusCode || 0;
|
|
92
|
+
if (status < 200 || status >= 300) {
|
|
93
|
+
reject(new Error(`Request failed (${status}): ${url}`));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
resolve(JSON.parse(body));
|
|
98
|
+
} catch {
|
|
99
|
+
reject(new Error(`Invalid JSON response from ${url}`));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
req.on('error', reject);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function streamDownload(url, outputPath) {
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const request = https.get(url, (res) => {
|
|
110
|
+
const status = res.statusCode || 0;
|
|
111
|
+
const redirect = res.headers.location;
|
|
112
|
+
if ([301, 302, 303, 307, 308].includes(status) && redirect) {
|
|
113
|
+
res.resume();
|
|
114
|
+
const nextUrl = new URL(redirect, url).toString();
|
|
115
|
+
streamDownload(nextUrl, outputPath).then(resolve).catch(reject);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (status < 200 || status >= 300) {
|
|
120
|
+
res.resume();
|
|
121
|
+
reject(new Error(`Download failed (${status}): ${url}`));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const out = fs.createWriteStream(outputPath);
|
|
126
|
+
out.on('error', reject);
|
|
127
|
+
out.on('finish', () => resolve(outputPath));
|
|
128
|
+
res.on('error', reject);
|
|
129
|
+
res.pipe(out);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
request.on('error', reject);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function createVersionResolver() {
|
|
137
|
+
return {
|
|
138
|
+
async resolveFromDmg(dmgPath) {
|
|
139
|
+
const normalizedPath = typeof dmgPath === 'string' ? dmgPath.trim() : '';
|
|
140
|
+
if (!normalizedPath) {
|
|
141
|
+
throw new Error('source dmg path is required to resolve version');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const mountBase = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-version-resolver-'));
|
|
145
|
+
const mountPoint = path.join(mountBase, 'mount');
|
|
146
|
+
fs.mkdirSync(mountPoint, { recursive: true });
|
|
147
|
+
|
|
148
|
+
const attach = spawnSync('hdiutil', [
|
|
149
|
+
'attach',
|
|
150
|
+
'-readonly',
|
|
151
|
+
'-nobrowse',
|
|
152
|
+
'-mountpoint',
|
|
153
|
+
mountPoint,
|
|
154
|
+
normalizedPath,
|
|
155
|
+
], { stdio: 'pipe' });
|
|
156
|
+
|
|
157
|
+
if (attach.status !== 0) {
|
|
158
|
+
const stderr = attach.stderr ? attach.stderr.toString().trim() : '';
|
|
159
|
+
throw new Error(`unable to mount source dmg for version detection${stderr ? `: ${stderr}` : ''}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const infoPlist = path.join(mountPoint, 'Codex.app', 'Contents', 'Info.plist');
|
|
164
|
+
if (!fs.existsSync(infoPlist)) {
|
|
165
|
+
throw new Error('Info.plist not found in mounted Codex.app');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const shortVersion = spawnSync('/usr/libexec/PlistBuddy', [
|
|
169
|
+
'-c',
|
|
170
|
+
'Print :CFBundleShortVersionString',
|
|
171
|
+
infoPlist,
|
|
172
|
+
], { stdio: 'pipe' });
|
|
173
|
+
|
|
174
|
+
if (shortVersion.status === 0) {
|
|
175
|
+
const value = shortVersion.stdout ? shortVersion.stdout.toString().trim() : '';
|
|
176
|
+
if (value) {
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const bundleVersion = spawnSync('/usr/libexec/PlistBuddy', [
|
|
182
|
+
'-c',
|
|
183
|
+
'Print :CFBundleVersion',
|
|
184
|
+
infoPlist,
|
|
185
|
+
], { stdio: 'pipe' });
|
|
186
|
+
|
|
187
|
+
if (bundleVersion.status === 0) {
|
|
188
|
+
const value = bundleVersion.stdout ? bundleVersion.stdout.toString().trim() : '';
|
|
189
|
+
if (value) {
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
throw new Error('could not read CFBundleShortVersionString from source app');
|
|
195
|
+
} finally {
|
|
196
|
+
const detach = spawnSync('hdiutil', ['detach', mountPoint], { stdio: 'pipe' });
|
|
197
|
+
if (detach.status !== 0) {
|
|
198
|
+
spawnSync('hdiutil', ['detach', '-force', mountPoint], { stdio: 'pipe' });
|
|
199
|
+
}
|
|
200
|
+
fs.rmSync(mountBase, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getDefaultDeps(overrides = {}) {
|
|
207
|
+
const io = overrides.io || createDefaultIo();
|
|
208
|
+
const env = overrides.env || process.env;
|
|
209
|
+
const githubRepo = resolveGithubRepo();
|
|
210
|
+
const githubToken = env.GITHUB_TOKEN || env.GH_TOKEN || '';
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
releaseApi: overrides.releaseApi || {
|
|
214
|
+
async getLatest() {
|
|
215
|
+
if (!githubRepo) {
|
|
216
|
+
throw new Error('Missing repository for latest release lookup (set repository.url in package.json)');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const url = `https://api.github.com/repos/${githubRepo}/releases/latest`;
|
|
220
|
+
const headers = {
|
|
221
|
+
'User-Agent': 'get-codex-lost-world',
|
|
222
|
+
Accept: 'application/vnd.github+json',
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
if (githubToken) {
|
|
226
|
+
headers.Authorization = `Bearer ${githubToken}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return getJson(url, headers);
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
io,
|
|
233
|
+
downloader: overrides.downloader || {
|
|
234
|
+
async download(url, location, filename) {
|
|
235
|
+
const normalizedLocation = String(location || '').trim();
|
|
236
|
+
const normalizedName = String(filename || '').trim();
|
|
237
|
+
if (!normalizedLocation || !normalizedName) {
|
|
238
|
+
throw new Error('downloader.download requires location and filename');
|
|
239
|
+
}
|
|
240
|
+
fs.mkdirSync(normalizedLocation, { recursive: true });
|
|
241
|
+
const outputPath = path.join(normalizedLocation, normalizedName);
|
|
242
|
+
return streamDownload(url, outputPath);
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
signer: overrides.signer || {
|
|
246
|
+
async sign(targetPath) {
|
|
247
|
+
const normalized = typeof targetPath === 'string' ? targetPath.trim() : '';
|
|
248
|
+
if (!normalized) {
|
|
249
|
+
throw new Error('sign path is required');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const result = spawnSync('codesign', [
|
|
253
|
+
'--force',
|
|
254
|
+
'--deep',
|
|
255
|
+
'--sign',
|
|
256
|
+
'-',
|
|
257
|
+
'--timestamp=none',
|
|
258
|
+
normalized,
|
|
259
|
+
], {
|
|
260
|
+
stdio: 'pipe',
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if (result.status !== 0) {
|
|
264
|
+
const stderr = result.stderr ? result.stderr.toString().trim() : '';
|
|
265
|
+
throw new Error(`codesign failed${stderr ? `: ${stderr}` : ''}`);
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
env,
|
|
270
|
+
builder: overrides.builder || createLocalBuilder(),
|
|
271
|
+
versionResolver: overrides.versionResolver || createVersionResolver(),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function getDefaultDownloadsLocation(env) {
|
|
276
|
+
const homeFromEnv = typeof env.HOME === 'string' ? env.HOME.trim() : '';
|
|
277
|
+
const homeDir = homeFromEnv || os.homedir();
|
|
278
|
+
return path.join(homeDir, 'Downloads');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function getDefaultBuildLocation(env) {
|
|
282
|
+
if (env && typeof env.PWD === 'string' && env.PWD.trim() !== '') {
|
|
283
|
+
return env.PWD.trim();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return process.cwd();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function usage() {
|
|
290
|
+
return [
|
|
291
|
+
'Usage:',
|
|
292
|
+
' npx get-codex-lost-world [mode]',
|
|
293
|
+
'',
|
|
294
|
+
'Modes (choose one, default: --build):',
|
|
295
|
+
' -b, --build Smart download (mac silicon: original Codex.dmg, others: latest GitHub release asset)',
|
|
296
|
+
' -c, --cache Show latest release info and optionally download/sign it',
|
|
297
|
+
' -s, --sign <path> Ad-hoc sign a local app/dmg path',
|
|
298
|
+
' -w, --workdir <path> Build working directory (download source + write Intel DMG)',
|
|
299
|
+
' --platform <p> Target platform: mac | windows',
|
|
300
|
+
' --arch <a> Target arch: x64 | arm64 (windows)',
|
|
301
|
+
' --format <f> Target format: dmg | zip',
|
|
302
|
+
'',
|
|
303
|
+
'Other options:',
|
|
304
|
+
' -h, --help Show this help message',
|
|
305
|
+
'',
|
|
306
|
+
'Examples:',
|
|
307
|
+
' npx get-codex-lost-world',
|
|
308
|
+
' npx get-codex-lost-world --workdir /tmp/codex-build',
|
|
309
|
+
' npx get-codex-lost-world --cache',
|
|
310
|
+
' npx get-codex-lost-world --sign /Applications/Codex.app',
|
|
311
|
+
].join('\n');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function mapHostArchToTargetArch(hostArch) {
|
|
315
|
+
const normalized = String(hostArch || '').trim().toLowerCase();
|
|
316
|
+
if (normalized === 'arm64') {
|
|
317
|
+
return 'arm64';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return 'x64';
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function resolveDefaultTargetInput(parsed, env) {
|
|
324
|
+
const runtimePlatform = String((env && env.RUNTIME_PLATFORM) || process.platform || '').trim().toLowerCase();
|
|
325
|
+
const runtimeArch = String((env && env.RUNTIME_ARCH) || process.arch || '').trim().toLowerCase();
|
|
326
|
+
|
|
327
|
+
const inferredPlatform = runtimePlatform === 'win32' ? 'windows' : 'mac';
|
|
328
|
+
const selectedPlatform = typeof parsed.platform === 'string' && parsed.platform.trim() !== ''
|
|
329
|
+
? parsed.platform
|
|
330
|
+
: inferredPlatform;
|
|
331
|
+
|
|
332
|
+
const normalizedPlatform = String(selectedPlatform).trim().toLowerCase();
|
|
333
|
+
const inferredArch = normalizedPlatform === 'windows' ? mapHostArchToTargetArch(runtimeArch) : 'x64';
|
|
334
|
+
const selectedArch = typeof parsed.arch === 'string' && parsed.arch.trim() !== ''
|
|
335
|
+
? parsed.arch
|
|
336
|
+
: inferredArch;
|
|
337
|
+
const selectedFormat = typeof parsed.format === 'string' && parsed.format.trim() !== ''
|
|
338
|
+
? parsed.format
|
|
339
|
+
: (normalizedPlatform === 'windows' ? 'zip' : 'dmg');
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
platform: selectedPlatform,
|
|
343
|
+
arch: selectedArch,
|
|
344
|
+
format: selectedFormat,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function isMacSiliconHost(env) {
|
|
349
|
+
const runtimePlatform = String((env && env.RUNTIME_PLATFORM) || process.platform || '').trim().toLowerCase();
|
|
350
|
+
const runtimeArch = String((env && env.RUNTIME_ARCH) || process.arch || '').trim().toLowerCase();
|
|
351
|
+
return runtimePlatform === 'darwin' && runtimeArch === 'arm64';
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function runCacheMode(parsed, deps) {
|
|
355
|
+
const target = normalizeTarget(resolveDefaultTargetInput(parsed, deps.env));
|
|
356
|
+
const latest = await deps.releaseApi.getLatest();
|
|
357
|
+
const summary = summarizeRelease(latest);
|
|
358
|
+
const asset = pickLatestAssetForTarget(latest, target);
|
|
359
|
+
const defaultLocation = getDefaultDownloadsLocation(deps.env);
|
|
360
|
+
|
|
361
|
+
deps.io.log(`Version: ${summary.version}`);
|
|
362
|
+
deps.io.log(`Datetime: ${summary.datetime}`);
|
|
363
|
+
deps.io.log(`Release notes:\n${summary.notes}`);
|
|
364
|
+
|
|
365
|
+
const locationFromArgs = String(parsed && parsed.workdir ? parsed.workdir : '').trim();
|
|
366
|
+
const locationInput = locationFromArgs !== ''
|
|
367
|
+
? locationFromArgs
|
|
368
|
+
: await deps.io.prompt('Download location', defaultLocation);
|
|
369
|
+
const location = String(locationInput || '').trim();
|
|
370
|
+
|
|
371
|
+
if (location === '') {
|
|
372
|
+
return {
|
|
373
|
+
mode: 'cache',
|
|
374
|
+
downloaded: false,
|
|
375
|
+
signed: false,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const filename = asset.name;
|
|
380
|
+
const fallbackPath = path.join(location, filename);
|
|
381
|
+
deps.io.log(`Downloading: ${asset.browser_download_url}`);
|
|
382
|
+
const downloadResult = await deps.downloader.download(asset.browser_download_url, location, filename);
|
|
383
|
+
const downloadedPath = typeof downloadResult === 'string'
|
|
384
|
+
? downloadResult
|
|
385
|
+
: (downloadResult && downloadResult.path) || fallbackPath;
|
|
386
|
+
deps.io.log(`Download done: ${downloadedPath}`);
|
|
387
|
+
|
|
388
|
+
if (target.platform === 'windows') {
|
|
389
|
+
return {
|
|
390
|
+
mode: 'cache',
|
|
391
|
+
downloaded: true,
|
|
392
|
+
signed: false,
|
|
393
|
+
path: downloadedPath,
|
|
394
|
+
target,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const signAnswer = await deps.io.prompt('Sign downloaded file? (Y/n)', 'Y');
|
|
399
|
+
let signed = false;
|
|
400
|
+
if (isAffirmative(signAnswer)) {
|
|
401
|
+
await deps.signer.sign(downloadedPath);
|
|
402
|
+
signed = true;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
mode: 'cache',
|
|
407
|
+
downloaded: true,
|
|
408
|
+
signed,
|
|
409
|
+
path: downloadedPath,
|
|
410
|
+
target,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function runSignMode(parsed, deps) {
|
|
415
|
+
await deps.signer.sign(parsed.signPath);
|
|
416
|
+
return {
|
|
417
|
+
mode: 'sign',
|
|
418
|
+
signed: true,
|
|
419
|
+
path: parsed.signPath,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function runBuildMode(parsed, deps) {
|
|
424
|
+
const target = normalizeTarget(resolveDefaultTargetInput(parsed, deps.env));
|
|
425
|
+
const defaultLocation = getDefaultBuildLocation(deps.env);
|
|
426
|
+
const location = String(parsed.workdir || '').trim() || defaultLocation;
|
|
427
|
+
const useOriginalDmg = target.platform === 'mac' && target.format === 'dmg' && isMacSiliconHost(deps.env);
|
|
428
|
+
|
|
429
|
+
if (!useOriginalDmg) {
|
|
430
|
+
const latest = await deps.releaseApi.getLatest();
|
|
431
|
+
const asset = pickLatestAssetForTarget(latest, target);
|
|
432
|
+
deps.io.log(`Downloading release asset: ${asset.browser_download_url}`);
|
|
433
|
+
const downloadResult = await deps.downloader.download(asset.browser_download_url, location, asset.name);
|
|
434
|
+
const downloadedPath = typeof downloadResult === 'string'
|
|
435
|
+
? downloadResult
|
|
436
|
+
: (downloadResult && downloadResult.path) || path.join(location, asset.name);
|
|
437
|
+
deps.io.log(`Download done: ${downloadedPath}`);
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
mode: 'build',
|
|
441
|
+
status: 'ok',
|
|
442
|
+
downloaded: true,
|
|
443
|
+
path: downloadedPath,
|
|
444
|
+
outputName: asset.name,
|
|
445
|
+
target,
|
|
446
|
+
source: 'release',
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const sourceUrl = String(deps.env.CI_SOURCE_URL || 'https://persistent.oaistatic.com/codex-app-prod/Codex.dmg');
|
|
451
|
+
const filename = 'Codex.dmg';
|
|
452
|
+
deps.io.log(`Downloading: ${sourceUrl}`);
|
|
453
|
+
const downloadResult = await deps.downloader.download(sourceUrl, location, filename);
|
|
454
|
+
const downloadedPath = typeof downloadResult === 'string'
|
|
455
|
+
? downloadResult
|
|
456
|
+
: (downloadResult && downloadResult.path) || path.join(location, filename);
|
|
457
|
+
deps.io.log(`Download done: ${downloadedPath}`);
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
mode: 'build',
|
|
461
|
+
status: 'ok',
|
|
462
|
+
downloaded: true,
|
|
463
|
+
path: downloadedPath,
|
|
464
|
+
outputName: filename,
|
|
465
|
+
target,
|
|
466
|
+
source: 'original-dmg',
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async function runMain(argv = process.argv.slice(2), overrides = {}) {
|
|
471
|
+
const parsed = parseArgs(argv);
|
|
472
|
+
const deps = getDefaultDeps(overrides);
|
|
473
|
+
|
|
474
|
+
if (parsed.mode === 'help') {
|
|
475
|
+
deps.io.log(usage());
|
|
476
|
+
return { mode: 'help' };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (parsed.mode === 'cache') {
|
|
480
|
+
return runCacheMode(parsed, deps);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (parsed.mode === 'sign') {
|
|
484
|
+
return runSignMode(parsed, deps);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return runBuildMode(parsed, deps);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
module.exports = {
|
|
491
|
+
runMain,
|
|
492
|
+
isAffirmative,
|
|
493
|
+
usage,
|
|
494
|
+
parseGithubRepoFromUrl,
|
|
495
|
+
resolveGithubRepo,
|
|
496
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function summarizeRelease(release) {
|
|
4
|
+
return {
|
|
5
|
+
version: release?.tag_name || release?.name || '',
|
|
6
|
+
datetime: release?.published_at || release?.created_at || '',
|
|
7
|
+
notes: release?.body || '',
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function ensureAssetUrl(asset, extensionLabel) {
|
|
12
|
+
if (typeof asset?.browser_download_url !== 'string' || asset.browser_download_url.trim() === '') {
|
|
13
|
+
throw new Error(`Selected ${extensionLabel} asset is missing browser_download_url`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function pickLatestAssetForTarget(release, target = {}) {
|
|
18
|
+
const assets = Array.isArray(release?.assets) ? release.assets : [];
|
|
19
|
+
const platform = String(target.platform || 'mac').trim().toLowerCase();
|
|
20
|
+
const arch = String(target.arch || 'x64').trim().toLowerCase();
|
|
21
|
+
const format = String(target.format || (platform === 'windows' ? 'zip' : 'dmg')).trim().toLowerCase();
|
|
22
|
+
|
|
23
|
+
const candidates = assets.filter((asset) => typeof asset?.name === 'string' && asset.name.toLowerCase().endsWith(`.${format}`));
|
|
24
|
+
if (candidates.length === 0) {
|
|
25
|
+
throw new Error(`No .${format} asset found in release assets`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (platform === 'windows') {
|
|
29
|
+
const strictMatch = candidates.find((asset) => {
|
|
30
|
+
const name = asset.name.toLowerCase();
|
|
31
|
+
return name.includes('windows') && name.includes(arch);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (strictMatch) {
|
|
35
|
+
ensureAssetUrl(strictMatch, `.${format}`);
|
|
36
|
+
return strictMatch;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const archMatch = candidates.find((asset) => asset.name.toLowerCase().includes(arch));
|
|
40
|
+
if (archMatch) {
|
|
41
|
+
ensureAssetUrl(archMatch, `.${format}`);
|
|
42
|
+
return archMatch;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw new Error(`No .${format} asset found for windows/${arch}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const preferred = candidates.find((asset) => /^CodexIntelMac_.*\.dmg$/i.test(asset.name));
|
|
49
|
+
const selected = preferred || candidates[0];
|
|
50
|
+
ensureAssetUrl(selected, `.${format}`);
|
|
51
|
+
return selected;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function pickLatestDmgAsset(release) {
|
|
55
|
+
return pickLatestAssetForTarget(release, { platform: 'mac', arch: 'x64', format: 'dmg' });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
summarizeRelease,
|
|
60
|
+
pickLatestAssetForTarget,
|
|
61
|
+
pickLatestDmgAsset,
|
|
62
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function buildSignCommand(targetPath) {
|
|
4
|
+
const normalized = typeof targetPath === 'string' ? targetPath.trim() : '';
|
|
5
|
+
if (!normalized) {
|
|
6
|
+
throw new Error('sign path is required');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const shellQuotedPath = `'${normalized.replace(/'/g, `'\\''`)}'`;
|
|
10
|
+
return `codesign --force --deep --sign - --timestamp=none ${shellQuotedPath}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
buildSignCommand,
|
|
15
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SUPPORTED_PLATFORMS = ['mac', 'windows'];
|
|
4
|
+
|
|
5
|
+
const SUPPORTED_ARCHES_BY_PLATFORM = {
|
|
6
|
+
mac: ['x64'],
|
|
7
|
+
windows: ['x64', 'arm64'],
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const SUPPORTED_FORMATS_BY_PLATFORM = {
|
|
11
|
+
mac: ['dmg'],
|
|
12
|
+
windows: ['zip'],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function normalizeValue(value) {
|
|
16
|
+
return String(value || '').trim().toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeTarget(input = {}) {
|
|
20
|
+
const platform = normalizeValue(input.platform) || 'mac';
|
|
21
|
+
const arch = normalizeValue(input.arch) || 'x64';
|
|
22
|
+
const format = normalizeValue(input.format) || (platform === 'windows' ? 'zip' : 'dmg');
|
|
23
|
+
const target = { platform, arch, format };
|
|
24
|
+
validateTarget(target);
|
|
25
|
+
return target;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateTarget(target = {}) {
|
|
29
|
+
const platform = normalizeValue(target.platform);
|
|
30
|
+
const arch = normalizeValue(target.arch);
|
|
31
|
+
const format = normalizeValue(target.format);
|
|
32
|
+
|
|
33
|
+
if (!SUPPORTED_PLATFORMS.includes(platform)) {
|
|
34
|
+
throw new Error(`Unsupported platform: ${target.platform}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!SUPPORTED_ARCHES_BY_PLATFORM[platform].includes(arch)) {
|
|
38
|
+
throw new Error(`Unsupported arch for ${platform}: ${target.arch}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!SUPPORTED_FORMATS_BY_PLATFORM[platform].includes(format)) {
|
|
42
|
+
throw new Error(`Unsupported format for ${platform}: ${target.format}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
SUPPORTED_PLATFORMS,
|
|
48
|
+
SUPPORTED_ARCHES_BY_PLATFORM,
|
|
49
|
+
SUPPORTED_FORMATS_BY_PLATFORM,
|
|
50
|
+
normalizeTarget,
|
|
51
|
+
validateTarget,
|
|
52
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "get-codex-lost-world",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "CLI to download/build Codex Mac Intel DMG and Windows ZIP artifacts from upstream Codex.dmg.",
|
|
5
|
+
"main": "lib/get-codex-lost-world/main.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"get-codex-lost-world": "bin/get-codex-lost-world.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test tests/ci/*.test.js tests/get-codex-lost-world/*.test.js tests/docs/*.test.js"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/0x0a0d/get-codex-lost-world"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"codex",
|
|
18
|
+
"intel",
|
|
19
|
+
"build",
|
|
20
|
+
"mac",
|
|
21
|
+
"windows",
|
|
22
|
+
"zip"
|
|
23
|
+
],
|
|
24
|
+
"author": "0x0a0d",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"files": [
|
|
27
|
+
"bin",
|
|
28
|
+
"lib",
|
|
29
|
+
"scripts"
|
|
30
|
+
]
|
|
31
|
+
}
|