@atom-js-org/cli 0.4.2-alpha.0 → 0.4.4-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 +159 -67
- package/src/doctor.cjs +1 -1
- package/src/init.cjs +3 -3
- package/src/run.cjs +6 -0
package/package.json
CHANGED
package/src/build.cjs
CHANGED
|
@@ -70,6 +70,7 @@ async function localBuild(project, target, options = {}) {
|
|
|
70
70
|
appDir,
|
|
71
71
|
target,
|
|
72
72
|
productName,
|
|
73
|
+
appId: project.config.appId,
|
|
73
74
|
payloadPath
|
|
74
75
|
});
|
|
75
76
|
|
|
@@ -106,7 +107,7 @@ async function localBuild(project, target, options = {}) {
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
const manifest = {
|
|
109
|
-
atomjsVersion: '0.4.
|
|
110
|
+
atomjsVersion: '0.4.4-alpha.0',
|
|
110
111
|
target,
|
|
111
112
|
productName,
|
|
112
113
|
appId: project.config.appId,
|
|
@@ -287,7 +288,7 @@ async function createApplicationPayload(appDir, outputPath) {
|
|
|
287
288
|
await fs.promises.writeFile(outputPath, compressed);
|
|
288
289
|
}
|
|
289
290
|
|
|
290
|
-
async function createSeaLauncher({ executablePath, appDir, target, productName, payloadPath = null }) {
|
|
291
|
+
async function createSeaLauncher({ executablePath, appDir, target, productName, appId, payloadPath = null }) {
|
|
291
292
|
const work = path.join(path.dirname(executablePath), '.sea-' + crypto.randomBytes(5).toString('hex'));
|
|
292
293
|
await fse.ensureDir(work);
|
|
293
294
|
const launcherPath = path.join(work, 'launcher.cjs');
|
|
@@ -295,7 +296,7 @@ async function createSeaLauncher({ executablePath, appDir, target, productName,
|
|
|
295
296
|
const configPath = path.join(work, 'sea-config.json');
|
|
296
297
|
|
|
297
298
|
if (!payloadPath) throw new Error('AtomJS SEA payload is required.');
|
|
298
|
-
const launcher = createEmbeddedLauncherSource(productName);
|
|
299
|
+
const launcher = createEmbeddedLauncherSource(productName, appId);
|
|
299
300
|
await fs.promises.writeFile(launcherPath, launcher, 'utf8');
|
|
300
301
|
|
|
301
302
|
const seaConfig = {
|
|
@@ -314,8 +315,8 @@ async function createSeaLauncher({ executablePath, appDir, target, productName,
|
|
|
314
315
|
if (target === 'windows') {
|
|
315
316
|
await prepareWindowsExecutableForInjection(executablePath);
|
|
316
317
|
}
|
|
317
|
-
if (target === 'macos' &&
|
|
318
|
-
spawnSync('codesign', ['--remove-signature', executablePath], { stdio: 'ignore' });
|
|
318
|
+
if (target === 'macos' && hasMacCodeSigningTool()) {
|
|
319
|
+
spawnSync('/usr/bin/codesign', ['--remove-signature', executablePath], { stdio: 'ignore' });
|
|
319
320
|
}
|
|
320
321
|
|
|
321
322
|
await inject(executablePath, 'NODE_SEA_BLOB', await fs.promises.readFile(blobPath), {
|
|
@@ -328,8 +329,8 @@ async function createSeaLauncher({ executablePath, appDir, target, productName,
|
|
|
328
329
|
} else {
|
|
329
330
|
await fs.promises.chmod(executablePath, 0o755);
|
|
330
331
|
}
|
|
331
|
-
if (target === 'macos' &&
|
|
332
|
-
await run('codesign', ['--force', '--sign', '-', executablePath]);
|
|
332
|
+
if (target === 'macos' && hasMacCodeSigningTool()) {
|
|
333
|
+
await run('/usr/bin/codesign', ['--force', '--sign', '-', executablePath]);
|
|
333
334
|
}
|
|
334
335
|
await fse.remove(work);
|
|
335
336
|
}
|
|
@@ -396,7 +397,7 @@ function readPortableExecutableLayout(image) {
|
|
|
396
397
|
};
|
|
397
398
|
}
|
|
398
399
|
|
|
399
|
-
function createEmbeddedLauncherSource(productName) {
|
|
400
|
+
function createEmbeddedLauncherSource(productName, appId) {
|
|
400
401
|
return `
|
|
401
402
|
'use strict';
|
|
402
403
|
const path = require('node:path');
|
|
@@ -409,6 +410,7 @@ const { pathToFileURL } = require('node:url');
|
|
|
409
410
|
const { getAsset } = require('node:sea');
|
|
410
411
|
|
|
411
412
|
const productName = ${JSON.stringify(productName)};
|
|
413
|
+
const appId = ${JSON.stringify(appId || 'com.atomjs.app')};
|
|
412
414
|
const payload = Buffer.from(getAsset('atom-app'));
|
|
413
415
|
const payloadHash = crypto.createHash('sha256').update(payload).digest('hex').slice(0, 24);
|
|
414
416
|
const dataRoot = process.platform === 'darwin'
|
|
@@ -448,10 +450,16 @@ if (!fs.existsSync(packagePath)) throw new Error('AtomJS could not materialize t
|
|
|
448
450
|
const executableDir = path.dirname(process.execPath);
|
|
449
451
|
process.chdir(appDir);
|
|
450
452
|
process.env.ATOM_PROJECT_ROOT = appDir;
|
|
453
|
+
process.env.ATOM_APP_NAME = productName;
|
|
454
|
+
process.env.ATOM_APP_ID = appId;
|
|
451
455
|
process.env.ATOM_BUILD = '1';
|
|
452
456
|
process.env.ATOM_EMBEDDED_RUNTIME = '1';
|
|
453
457
|
process.env.ATOM_WINDOW_HOST_ENTRY = path.join(appDir, 'vendor', 'atomjs', 'src', 'runtime', 'window-host.mjs');
|
|
454
458
|
process.env.ATOM_MACOS_HOST_EXECUTABLE = path.join(executableDir, 'AtomJSWindowHost');
|
|
459
|
+
const bundledMacIcon = path.join(executableDir, '..', 'Resources', 'AppIcon.icns');
|
|
460
|
+
if (process.platform === 'darwin' && fs.existsSync(bundledMacIcon)) {
|
|
461
|
+
process.env.ATOM_APP_ICON = bundledMacIcon;
|
|
462
|
+
}
|
|
455
463
|
const hostModeIndex = process.argv.indexOf('--atomjs-window-host');
|
|
456
464
|
if (hostModeIndex !== -1) {
|
|
457
465
|
const hostEntry = process.env.ATOM_WINDOW_HOST_ENTRY;
|
|
@@ -558,50 +566,105 @@ function resolveNsisExecutable() {
|
|
|
558
566
|
return candidates.find((candidate) => fs.existsSync(candidate)) || null;
|
|
559
567
|
}
|
|
560
568
|
|
|
569
|
+
function hasMacCodeSigningTool() {
|
|
570
|
+
return process.platform === 'darwin' && fs.existsSync('/usr/bin/codesign');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async function removeMacMetadataFiles(root) {
|
|
574
|
+
const removed = [];
|
|
575
|
+
if (!root || !fs.existsSync(root)) return removed;
|
|
576
|
+
|
|
577
|
+
async function visit(directory) {
|
|
578
|
+
const entries = await fs.promises.readdir(directory, { withFileTypes: true });
|
|
579
|
+
for (const entry of entries) {
|
|
580
|
+
const absolute = path.join(directory, entry.name);
|
|
581
|
+
const isMetadata = entry.name === '.DS_Store'
|
|
582
|
+
|| entry.name === '__MACOSX'
|
|
583
|
+
|| entry.name.startsWith('._');
|
|
584
|
+
|
|
585
|
+
if (isMetadata) {
|
|
586
|
+
await fs.promises.rm(absolute, { recursive: true, force: true });
|
|
587
|
+
removed.push(absolute);
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (entry.isDirectory()) await visit(absolute);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
await visit(root);
|
|
596
|
+
return removed;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async function sanitizeMacBundle(appBundle) {
|
|
600
|
+
await removeMacMetadataFiles(appBundle);
|
|
601
|
+
|
|
602
|
+
if (process.platform === 'darwin' && fs.existsSync('/usr/bin/xattr')) {
|
|
603
|
+
const result = spawnSync('/usr/bin/xattr', ['-cr', appBundle], {
|
|
604
|
+
encoding: 'utf8',
|
|
605
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
606
|
+
});
|
|
607
|
+
if (result.error) {
|
|
608
|
+
console.warn(`AtomJS could not clear macOS extended attributes: ${result.error.message}`);
|
|
609
|
+
} else if (result.status !== 0) {
|
|
610
|
+
const detail = String(result.stderr || result.stdout || '').trim();
|
|
611
|
+
if (detail) console.warn(`AtomJS could not clear every macOS extended attribute: ${detail}`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Filesystems such as exFAT can materialize extended attributes as AppleDouble
|
|
616
|
+
// sidecars while xattr is running, so remove metadata once more afterwards.
|
|
617
|
+
await removeMacMetadataFiles(appBundle);
|
|
618
|
+
}
|
|
619
|
+
|
|
561
620
|
async function packageMacOS({ project, buildRoot, unpacked, executableName, productName, hostSource }) {
|
|
562
621
|
const outputs = [];
|
|
563
|
-
const
|
|
622
|
+
const finalAppBundle = path.join(buildRoot, `${productName}.app`);
|
|
623
|
+
const stageBase = await resolveShortStageBase();
|
|
624
|
+
const bundleStageRoot = await fs.promises.mkdtemp(path.join(stageBase, 'macos-app-'));
|
|
625
|
+
const appBundle = path.join(bundleStageRoot, `${productName}.app`);
|
|
564
626
|
const contents = path.join(appBundle, 'Contents');
|
|
565
627
|
const macosDir = path.join(contents, 'MacOS');
|
|
566
628
|
const resources = path.join(contents, 'Resources');
|
|
567
629
|
const mainExecutable = path.join(macosDir, productName);
|
|
568
630
|
const nativeHost = path.join(macosDir, 'AtomJSWindowHost');
|
|
569
631
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
await run('/usr/bin/xcrun', [
|
|
584
|
-
'clang',
|
|
585
|
-
'-fobjc-arc',
|
|
586
|
-
'-fmodules',
|
|
587
|
-
'-mmacosx-version-min=12.0',
|
|
588
|
-
'-framework', 'Cocoa',
|
|
589
|
-
'-framework', 'WebKit',
|
|
590
|
-
hostSource,
|
|
591
|
-
'-o', nativeHost
|
|
592
|
-
]);
|
|
593
|
-
await fs.promises.chmod(nativeHost, 0o755);
|
|
632
|
+
try {
|
|
633
|
+
await fse.ensureDir(macosDir);
|
|
634
|
+
await fse.ensureDir(resources);
|
|
635
|
+
await fse.copy(path.join(unpacked, executableName), mainExecutable);
|
|
636
|
+
await fse.copy(path.join(unpacked, 'ATOMJS-CREDIT.txt'), path.join(resources, 'ATOMJS-CREDIT.txt'));
|
|
637
|
+
await fs.promises.chmod(mainExecutable, 0o755);
|
|
638
|
+
|
|
639
|
+
if (!fs.existsSync(hostSource)) {
|
|
640
|
+
throw new Error(`AtomJS macOS native host source was not found: ${hostSource}`);
|
|
641
|
+
}
|
|
642
|
+
if (!commandExists('/usr/bin/xcrun', ['--version'])) {
|
|
643
|
+
throw new Error('macOS builds require the Xcode Command Line Tools. Run `xcode-select --install`.');
|
|
644
|
+
}
|
|
594
645
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
646
|
+
await run('/usr/bin/xcrun', [
|
|
647
|
+
'clang',
|
|
648
|
+
'-fobjc-arc',
|
|
649
|
+
'-fmodules',
|
|
650
|
+
'-mmacosx-version-min=12.0',
|
|
651
|
+
'-framework', 'Cocoa',
|
|
652
|
+
'-framework', 'WebKit',
|
|
653
|
+
hostSource,
|
|
654
|
+
'-o', nativeHost
|
|
655
|
+
]);
|
|
656
|
+
await fs.promises.chmod(nativeHost, 0o755);
|
|
657
|
+
|
|
658
|
+
let iconEntry = '';
|
|
659
|
+
const iconSource = project.config.icon ? path.resolve(project.root, project.config.icon) : null;
|
|
660
|
+
if (iconSource && fs.existsSync(iconSource) && path.extname(iconSource).toLowerCase() === '.icns') {
|
|
661
|
+
await fse.copy(iconSource, path.join(resources, 'AppIcon.icns'));
|
|
662
|
+
iconEntry = '<key>CFBundleIconFile</key><string>AppIcon</string>';
|
|
663
|
+
}
|
|
601
664
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
665
|
+
const version = String(project.packageJson.version || '0.0.0');
|
|
666
|
+
const bundleVersion = (version.match(/\d+/g) || ['1']).slice(0, 3).join('.');
|
|
667
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
605
668
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
606
669
|
<plist version="1.0"><dict>
|
|
607
670
|
<key>CFBundleExecutable</key><string>${xml(productName)}</string>
|
|
@@ -615,35 +678,64 @@ async function packageMacOS({ project, buildRoot, unpacked, executableName, prod
|
|
|
615
678
|
<key>NSHighResolutionCapable</key><true/>
|
|
616
679
|
${iconEntry}
|
|
617
680
|
</dict></plist>`;
|
|
618
|
-
|
|
619
|
-
|
|
681
|
+
const plistPath = path.join(contents, 'Info.plist');
|
|
682
|
+
await fs.promises.writeFile(plistPath, plist, 'utf8');
|
|
620
683
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
684
|
+
if (commandExists('/usr/bin/plutil', ['-help'])) {
|
|
685
|
+
await run('/usr/bin/plutil', ['-lint', plistPath]);
|
|
686
|
+
}
|
|
624
687
|
|
|
625
|
-
|
|
626
|
-
await run('/usr/bin/codesign', ['--force', '--sign', '-', nativeHost]);
|
|
627
|
-
await run('/usr/bin/codesign', ['--force', '--sign', '-', mainExecutable]);
|
|
628
|
-
await run('/usr/bin/codesign', ['--force', '--deep', '--sign', '-', appBundle]);
|
|
629
|
-
await run('/usr/bin/codesign', ['--verify', '--deep', '--strict', appBundle]);
|
|
630
|
-
}
|
|
688
|
+
await sanitizeMacBundle(appBundle);
|
|
631
689
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
await archiveDirectory(appBundle, zipPath, 'zip', `${productName}.app`);
|
|
637
|
-
}
|
|
638
|
-
outputs.push(zipPath);
|
|
690
|
+
if (hasMacCodeSigningTool()) {
|
|
691
|
+
const signingEnvironment = { ...process.env, COPYFILE_DISABLE: '1' };
|
|
692
|
+
await run('/usr/bin/codesign', ['--force', '--sign', '-', nativeHost], { env: signingEnvironment });
|
|
693
|
+
await run('/usr/bin/codesign', ['--force', '--sign', '-', mainExecutable], { env: signingEnvironment });
|
|
639
694
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
695
|
+
// Signing nested Mach-O files on non-APFS volumes can create fresh `._*`
|
|
696
|
+
// sidecars. They must not be present when the outer bundle is sealed.
|
|
697
|
+
await sanitizeMacBundle(appBundle);
|
|
698
|
+
await run('/usr/bin/codesign', ['--force', '--deep', '--sign', '-', appBundle], { env: signingEnvironment });
|
|
699
|
+
await run('/usr/bin/codesign', ['--verify', '--deep', '--strict', '--verbose=2', appBundle], { env: signingEnvironment });
|
|
700
|
+
}
|
|
645
701
|
|
|
646
|
-
|
|
702
|
+
// Assemble and sign on the local temporary filesystem, then copy the sealed
|
|
703
|
+
// bundle to the project output. This avoids AppleDouble metadata generated by
|
|
704
|
+
// external/exFAT project drives such as /Volumes/... during codesign.
|
|
705
|
+
await fse.remove(finalAppBundle);
|
|
706
|
+
await fse.copy(appBundle, finalAppBundle);
|
|
707
|
+
await fs.promises.chmod(path.join(finalAppBundle, 'Contents', 'MacOS', productName), 0o755);
|
|
708
|
+
await fs.promises.chmod(path.join(finalAppBundle, 'Contents', 'MacOS', 'AtomJSWindowHost'), 0o755);
|
|
709
|
+
await sanitizeMacBundle(finalAppBundle);
|
|
710
|
+
|
|
711
|
+
if (hasMacCodeSigningTool()) {
|
|
712
|
+
await run('/usr/bin/codesign', ['--verify', '--deep', '--strict', '--verbose=2', finalAppBundle], {
|
|
713
|
+
env: { ...process.env, COPYFILE_DISABLE: '1' }
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const zipPath = path.join(buildRoot, `${productName}-macos.zip`);
|
|
718
|
+
if (commandExists('ditto', ['-h'])) {
|
|
719
|
+
await run('ditto', ['--norsrc', '-c', '-k', '--keepParent', finalAppBundle, zipPath], {
|
|
720
|
+
env: { ...process.env, COPYFILE_DISABLE: '1' }
|
|
721
|
+
});
|
|
722
|
+
} else {
|
|
723
|
+
await archiveDirectory(finalAppBundle, zipPath, 'zip', `${productName}.app`);
|
|
724
|
+
}
|
|
725
|
+
outputs.push(zipPath);
|
|
726
|
+
|
|
727
|
+
if (commandExists('hdiutil', ['help'])) {
|
|
728
|
+
const dmgPath = path.join(buildRoot, `${productName}.dmg`);
|
|
729
|
+
await run('hdiutil', ['create', '-volname', productName, '-srcfolder', finalAppBundle, '-ov', '-format', 'UDZO', dmgPath], {
|
|
730
|
+
env: { ...process.env, COPYFILE_DISABLE: '1' }
|
|
731
|
+
});
|
|
732
|
+
outputs.push(dmgPath);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return { outputs, appBundle: finalAppBundle };
|
|
736
|
+
} finally {
|
|
737
|
+
await fse.remove(bundleStageRoot);
|
|
738
|
+
}
|
|
647
739
|
}
|
|
648
740
|
|
|
649
741
|
async function packageLinux({ project, buildRoot, unpacked, executableName, productName }) {
|
|
@@ -796,4 +888,4 @@ function xml(value) {
|
|
|
796
888
|
return String(value).replace(/[<>&'\"]/g, (char) => ({ '<': '<', '>': '>', '&': '&', "'": ''', '"': '"' })[char]);
|
|
797
889
|
}
|
|
798
890
|
|
|
799
|
-
module.exports = { buildCommand, localBuild, createApplicationPayload, readPortableExecutableLayout };
|
|
891
|
+
module.exports = { buildCommand, localBuild, createApplicationPayload, readPortableExecutableLayout, removeMacMetadataFiles };
|
package/src/doctor.cjs
CHANGED
|
@@ -19,7 +19,7 @@ async function doctorCommand(options = {}) {
|
|
|
19
19
|
rows.push(check('Xcode Command Line Tools', commandExists('/usr/bin/xcrun', ['--version']), 'required to compile the native Cocoa host'));
|
|
20
20
|
rows.push(check('Clang', commandExists('/usr/bin/xcrun', ['clang', '--version'])));
|
|
21
21
|
rows.push(check('WebKit framework', fs.existsSync('/System/Library/Frameworks/WebKit.framework')));
|
|
22
|
-
rows.push(check('codesign',
|
|
22
|
+
rows.push(check('codesign', fs.existsSync('/usr/bin/codesign'), '/usr/bin/codesign'));
|
|
23
23
|
rows.push(check('hdiutil', commandExists('hdiutil', ['help'])));
|
|
24
24
|
} else {
|
|
25
25
|
rows.push(check('pkg-config', commandExists('pkg-config', ['--version'])));
|
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.4.
|
|
34
|
-
electron: 'npm:@atom-js-org/electron@0.4.
|
|
33
|
+
'@atom-js-org/runtime': '0.4.4-alpha.0',
|
|
34
|
+
electron: 'npm:@atom-js-org/electron@0.4.4-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.4.
|
|
40
|
+
'@atom-js-org/cli': '0.4.4-alpha.0'
|
|
41
41
|
},
|
|
42
42
|
overrides: {
|
|
43
43
|
tar: '7.5.20'
|
package/src/run.cjs
CHANGED
|
@@ -23,11 +23,17 @@ async function runDev(options) {
|
|
|
23
23
|
|
|
24
24
|
await ensureElectronCompatibility(project.root);
|
|
25
25
|
|
|
26
|
+
const iconPath = project.config.icon
|
|
27
|
+
? path.resolve(project.root, project.config.icon)
|
|
28
|
+
: null;
|
|
26
29
|
const child = spawn(process.execPath, [mainPath], {
|
|
27
30
|
cwd: project.root,
|
|
28
31
|
env: {
|
|
29
32
|
...process.env,
|
|
30
33
|
ATOM_PROJECT_ROOT: project.root,
|
|
34
|
+
ATOM_APP_NAME: project.config.productName,
|
|
35
|
+
ATOM_APP_ID: project.config.appId,
|
|
36
|
+
...(iconPath && fs.existsSync(iconPath) ? { ATOM_APP_ICON: iconPath } : {}),
|
|
31
37
|
ATOM_DEV: '1'
|
|
32
38
|
},
|
|
33
39
|
stdio: 'inherit'
|