@atom-js-org/cli 0.2.0-alpha.0 → 0.3.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atom-js-org/cli",
3
- "version": "0.2.0-alpha.0",
3
+ "version": "0.3.0-alpha.0",
4
4
  "description": "CLI for running and building AtomJS desktop applications.",
5
5
  "bin": {
6
6
  "atom": "bin/atom.cjs"
package/src/build.cjs CHANGED
@@ -52,54 +52,90 @@ async function localBuild(project, target, options = {}) {
52
52
 
53
53
  const stageRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'atomjs-stage-'));
54
54
  const stagedApp = path.join(stageRoot, 'app');
55
+ const payloadPath = path.join(stageRoot, 'atom-app.payload.gz');
56
+
55
57
  try {
56
58
  await copyApplication(project.root, stagedApp);
57
59
  await vendorFramework(stagedApp, project, target);
58
60
  await installProductionDependencies(stagedApp, options.skipInstall, target);
59
- await fse.move(stagedApp, appDir, { overwrite: true });
60
- } finally {
61
- await fse.remove(stageRoot);
62
- }
63
61
 
64
- const runtimeDir = path.join(unpacked, 'runtime');
65
- await fse.ensureDir(runtimeDir);
66
- const runtimeNodeName = target === 'windows' ? 'node.exe' : 'node';
67
- const runtimeNodePath = path.join(runtimeDir, runtimeNodeName);
68
- await fs.promises.copyFile(process.execPath, runtimeNodePath);
69
- if (target !== 'windows') await fs.promises.chmod(runtimeNodePath, 0o755);
62
+ if (target === 'macos') {
63
+ console.log('Embedding application code and production dependencies into the native executable...');
64
+ await createApplicationPayload(stagedApp, payloadPath);
65
+ } else {
66
+ await fse.move(stagedApp, appDir, { overwrite: true });
67
+ }
68
+
69
+ if (target !== 'macos') {
70
+ const runtimeDir = path.join(unpacked, 'runtime');
71
+ await fse.ensureDir(runtimeDir);
72
+ const runtimeNodeName = target === 'windows' ? 'node.exe' : 'node';
73
+ const runtimeNodePath = path.join(runtimeDir, runtimeNodeName);
74
+ await fs.promises.copyFile(process.execPath, runtimeNodePath);
75
+ if (target !== 'windows') await fs.promises.chmod(runtimeNodePath, 0o755);
76
+ }
70
77
 
71
- const executableName = target === 'windows' ? `${productName}.exe` : productName;
72
- const executablePath = path.join(unpacked, executableName);
73
- await createSeaLauncher({ executablePath, appDir, target });
78
+ const executableName = target === 'windows' ? `${productName}.exe` : productName;
79
+ const executablePath = path.join(unpacked, executableName);
80
+ await createSeaLauncher({
81
+ executablePath,
82
+ appDir,
83
+ target,
84
+ productName,
85
+ payloadPath: target === 'macos' ? payloadPath : null
86
+ });
87
+
88
+ const creditPath = path.join(unpacked, 'ATOMJS-CREDIT.txt');
89
+ await fs.promises.writeFile(
90
+ creditPath,
91
+ 'Built with AtomJS\nhttps://github.com/Atom-js-org/atom\nCredit is optional inside applications.\n',
92
+ 'utf8'
93
+ );
74
94
 
75
- const creditPath = path.join(unpacked, 'ATOMJS-CREDIT.txt');
76
- await fs.promises.writeFile(
77
- creditPath,
78
- 'Built with AtomJS\nhttps://github.com/Atom-js-org/atom\nCredit is optional inside applications.\n',
79
- 'utf8'
80
- );
95
+ let outputs = [];
96
+ let runPath = executablePath;
97
+ let unpackedPath = unpacked;
81
98
 
82
- const outputs = [];
83
- if (target === 'windows') outputs.push(...await packageWindows({ project, buildRoot, unpacked, executableName, productName }));
84
- if (target === 'macos') outputs.push(...await packageMacOS({ project, buildRoot, unpacked, executableName, productName }));
85
- if (target === 'linux') outputs.push(...await packageLinux({ project, buildRoot, unpacked, executableName, productName }));
86
-
87
- const manifest = {
88
- atomjsVersion: '0.2.0-alpha.0',
89
- target,
90
- productName,
91
- appId: project.config.appId,
92
- createdAt: new Date().toISOString(),
93
- run: path.relative(buildRoot, executablePath),
94
- unpacked: path.relative(buildRoot, unpacked),
95
- outputs: outputs.map((file) => path.relative(buildRoot, file))
96
- };
97
- await fs.promises.writeFile(path.join(buildRoot, 'manifest.json'), JSON.stringify(manifest, null, 2));
99
+ if (target === 'windows') {
100
+ outputs = await packageWindows({ project, buildRoot, unpacked, executableName, productName });
101
+ }
102
+ if (target === 'macos') {
103
+ const packaged = await packageMacOS({
104
+ project,
105
+ buildRoot,
106
+ unpacked,
107
+ executableName,
108
+ productName,
109
+ hostSource: path.join(resolveFrameworkRoot(project.root), 'src', 'runtime', 'macos-native-host.m')
110
+ });
111
+ outputs = packaged.outputs;
112
+ runPath = packaged.appBundle;
113
+ unpackedPath = packaged.appBundle;
114
+ await fse.remove(unpacked);
115
+ }
116
+ if (target === 'linux') {
117
+ outputs = await packageLinux({ project, buildRoot, unpacked, executableName, productName });
118
+ }
98
119
 
99
- console.log('\nBuild complete:');
100
- console.log(` ${path.relative(project.root, unpacked)}`);
101
- for (const output of outputs) console.log(` ${path.relative(project.root, output)}`);
102
- return manifest;
120
+ const manifest = {
121
+ atomjsVersion: '0.3.0-alpha.0',
122
+ target,
123
+ productName,
124
+ appId: project.config.appId,
125
+ createdAt: new Date().toISOString(),
126
+ run: path.relative(buildRoot, runPath),
127
+ unpacked: path.relative(buildRoot, unpackedPath),
128
+ outputs: outputs.map((file) => path.relative(buildRoot, file))
129
+ };
130
+ await fs.promises.writeFile(path.join(buildRoot, 'manifest.json'), JSON.stringify(manifest, null, 2));
131
+
132
+ console.log('\nBuild complete:');
133
+ console.log(` ${path.relative(project.root, unpackedPath)}`);
134
+ for (const output of outputs) console.log(` ${path.relative(project.root, output)}`);
135
+ return manifest;
136
+ } finally {
137
+ await fse.remove(stageRoot);
138
+ }
103
139
  }
104
140
 
105
141
  async function copyApplication(projectRoot, appDir) {
@@ -188,43 +224,69 @@ async function installProductionDependencies(appDir, skipInstall, target) {
188
224
  }
189
225
  }
190
226
 
191
- async function createSeaLauncher({ executablePath, appDir, target }) {
192
- const work = path.join(path.dirname(executablePath), '.sea-' + crypto.randomBytes(5).toString('hex'));
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');
227
+ async function createApplicationPayload(appDir, outputPath) {
228
+ const files = [];
197
229
 
198
- const launcher = `
199
- 'use strict';
200
- const path = require('node:path');
201
- const fs = require('node:fs');
202
- const { createRequire } = require('node:module');
230
+ async function addFile(source, relative, stat) {
231
+ files.push({
232
+ path: relative.split(path.sep).join('/'),
233
+ mode: stat.mode & 0o777,
234
+ data: (await fs.promises.readFile(source)).toString('base64')
235
+ });
236
+ }
203
237
 
204
- const executableDir = path.dirname(process.execPath);
205
- const appDir = process.platform === 'darwin' && executableDir.includes('.app' + path.sep + 'Contents' + path.sep + 'MacOS')
206
- ? path.resolve(executableDir, '..', 'Resources', 'app')
207
- : path.join(executableDir, 'app');
238
+ async function visit(directory, relativePrefix = '', ancestors = new Set()) {
239
+ const realDirectory = await fs.promises.realpath(directory);
240
+ if (ancestors.has(realDirectory)) return;
241
+ const nextAncestors = new Set(ancestors);
242
+ nextAncestors.add(realDirectory);
243
+
244
+ const entries = await fs.promises.readdir(directory, { withFileTypes: true });
245
+ entries.sort((a, b) => a.name.localeCompare(b.name));
246
+
247
+ for (const entry of entries) {
248
+ const absolute = path.join(directory, entry.name);
249
+ const relative = relativePrefix ? path.join(relativePrefix, entry.name) : entry.name;
250
+
251
+ if (entry.isDirectory()) {
252
+ await visit(absolute, relative, nextAncestors);
253
+ continue;
254
+ }
255
+
256
+ if (entry.isSymbolicLink()) {
257
+ const resolved = await fs.promises.realpath(absolute);
258
+ const stat = await fs.promises.stat(resolved);
259
+ if (stat.isDirectory()) {
260
+ await visit(resolved, relative, nextAncestors);
261
+ } else if (stat.isFile()) {
262
+ await addFile(resolved, relative, stat);
263
+ }
264
+ continue;
265
+ }
266
+
267
+ if (entry.isFile()) {
268
+ const stat = await fs.promises.stat(absolute);
269
+ await addFile(absolute, relative, stat);
270
+ }
271
+ }
272
+ }
208
273
 
209
- const packagePath = path.join(appDir, 'package.json');
210
- if (!fs.existsSync(packagePath)) {
211
- console.error('AtomJS application files are missing:', appDir);
212
- process.exit(1);
274
+ await visit(appDir);
275
+ const payload = Buffer.from(JSON.stringify({ format: 1, files }));
276
+ const compressed = require('node:zlib').gzipSync(payload, { level: 9 });
277
+ await fs.promises.writeFile(outputPath, compressed);
213
278
  }
214
279
 
215
- const runtimeNode = process.platform === 'darwin' && executableDir.includes('.app' + path.sep + 'Contents' + path.sep + 'MacOS')
216
- ? path.resolve(executableDir, '..', 'Resources', 'runtime', 'node')
217
- : path.join(executableDir, 'runtime', process.platform === 'win32' ? 'node.exe' : 'node');
280
+ async function createSeaLauncher({ executablePath, appDir, target, productName, payloadPath = null }) {
281
+ const work = path.join(path.dirname(executablePath), '.sea-' + crypto.randomBytes(5).toString('hex'));
282
+ await fse.ensureDir(work);
283
+ const launcherPath = path.join(work, 'launcher.cjs');
284
+ const blobPath = path.join(work, target === 'windows' ? 'sea-prep.blob.exe' : 'sea-prep.blob');
285
+ const configPath = path.join(work, 'sea-config.json');
218
286
 
219
- process.chdir(appDir);
220
- process.env.ATOM_PROJECT_ROOT = appDir;
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
- `;
287
+ const launcher = payloadPath
288
+ ? createEmbeddedLauncherSource(productName)
289
+ : createLooseLauncherSource();
228
290
  await fs.promises.writeFile(launcherPath, launcher, 'utf8');
229
291
 
230
292
  const seaConfig = {
@@ -232,7 +294,8 @@ load(mainPath);
232
294
  output: blobPath,
233
295
  disableExperimentalSEAWarning: true,
234
296
  useSnapshot: false,
235
- useCodeCache: false
297
+ useCodeCache: false,
298
+ ...(payloadPath ? { assets: { 'atom-app': payloadPath } } : {})
236
299
  };
237
300
  await fs.promises.writeFile(configPath, JSON.stringify(seaConfig, null, 2));
238
301
 
@@ -250,11 +313,98 @@ load(mainPath);
250
313
 
251
314
  if (target !== 'windows') await fs.promises.chmod(executablePath, 0o755);
252
315
  if (target === 'macos' && commandExists('codesign', ['--version'])) {
253
- await run('codesign', ['--sign', '-', executablePath]);
316
+ await run('codesign', ['--force', '--sign', '-', executablePath]);
254
317
  }
255
318
  await fse.remove(work);
256
319
  }
257
320
 
321
+ function createLooseLauncherSource() {
322
+ return `
323
+ 'use strict';
324
+ const path = require('node:path');
325
+ const fs = require('node:fs');
326
+ const { createRequire } = require('node:module');
327
+
328
+ const executableDir = path.dirname(process.execPath);
329
+ const appDir = path.join(executableDir, 'app');
330
+ const packagePath = path.join(appDir, 'package.json');
331
+ if (!fs.existsSync(packagePath)) {
332
+ console.error('AtomJS application files are missing:', appDir);
333
+ process.exit(1);
334
+ }
335
+
336
+ const runtimeNode = path.join(executableDir, 'runtime', process.platform === 'win32' ? 'node.exe' : 'node');
337
+ process.chdir(appDir);
338
+ process.env.ATOM_PROJECT_ROOT = appDir;
339
+ process.env.ATOM_BUILD = '1';
340
+ process.env.ATOM_NODE_EXECUTABLE = runtimeNode;
341
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
342
+ const mainPath = path.resolve(appDir, pkg.main || 'main.js');
343
+ const load = createRequire(packagePath);
344
+ load(mainPath);
345
+ `;
346
+ }
347
+
348
+ function createEmbeddedLauncherSource(productName) {
349
+ return `
350
+ 'use strict';
351
+ const path = require('node:path');
352
+ const fs = require('node:fs');
353
+ const os = require('node:os');
354
+ const crypto = require('node:crypto');
355
+ const zlib = require('node:zlib');
356
+ const { createRequire } = require('node:module');
357
+ const { getAsset } = require('node:sea');
358
+
359
+ const productName = ${JSON.stringify(productName)};
360
+ const payload = Buffer.from(getAsset('atom-app'));
361
+ const payloadHash = crypto.createHash('sha256').update(payload).digest('hex').slice(0, 24);
362
+ const dataRoot = process.platform === 'darwin'
363
+ ? path.join(os.homedir(), 'Library', 'Application Support')
364
+ : process.platform === 'win32'
365
+ ? (process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'))
366
+ : (process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'));
367
+ const appDir = path.join(dataRoot, productName, 'AtomJS Runtime', payloadHash);
368
+ const marker = path.join(appDir, '.atom-ready');
369
+
370
+ if (!fs.existsSync(marker)) {
371
+ const temporary = appDir + '.tmp-' + process.pid;
372
+ fs.rmSync(temporary, { recursive: true, force: true });
373
+ fs.mkdirSync(temporary, { recursive: true });
374
+ const archive = JSON.parse(zlib.gunzipSync(payload).toString('utf8'));
375
+ if (!archive || archive.format !== 1 || !Array.isArray(archive.files)) {
376
+ throw new Error('AtomJS embedded application payload is invalid.');
377
+ }
378
+
379
+ for (const entry of archive.files) {
380
+ const destination = path.resolve(temporary, String(entry.path));
381
+ const root = path.resolve(temporary) + path.sep;
382
+ if (!destination.startsWith(root)) throw new Error('Unsafe path in AtomJS application payload.');
383
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
384
+ fs.writeFileSync(destination, Buffer.from(entry.data, 'base64'));
385
+ if (process.platform !== 'win32' && Number.isInteger(entry.mode)) fs.chmodSync(destination, entry.mode);
386
+ }
387
+ fs.writeFileSync(path.join(temporary, '.atom-ready'), payloadHash);
388
+ fs.mkdirSync(path.dirname(appDir), { recursive: true });
389
+ fs.rmSync(appDir, { recursive: true, force: true });
390
+ fs.renameSync(temporary, appDir);
391
+ }
392
+
393
+ const packagePath = path.join(appDir, 'package.json');
394
+ if (!fs.existsSync(packagePath)) throw new Error('AtomJS could not materialize the embedded application.');
395
+
396
+ const executableDir = path.dirname(process.execPath);
397
+ process.chdir(appDir);
398
+ process.env.ATOM_PROJECT_ROOT = appDir;
399
+ process.env.ATOM_BUILD = '1';
400
+ process.env.ATOM_MACOS_HOST_EXECUTABLE = path.join(executableDir, 'AtomJSWindowHost');
401
+ const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
402
+ const mainPath = path.resolve(appDir, pkg.main || 'main.js');
403
+ const load = createRequire(packagePath);
404
+ load(mainPath);
405
+ `;
406
+ }
407
+
258
408
  async function packageWindows({ project, buildRoot, unpacked, executableName, productName }) {
259
409
  const outputs = [];
260
410
  const zipPath = path.join(buildRoot, `${productName}-windows.zip`);
@@ -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), path.join(macosDir, productName));
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);
320
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
+ }
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(project.packageJson.version || '0.0.0')}</string>
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
- await fs.promises.writeFile(path.join(contents, 'Info.plist'), plist, 'utf8');
333
- await fs.promises.chmod(path.join(macosDir, productName), 0o755);
515
+ const plistPath = path.join(contents, 'Info.plist');
516
+ await fs.promises.writeFile(plistPath, plist, 'utf8');
517
+
518
+ if (commandExists('/usr/bin/plutil', ['-help'])) {
519
+ await run('/usr/bin/plutil', ['-lint', plistPath]);
520
+ }
334
521
 
335
- if (commandExists('codesign', ['--version'])) await run('codesign', ['--force', '--deep', '--sign', '-', appBundle]);
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
- return outputs;
542
+
543
+ return { outputs, appBundle };
351
544
  }
352
545
 
353
546
  async function packageLinux({ project, buildRoot, unpacked, executableName, productName }) {
@@ -502,4 +695,4 @@ function xml(value) {
502
695
  return String(value).replace(/[<>&'\"]/g, (char) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', "'": '&apos;', '"': '&quot;' })[char]);
503
696
  }
504
697
 
505
- module.exports = { buildCommand, localBuild };
698
+ 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('osascript JavaScript host', fs.existsSync('/usr/bin/osascript')));
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, 'built in through JavaScript for Automation'));
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.2.0-alpha.0',
34
- electron: 'npm:@atom-js-org/electron@0.2.0-alpha.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.2.0-alpha.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 child = spawn(executable, [], {
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
  });