@atom-js-org/cli 0.2.0-alpha.0 → 0.4.0-alpha.0
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/package.json +1 -1
- package/src/build.cjs +278 -87
- package/src/doctor.cjs +4 -3
- package/src/init.cjs +3 -3
- package/src/run.cjs +3 -1
- package/src/utils.cjs +13 -3
package/package.json
CHANGED
package/src/build.cjs
CHANGED
|
@@ -39,8 +39,8 @@ async function localBuild(project, target, options = {}) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const buildRoot = path.join(project.root, 'build', target);
|
|
42
|
-
const unpacked = path.join(buildRoot, '
|
|
43
|
-
const appDir =
|
|
42
|
+
const unpacked = path.join(buildRoot, 'portable');
|
|
43
|
+
const appDir = null;
|
|
44
44
|
const productName = sanitizeFilename(project.config.productName);
|
|
45
45
|
|
|
46
46
|
console.log(`\nAtomJS build (${target})`);
|
|
@@ -50,56 +50,102 @@ async function localBuild(project, target, options = {}) {
|
|
|
50
50
|
await fse.remove(buildRoot);
|
|
51
51
|
await fse.ensureDir(unpacked);
|
|
52
52
|
|
|
53
|
-
const
|
|
53
|
+
const stageBase = await resolveShortStageBase();
|
|
54
|
+
const stageRoot = await fs.promises.mkdtemp(path.join(stageBase, 's-'));
|
|
54
55
|
const stagedApp = path.join(stageRoot, 'app');
|
|
56
|
+
const payloadPath = path.join(stageRoot, 'atom-app.payload.gz');
|
|
57
|
+
|
|
55
58
|
try {
|
|
56
59
|
await copyApplication(project.root, stagedApp);
|
|
57
60
|
await vendorFramework(stagedApp, project, target);
|
|
58
61
|
await installProductionDependencies(stagedApp, options.skipInstall, target);
|
|
59
|
-
await fse.move(stagedApp, appDir, { overwrite: true });
|
|
60
|
-
} finally {
|
|
61
|
-
await fse.remove(stageRoot);
|
|
62
|
-
}
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
console.log('Embedding application code and production dependencies into the executable...');
|
|
64
|
+
await createApplicationPayload(stagedApp, payloadPath);
|
|
65
|
+
|
|
66
|
+
const executableName = target === 'windows' ? `${productName}.exe` : productName;
|
|
67
|
+
const executablePath = path.join(unpacked, executableName);
|
|
68
|
+
await createSeaLauncher({
|
|
69
|
+
executablePath,
|
|
70
|
+
appDir,
|
|
71
|
+
target,
|
|
72
|
+
productName,
|
|
73
|
+
payloadPath
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const creditPath = path.join(unpacked, 'ATOMJS-CREDIT.txt');
|
|
77
|
+
await fs.promises.writeFile(
|
|
78
|
+
creditPath,
|
|
79
|
+
'Built with AtomJS\nhttps://github.com/Atom-js-org/atom\nCredit is optional inside applications.\n',
|
|
80
|
+
'utf8'
|
|
81
|
+
);
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
let outputs = [];
|
|
84
|
+
let runPath = executablePath;
|
|
85
|
+
let unpackedPath = unpacked;
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
if (target === 'windows') {
|
|
88
|
+
outputs = await packageWindows({ project, buildRoot, unpacked, executableName, productName });
|
|
89
|
+
}
|
|
90
|
+
if (target === 'macos') {
|
|
91
|
+
const packaged = await packageMacOS({
|
|
92
|
+
project,
|
|
93
|
+
buildRoot,
|
|
94
|
+
unpacked,
|
|
95
|
+
executableName,
|
|
96
|
+
productName,
|
|
97
|
+
hostSource: path.join(resolveFrameworkRoot(project.root), 'src', 'runtime', 'macos-native-host.m')
|
|
98
|
+
});
|
|
99
|
+
outputs = packaged.outputs;
|
|
100
|
+
runPath = packaged.appBundle;
|
|
101
|
+
unpackedPath = packaged.appBundle;
|
|
102
|
+
await fse.remove(unpacked);
|
|
103
|
+
}
|
|
104
|
+
if (target === 'linux') {
|
|
105
|
+
outputs = await packageLinux({ project, buildRoot, unpacked, executableName, productName });
|
|
106
|
+
}
|
|
81
107
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
};
|
|
97
|
-
|
|
108
|
+
const manifest = {
|
|
109
|
+
atomjsVersion: '0.3.0-alpha.0',
|
|
110
|
+
target,
|
|
111
|
+
productName,
|
|
112
|
+
appId: project.config.appId,
|
|
113
|
+
createdAt: new Date().toISOString(),
|
|
114
|
+
run: path.relative(buildRoot, runPath),
|
|
115
|
+
unpacked: path.relative(buildRoot, unpackedPath),
|
|
116
|
+
outputs: outputs.map((file) => path.relative(buildRoot, file))
|
|
117
|
+
};
|
|
118
|
+
await fs.promises.writeFile(path.join(buildRoot, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
119
|
+
|
|
120
|
+
console.log('\nBuild complete:');
|
|
121
|
+
console.log(` ${path.relative(project.root, unpackedPath)}`);
|
|
122
|
+
for (const output of outputs) console.log(` ${path.relative(project.root, output)}`);
|
|
123
|
+
return manifest;
|
|
124
|
+
} finally {
|
|
125
|
+
await fse.remove(stageRoot);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
98
128
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
129
|
+
|
|
130
|
+
async function resolveShortStageBase() {
|
|
131
|
+
const configured = process.env.ATOMJS_TEMP_DIR || process.env.ATOM_TEMP_DIR;
|
|
132
|
+
const candidates = configured
|
|
133
|
+
? [path.resolve(configured)]
|
|
134
|
+
: process.platform === 'win32'
|
|
135
|
+
? [path.join(path.parse(process.cwd()).root, '.atomjs-tmp'), path.join(os.tmpdir(), 'atomjs')]
|
|
136
|
+
: [path.join(os.tmpdir(), 'atomjs')];
|
|
137
|
+
|
|
138
|
+
let lastError;
|
|
139
|
+
for (const candidate of candidates) {
|
|
140
|
+
try {
|
|
141
|
+
await fse.ensureDir(candidate);
|
|
142
|
+
await fs.promises.access(candidate, fs.constants.W_OK);
|
|
143
|
+
return candidate;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
lastError = error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
throw new Error(`AtomJS could not create a writable short staging directory: ${lastError ? lastError.message : 'unknown error'}`);
|
|
103
149
|
}
|
|
104
150
|
|
|
105
151
|
async function copyApplication(projectRoot, appDir) {
|
|
@@ -188,43 +234,68 @@ async function installProductionDependencies(appDir, skipInstall, target) {
|
|
|
188
234
|
}
|
|
189
235
|
}
|
|
190
236
|
|
|
191
|
-
async function
|
|
192
|
-
const
|
|
193
|
-
await fse.ensureDir(work);
|
|
194
|
-
const launcherPath = path.join(work, 'launcher.cjs');
|
|
195
|
-
const blobPath = path.join(work, target === 'windows' ? 'sea-prep.blob.exe' : 'sea-prep.blob');
|
|
196
|
-
const configPath = path.join(work, 'sea-config.json');
|
|
237
|
+
async function createApplicationPayload(appDir, outputPath) {
|
|
238
|
+
const files = [];
|
|
197
239
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
240
|
+
async function addFile(source, relative, stat) {
|
|
241
|
+
files.push({
|
|
242
|
+
path: relative.split(path.sep).join('/'),
|
|
243
|
+
mode: stat.mode & 0o777,
|
|
244
|
+
data: (await fs.promises.readFile(source)).toString('base64')
|
|
245
|
+
});
|
|
246
|
+
}
|
|
203
247
|
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
248
|
+
async function visit(directory, relativePrefix = '', ancestors = new Set()) {
|
|
249
|
+
const realDirectory = await fs.promises.realpath(directory);
|
|
250
|
+
if (ancestors.has(realDirectory)) return;
|
|
251
|
+
const nextAncestors = new Set(ancestors);
|
|
252
|
+
nextAncestors.add(realDirectory);
|
|
253
|
+
|
|
254
|
+
const entries = await fs.promises.readdir(directory, { withFileTypes: true });
|
|
255
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
256
|
+
|
|
257
|
+
for (const entry of entries) {
|
|
258
|
+
const absolute = path.join(directory, entry.name);
|
|
259
|
+
const relative = relativePrefix ? path.join(relativePrefix, entry.name) : entry.name;
|
|
260
|
+
|
|
261
|
+
if (entry.isDirectory()) {
|
|
262
|
+
await visit(absolute, relative, nextAncestors);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (entry.isSymbolicLink()) {
|
|
267
|
+
const resolved = await fs.promises.realpath(absolute);
|
|
268
|
+
const stat = await fs.promises.stat(resolved);
|
|
269
|
+
if (stat.isDirectory()) {
|
|
270
|
+
await visit(resolved, relative, nextAncestors);
|
|
271
|
+
} else if (stat.isFile()) {
|
|
272
|
+
await addFile(resolved, relative, stat);
|
|
273
|
+
}
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (entry.isFile()) {
|
|
278
|
+
const stat = await fs.promises.stat(absolute);
|
|
279
|
+
await addFile(absolute, relative, stat);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
208
283
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
284
|
+
await visit(appDir);
|
|
285
|
+
const payload = Buffer.from(JSON.stringify({ format: 1, files }));
|
|
286
|
+
const compressed = require('node:zlib').gzipSync(payload, { level: 9 });
|
|
287
|
+
await fs.promises.writeFile(outputPath, compressed);
|
|
213
288
|
}
|
|
214
289
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
290
|
+
async function createSeaLauncher({ executablePath, appDir, target, productName, payloadPath = null }) {
|
|
291
|
+
const work = path.join(path.dirname(executablePath), '.sea-' + crypto.randomBytes(5).toString('hex'));
|
|
292
|
+
await fse.ensureDir(work);
|
|
293
|
+
const launcherPath = path.join(work, 'launcher.cjs');
|
|
294
|
+
const blobPath = path.join(work, target === 'windows' ? 'sea-prep.blob.exe' : 'sea-prep.blob');
|
|
295
|
+
const configPath = path.join(work, 'sea-config.json');
|
|
218
296
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
process.env.ATOM_BUILD = '1';
|
|
222
|
-
process.env.ATOM_NODE_EXECUTABLE = runtimeNode;
|
|
223
|
-
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
224
|
-
const mainPath = path.resolve(appDir, pkg.main || 'main.js');
|
|
225
|
-
const load = createRequire(packagePath);
|
|
226
|
-
load(mainPath);
|
|
227
|
-
`;
|
|
297
|
+
if (!payloadPath) throw new Error('AtomJS SEA payload is required.');
|
|
298
|
+
const launcher = createEmbeddedLauncherSource(productName);
|
|
228
299
|
await fs.promises.writeFile(launcherPath, launcher, 'utf8');
|
|
229
300
|
|
|
230
301
|
const seaConfig = {
|
|
@@ -232,7 +303,8 @@ load(mainPath);
|
|
|
232
303
|
output: blobPath,
|
|
233
304
|
disableExperimentalSEAWarning: true,
|
|
234
305
|
useSnapshot: false,
|
|
235
|
-
useCodeCache: false
|
|
306
|
+
useCodeCache: false,
|
|
307
|
+
assets: { 'atom-app': payloadPath }
|
|
236
308
|
};
|
|
237
309
|
await fs.promises.writeFile(configPath, JSON.stringify(seaConfig, null, 2));
|
|
238
310
|
|
|
@@ -250,11 +322,85 @@ load(mainPath);
|
|
|
250
322
|
|
|
251
323
|
if (target !== 'windows') await fs.promises.chmod(executablePath, 0o755);
|
|
252
324
|
if (target === 'macos' && commandExists('codesign', ['--version'])) {
|
|
253
|
-
await run('codesign', ['--sign', '-', executablePath]);
|
|
325
|
+
await run('codesign', ['--force', '--sign', '-', executablePath]);
|
|
254
326
|
}
|
|
255
327
|
await fse.remove(work);
|
|
256
328
|
}
|
|
257
329
|
|
|
330
|
+
function createEmbeddedLauncherSource(productName) {
|
|
331
|
+
return `
|
|
332
|
+
'use strict';
|
|
333
|
+
const path = require('node:path');
|
|
334
|
+
const fs = require('node:fs');
|
|
335
|
+
const os = require('node:os');
|
|
336
|
+
const crypto = require('node:crypto');
|
|
337
|
+
const zlib = require('node:zlib');
|
|
338
|
+
const { createRequire } = require('node:module');
|
|
339
|
+
const { pathToFileURL } = require('node:url');
|
|
340
|
+
const { getAsset } = require('node:sea');
|
|
341
|
+
|
|
342
|
+
const productName = ${JSON.stringify(productName)};
|
|
343
|
+
const payload = Buffer.from(getAsset('atom-app'));
|
|
344
|
+
const payloadHash = crypto.createHash('sha256').update(payload).digest('hex').slice(0, 24);
|
|
345
|
+
const dataRoot = process.platform === 'darwin'
|
|
346
|
+
? path.join(os.homedir(), 'Library', 'Application Support')
|
|
347
|
+
: process.platform === 'win32'
|
|
348
|
+
? (process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'))
|
|
349
|
+
: (process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'));
|
|
350
|
+
const appDir = path.join(dataRoot, productName, 'AtomJS Runtime', payloadHash);
|
|
351
|
+
const marker = path.join(appDir, '.atom-ready');
|
|
352
|
+
|
|
353
|
+
if (!fs.existsSync(marker)) {
|
|
354
|
+
const temporary = appDir + '.tmp-' + process.pid;
|
|
355
|
+
fs.rmSync(temporary, { recursive: true, force: true });
|
|
356
|
+
fs.mkdirSync(temporary, { recursive: true });
|
|
357
|
+
const archive = JSON.parse(zlib.gunzipSync(payload).toString('utf8'));
|
|
358
|
+
if (!archive || archive.format !== 1 || !Array.isArray(archive.files)) {
|
|
359
|
+
throw new Error('AtomJS embedded application payload is invalid.');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const entry of archive.files) {
|
|
363
|
+
const destination = path.resolve(temporary, String(entry.path));
|
|
364
|
+
const root = path.resolve(temporary) + path.sep;
|
|
365
|
+
if (!destination.startsWith(root)) throw new Error('Unsafe path in AtomJS application payload.');
|
|
366
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
367
|
+
fs.writeFileSync(destination, Buffer.from(entry.data, 'base64'));
|
|
368
|
+
if (process.platform !== 'win32' && Number.isInteger(entry.mode)) fs.chmodSync(destination, entry.mode);
|
|
369
|
+
}
|
|
370
|
+
fs.writeFileSync(path.join(temporary, '.atom-ready'), payloadHash);
|
|
371
|
+
fs.mkdirSync(path.dirname(appDir), { recursive: true });
|
|
372
|
+
fs.rmSync(appDir, { recursive: true, force: true });
|
|
373
|
+
fs.renameSync(temporary, appDir);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const packagePath = path.join(appDir, 'package.json');
|
|
377
|
+
if (!fs.existsSync(packagePath)) throw new Error('AtomJS could not materialize the embedded application.');
|
|
378
|
+
|
|
379
|
+
const executableDir = path.dirname(process.execPath);
|
|
380
|
+
process.chdir(appDir);
|
|
381
|
+
process.env.ATOM_PROJECT_ROOT = appDir;
|
|
382
|
+
process.env.ATOM_BUILD = '1';
|
|
383
|
+
process.env.ATOM_EMBEDDED_RUNTIME = '1';
|
|
384
|
+
process.env.ATOM_WINDOW_HOST_ENTRY = path.join(appDir, 'vendor', 'atomjs', 'src', 'runtime', 'window-host.mjs');
|
|
385
|
+
process.env.ATOM_MACOS_HOST_EXECUTABLE = path.join(executableDir, 'AtomJSWindowHost');
|
|
386
|
+
const hostModeIndex = process.argv.indexOf('--atomjs-window-host');
|
|
387
|
+
if (hostModeIndex !== -1) {
|
|
388
|
+
const hostEntry = process.env.ATOM_WINDOW_HOST_ENTRY;
|
|
389
|
+
const configPath = process.argv[hostModeIndex + 1];
|
|
390
|
+
process.argv = [process.argv[0], hostEntry, configPath];
|
|
391
|
+
import(pathToFileURL(hostEntry).href).catch((error) => {
|
|
392
|
+
console.error(error && error.stack ? error.stack : error);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
});
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
398
|
+
const mainPath = path.resolve(appDir, pkg.main || 'main.js');
|
|
399
|
+
const load = createRequire(packagePath);
|
|
400
|
+
load(mainPath);
|
|
401
|
+
`;
|
|
402
|
+
}
|
|
403
|
+
|
|
258
404
|
async function packageWindows({ project, buildRoot, unpacked, executableName, productName }) {
|
|
259
405
|
const outputs = [];
|
|
260
406
|
const zipPath = path.join(buildRoot, `${productName}-windows.zip`);
|
|
@@ -269,11 +415,14 @@ async function packageWindows({ project, buildRoot, unpacked, executableName, pr
|
|
|
269
415
|
: '';
|
|
270
416
|
const script = `
|
|
271
417
|
Unicode true
|
|
418
|
+
SetCompressor /SOLID lzma
|
|
272
419
|
!include "MUI2.nsh"
|
|
273
420
|
Name "${escapeNsis(productName)}"
|
|
274
421
|
OutFile "${installerPath.replace(/\\/g, '\\\\')}"
|
|
275
422
|
InstallDir "$LOCALAPPDATA\\Programs\\${escapeNsis(productName)}"
|
|
276
423
|
RequestExecutionLevel user
|
|
424
|
+
ShowInstDetails show
|
|
425
|
+
ShowUninstDetails show
|
|
277
426
|
${credit}
|
|
278
427
|
!insertmacro MUI_PAGE_WELCOME
|
|
279
428
|
!insertmacro MUI_PAGE_DIRECTORY
|
|
@@ -291,6 +440,7 @@ SectionEnd
|
|
|
291
440
|
Section "Uninstall"
|
|
292
441
|
Delete "$DESKTOP\\${escapeNsis(productName)}.lnk"
|
|
293
442
|
RMDir /r "$SMPROGRAMS\\${escapeNsis(productName)}"
|
|
443
|
+
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${escapeNsis(project.config.appId)}"
|
|
294
444
|
RMDir /r "$INSTDIR"
|
|
295
445
|
SectionEnd
|
|
296
446
|
`;
|
|
@@ -305,19 +455,49 @@ SectionEnd
|
|
|
305
455
|
return outputs;
|
|
306
456
|
}
|
|
307
457
|
|
|
308
|
-
async function packageMacOS({ project, buildRoot, unpacked, executableName, productName }) {
|
|
458
|
+
async function packageMacOS({ project, buildRoot, unpacked, executableName, productName, hostSource }) {
|
|
309
459
|
const outputs = [];
|
|
310
460
|
const appBundle = path.join(buildRoot, `${productName}.app`);
|
|
311
461
|
const contents = path.join(appBundle, 'Contents');
|
|
312
462
|
const macosDir = path.join(contents, 'MacOS');
|
|
313
463
|
const resources = path.join(contents, 'Resources');
|
|
464
|
+
const mainExecutable = path.join(macosDir, productName);
|
|
465
|
+
const nativeHost = path.join(macosDir, 'AtomJSWindowHost');
|
|
466
|
+
|
|
314
467
|
await fse.ensureDir(macosDir);
|
|
315
468
|
await fse.ensureDir(resources);
|
|
316
|
-
await fse.copy(path.join(unpacked, executableName),
|
|
317
|
-
await fse.copy(path.join(unpacked, 'app'), path.join(resources, 'app'));
|
|
318
|
-
await fse.copy(path.join(unpacked, 'runtime'), path.join(resources, 'runtime'));
|
|
469
|
+
await fse.copy(path.join(unpacked, executableName), mainExecutable);
|
|
319
470
|
await fse.copy(path.join(unpacked, 'ATOMJS-CREDIT.txt'), path.join(resources, 'ATOMJS-CREDIT.txt'));
|
|
471
|
+
await fs.promises.chmod(mainExecutable, 0o755);
|
|
472
|
+
|
|
473
|
+
if (!fs.existsSync(hostSource)) {
|
|
474
|
+
throw new Error(`AtomJS macOS native host source was not found: ${hostSource}`);
|
|
475
|
+
}
|
|
476
|
+
if (!commandExists('/usr/bin/xcrun', ['--version'])) {
|
|
477
|
+
throw new Error('macOS builds require the Xcode Command Line Tools. Run `xcode-select --install`.');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
await run('/usr/bin/xcrun', [
|
|
481
|
+
'clang',
|
|
482
|
+
'-fobjc-arc',
|
|
483
|
+
'-fmodules',
|
|
484
|
+
'-mmacosx-version-min=12.0',
|
|
485
|
+
'-framework', 'Cocoa',
|
|
486
|
+
'-framework', 'WebKit',
|
|
487
|
+
hostSource,
|
|
488
|
+
'-o', nativeHost
|
|
489
|
+
]);
|
|
490
|
+
await fs.promises.chmod(nativeHost, 0o755);
|
|
491
|
+
|
|
492
|
+
let iconEntry = '';
|
|
493
|
+
const iconSource = project.config.icon ? path.resolve(project.root, project.config.icon) : null;
|
|
494
|
+
if (iconSource && fs.existsSync(iconSource) && path.extname(iconSource).toLowerCase() === '.icns') {
|
|
495
|
+
await fse.copy(iconSource, path.join(resources, 'AppIcon.icns'));
|
|
496
|
+
iconEntry = '<key>CFBundleIconFile</key><string>AppIcon</string>';
|
|
497
|
+
}
|
|
320
498
|
|
|
499
|
+
const version = String(project.packageJson.version || '0.0.0');
|
|
500
|
+
const bundleVersion = (version.match(/\d+/g) || ['1']).slice(0, 3).join('.');
|
|
321
501
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
322
502
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
323
503
|
<plist version="1.0"><dict>
|
|
@@ -326,13 +506,25 @@ async function packageMacOS({ project, buildRoot, unpacked, executableName, prod
|
|
|
326
506
|
<key>CFBundleName</key><string>${xml(productName)}</string>
|
|
327
507
|
<key>CFBundleDisplayName</key><string>${xml(productName)}</string>
|
|
328
508
|
<key>CFBundlePackageType</key><string>APPL</string>
|
|
329
|
-
<key>CFBundleShortVersionString</key><string>${xml(
|
|
509
|
+
<key>CFBundleShortVersionString</key><string>${xml(version)}</string>
|
|
510
|
+
<key>CFBundleVersion</key><string>${xml(bundleVersion)}</string>
|
|
511
|
+
<key>LSMinimumSystemVersion</key><string>12.0</string>
|
|
330
512
|
<key>NSHighResolutionCapable</key><true/>
|
|
513
|
+
${iconEntry}
|
|
331
514
|
</dict></plist>`;
|
|
332
|
-
|
|
333
|
-
await fs.promises.
|
|
515
|
+
const plistPath = path.join(contents, 'Info.plist');
|
|
516
|
+
await fs.promises.writeFile(plistPath, plist, 'utf8');
|
|
334
517
|
|
|
335
|
-
if (commandExists('
|
|
518
|
+
if (commandExists('/usr/bin/plutil', ['-help'])) {
|
|
519
|
+
await run('/usr/bin/plutil', ['-lint', plistPath]);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (commandExists('/usr/bin/codesign', ['--version'])) {
|
|
523
|
+
await run('/usr/bin/codesign', ['--force', '--sign', '-', nativeHost]);
|
|
524
|
+
await run('/usr/bin/codesign', ['--force', '--sign', '-', mainExecutable]);
|
|
525
|
+
await run('/usr/bin/codesign', ['--force', '--deep', '--sign', '-', appBundle]);
|
|
526
|
+
await run('/usr/bin/codesign', ['--verify', '--deep', '--strict', appBundle]);
|
|
527
|
+
}
|
|
336
528
|
|
|
337
529
|
const zipPath = path.join(buildRoot, `${productName}-macos.zip`);
|
|
338
530
|
if (commandExists('ditto', ['-h'])) {
|
|
@@ -347,7 +539,8 @@ async function packageMacOS({ project, buildRoot, unpacked, executableName, prod
|
|
|
347
539
|
await run('hdiutil', ['create', '-volname', productName, '-srcfolder', appBundle, '-ov', '-format', 'UDZO', dmgPath]);
|
|
348
540
|
outputs.push(dmgPath);
|
|
349
541
|
}
|
|
350
|
-
|
|
542
|
+
|
|
543
|
+
return { outputs, appBundle };
|
|
351
544
|
}
|
|
352
545
|
|
|
353
546
|
async function packageLinux({ project, buildRoot, unpacked, executableName, productName }) {
|
|
@@ -362,8 +555,6 @@ async function packageLinux({ project, buildRoot, unpacked, executableName, prod
|
|
|
362
555
|
await fse.ensureDir(usrBin);
|
|
363
556
|
await fse.ensureDir(usrLibApp);
|
|
364
557
|
await fse.copy(path.join(unpacked, executableName), path.join(usrBin, productName));
|
|
365
|
-
await fse.copy(path.join(unpacked, 'app'), path.join(usrBin, 'app'));
|
|
366
|
-
await fse.copy(path.join(unpacked, 'runtime'), path.join(usrBin, 'runtime'));
|
|
367
558
|
await fse.copy(path.join(unpacked, 'ATOMJS-CREDIT.txt'), path.join(usrLibApp, 'ATOMJS-CREDIT.txt'));
|
|
368
559
|
|
|
369
560
|
const appRun = `#!/bin/sh\nHERE="$(dirname "$(readlink -f "$0")")"\nexec "$HERE/usr/bin/${productName}" "$@"\n`;
|
|
@@ -502,4 +693,4 @@ function xml(value) {
|
|
|
502
693
|
return String(value).replace(/[<>&'\"]/g, (char) => ({ '<': '<', '>': '>', '&': '&', "'": ''', '"': '"' })[char]);
|
|
503
694
|
}
|
|
504
695
|
|
|
505
|
-
module.exports = { buildCommand, localBuild };
|
|
696
|
+
module.exports = { buildCommand, localBuild, createApplicationPayload };
|
package/src/doctor.cjs
CHANGED
|
@@ -15,9 +15,10 @@ async function doctorCommand(options = {}) {
|
|
|
15
15
|
rows.push(check('WebView2 Runtime', checkWindowsWebView2(), 'required by the native WebView'));
|
|
16
16
|
rows.push(check('NSIS (optional installer)', commandExists('makensis', ['/VERSION'])));
|
|
17
17
|
} else if (process.platform === 'darwin') {
|
|
18
|
-
rows.push(check('
|
|
18
|
+
rows.push(check('Xcode Command Line Tools', commandExists('/usr/bin/xcrun', ['--version']), 'required to compile the native Cocoa host'));
|
|
19
|
+
rows.push(check('Clang', commandExists('/usr/bin/xcrun', ['clang', '--version'])));
|
|
19
20
|
rows.push(check('WebKit framework', fs.existsSync('/System/Library/Frameworks/WebKit.framework')));
|
|
20
|
-
rows.push(check('codesign', commandExists('codesign', ['--version'])));
|
|
21
|
+
rows.push(check('codesign', commandExists('/usr/bin/codesign', ['--version'])));
|
|
21
22
|
rows.push(check('hdiutil', commandExists('hdiutil', ['help'])));
|
|
22
23
|
} else {
|
|
23
24
|
rows.push(check('pkg-config', commandExists('pkg-config', ['--version'])));
|
|
@@ -28,7 +29,7 @@ async function doctorCommand(options = {}) {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
if (process.platform === 'darwin') {
|
|
31
|
-
rows.push(check('macOS WKWebView backend', true, '
|
|
32
|
+
rows.push(check('macOS WKWebView backend', true, 'shared native Cocoa host; no osascript process'));
|
|
32
33
|
} else {
|
|
33
34
|
try {
|
|
34
35
|
const project = loadProject(options.project);
|
package/src/init.cjs
CHANGED
|
@@ -30,14 +30,14 @@ async function initCommand(directory, options = {}) {
|
|
|
30
30
|
start: 'atom run build'
|
|
31
31
|
},
|
|
32
32
|
dependencies: {
|
|
33
|
-
'@atom-js-org/runtime': '0.
|
|
34
|
-
electron: 'npm:@atom-js-org/electron@0.
|
|
33
|
+
'@atom-js-org/runtime': '0.3.0-alpha.0',
|
|
34
|
+
electron: 'npm:@atom-js-org/electron@0.3.0-alpha.0'
|
|
35
35
|
},
|
|
36
36
|
optionalDependencies: {
|
|
37
37
|
'webview-nodejs': '0.5.0'
|
|
38
38
|
},
|
|
39
39
|
devDependencies: {
|
|
40
|
-
'@atom-js-org/cli': '0.
|
|
40
|
+
'@atom-js-org/cli': '0.3.0-alpha.0'
|
|
41
41
|
},
|
|
42
42
|
overrides: {
|
|
43
43
|
tar: '7.5.20'
|
package/src/run.cjs
CHANGED
|
@@ -54,7 +54,9 @@ async function runBuild(options) {
|
|
|
54
54
|
if (!fs.existsSync(executable)) throw new Error(`Built executable not found: ${executable}`);
|
|
55
55
|
|
|
56
56
|
console.log(`Running ${path.basename(executable)}`);
|
|
57
|
-
const
|
|
57
|
+
const command = target === 'macos' && executable.endsWith('.app') ? 'open' : executable;
|
|
58
|
+
const args = command === 'open' ? ['-W', executable] : [];
|
|
59
|
+
const child = spawn(command, args, {
|
|
58
60
|
cwd: path.dirname(executable),
|
|
59
61
|
stdio: 'inherit'
|
|
60
62
|
});
|
package/src/utils.cjs
CHANGED
|
@@ -54,9 +54,17 @@ function loadProject(projectInput) {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function normalizeSpawn(command, args) {
|
|
58
|
+
if (process.platform === 'win32' && /\.(?:cmd|bat)$/i.test(command)) {
|
|
59
|
+
return { command: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', command, ...args] };
|
|
60
|
+
}
|
|
61
|
+
return { command, args };
|
|
62
|
+
}
|
|
63
|
+
|
|
57
64
|
function run(command, args, options = {}) {
|
|
58
65
|
return new Promise((resolve, reject) => {
|
|
59
|
-
const
|
|
66
|
+
const normalized = normalizeSpawn(command, args);
|
|
67
|
+
const child = spawn(normalized.command, normalized.args, {
|
|
60
68
|
cwd: options.cwd || process.cwd(),
|
|
61
69
|
env: options.env || process.env,
|
|
62
70
|
stdio: options.stdio || 'inherit',
|
|
@@ -71,7 +79,8 @@ function run(command, args, options = {}) {
|
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
function capture(command, args, options = {}) {
|
|
74
|
-
const
|
|
82
|
+
const normalized = normalizeSpawn(command, args);
|
|
83
|
+
const result = spawnSync(normalized.command, normalized.args, {
|
|
75
84
|
cwd: options.cwd || process.cwd(),
|
|
76
85
|
env: options.env || process.env,
|
|
77
86
|
encoding: 'utf8',
|
|
@@ -86,7 +95,8 @@ function capture(command, args, options = {}) {
|
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
function commandExists(command, args = ['--version']) {
|
|
89
|
-
const
|
|
98
|
+
const normalized = normalizeSpawn(command, args);
|
|
99
|
+
const result = spawnSync(normalized.command, normalized.args, { stdio: 'ignore', shell: false });
|
|
90
100
|
return !result.error && result.status === 0;
|
|
91
101
|
}
|
|
92
102
|
|