@atom-js-org/cli 0.4.3-alpha.0 → 0.4.5-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.4.3-alpha.0",
3
+ "version": "0.4.5-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
@@ -107,7 +107,7 @@ async function localBuild(project, target, options = {}) {
107
107
  }
108
108
 
109
109
  const manifest = {
110
- atomjsVersion: '0.4.3-alpha.0',
110
+ atomjsVersion: '0.4.5-alpha.0',
111
111
  target,
112
112
  productName,
113
113
  appId: project.config.appId,
@@ -570,50 +570,101 @@ function hasMacCodeSigningTool() {
570
570
  return process.platform === 'darwin' && fs.existsSync('/usr/bin/codesign');
571
571
  }
572
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
+
573
620
  async function packageMacOS({ project, buildRoot, unpacked, executableName, productName, hostSource }) {
574
621
  const outputs = [];
575
- const appBundle = path.join(buildRoot, `${productName}.app`);
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`);
576
626
  const contents = path.join(appBundle, 'Contents');
577
627
  const macosDir = path.join(contents, 'MacOS');
578
628
  const resources = path.join(contents, 'Resources');
579
629
  const mainExecutable = path.join(macosDir, productName);
580
630
  const nativeHost = path.join(macosDir, 'AtomJSWindowHost');
581
631
 
582
- await fse.ensureDir(macosDir);
583
- await fse.ensureDir(resources);
584
- await fse.copy(path.join(unpacked, executableName), mainExecutable);
585
- await fse.copy(path.join(unpacked, 'ATOMJS-CREDIT.txt'), path.join(resources, 'ATOMJS-CREDIT.txt'));
586
- await fs.promises.chmod(mainExecutable, 0o755);
587
-
588
- if (!fs.existsSync(hostSource)) {
589
- throw new Error(`AtomJS macOS native host source was not found: ${hostSource}`);
590
- }
591
- if (!commandExists('/usr/bin/xcrun', ['--version'])) {
592
- throw new Error('macOS builds require the Xcode Command Line Tools. Run `xcode-select --install`.');
593
- }
594
-
595
- await run('/usr/bin/xcrun', [
596
- 'clang',
597
- '-fobjc-arc',
598
- '-fmodules',
599
- '-mmacosx-version-min=12.0',
600
- '-framework', 'Cocoa',
601
- '-framework', 'WebKit',
602
- hostSource,
603
- '-o', nativeHost
604
- ]);
605
- 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
+ }
606
645
 
607
- let iconEntry = '';
608
- const iconSource = project.config.icon ? path.resolve(project.root, project.config.icon) : null;
609
- if (iconSource && fs.existsSync(iconSource) && path.extname(iconSource).toLowerCase() === '.icns') {
610
- await fse.copy(iconSource, path.join(resources, 'AppIcon.icns'));
611
- iconEntry = '<key>CFBundleIconFile</key><string>AppIcon</string>';
612
- }
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
+ }
613
664
 
614
- const version = String(project.packageJson.version || '0.0.0');
615
- const bundleVersion = (version.match(/\d+/g) || ['1']).slice(0, 3).join('.');
616
- const plist = `<?xml version="1.0" encoding="UTF-8"?>
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"?>
617
668
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
618
669
  <plist version="1.0"><dict>
619
670
  <key>CFBundleExecutable</key><string>${xml(productName)}</string>
@@ -627,35 +678,88 @@ async function packageMacOS({ project, buildRoot, unpacked, executableName, prod
627
678
  <key>NSHighResolutionCapable</key><true/>
628
679
  ${iconEntry}
629
680
  </dict></plist>`;
630
- const plistPath = path.join(contents, 'Info.plist');
631
- await fs.promises.writeFile(plistPath, plist, 'utf8');
681
+ const plistPath = path.join(contents, 'Info.plist');
682
+ await fs.promises.writeFile(plistPath, plist, 'utf8');
632
683
 
633
- if (commandExists('/usr/bin/plutil', ['-help'])) {
634
- await run('/usr/bin/plutil', ['-lint', plistPath]);
635
- }
684
+ if (commandExists('/usr/bin/plutil', ['-help'])) {
685
+ await run('/usr/bin/plutil', ['-lint', plistPath]);
686
+ }
636
687
 
637
- if (hasMacCodeSigningTool()) {
638
- await run('/usr/bin/codesign', ['--force', '--sign', '-', nativeHost]);
639
- await run('/usr/bin/codesign', ['--force', '--sign', '-', mainExecutable]);
640
- await run('/usr/bin/codesign', ['--force', '--deep', '--sign', '-', appBundle]);
641
- await run('/usr/bin/codesign', ['--verify', '--deep', '--strict', appBundle]);
642
- }
688
+ await sanitizeMacBundle(appBundle);
643
689
 
644
- const zipPath = path.join(buildRoot, `${productName}-macos.zip`);
645
- if (commandExists('ditto', ['-h'])) {
646
- await run('ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', appBundle, zipPath]);
647
- } else {
648
- await archiveDirectory(appBundle, zipPath, 'zip', `${productName}.app`);
649
- }
650
- 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 });
651
694
 
652
- if (commandExists('hdiutil', ['help'])) {
653
- const dmgPath = path.join(buildRoot, `${productName}.dmg`);
654
- await run('hdiutil', ['create', '-volname', productName, '-srcfolder', appBundle, '-ov', '-format', 'UDZO', dmgPath]);
655
- outputs.push(dmgPath);
656
- }
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
+ }
701
+
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('/usr/bin/hdiutil', ['help'])) {
728
+ const dmgPath = path.join(buildRoot, `${productName}.dmg`);
729
+ const temporaryDmgPath = path.join(bundleStageRoot, `${productName}.dmg`);
730
+
731
+ try {
732
+ // hdiutil can reject both source and output paths located on external,
733
+ // removable, network, or non-APFS volumes. Create the image entirely on
734
+ // the local temporary filesystem, then copy the finished DMG to build/.
735
+ await fse.remove(temporaryDmgPath);
736
+ await run('/usr/bin/hdiutil', [
737
+ 'create',
738
+ '-volname', productName,
739
+ '-srcfolder', appBundle,
740
+ '-ov',
741
+ '-format', 'UDZO',
742
+ temporaryDmgPath
743
+ ], {
744
+ env: { ...process.env, COPYFILE_DISABLE: '1' }
745
+ });
746
+
747
+ await fse.remove(dmgPath);
748
+ await fs.promises.copyFile(temporaryDmgPath, dmgPath);
749
+ outputs.push(dmgPath);
750
+ } catch (error) {
751
+ await fse.remove(temporaryDmgPath);
752
+ await fse.remove(dmgPath);
753
+
754
+ if (process.env.ATOM_REQUIRE_DMG === '1') throw error;
755
+ console.warn(`AtomJS could not create the optional macOS DMG; the signed .app and ZIP are still valid. ${error.message}`);
756
+ }
757
+ }
657
758
 
658
- return { outputs, appBundle };
759
+ return { outputs, appBundle: finalAppBundle };
760
+ } finally {
761
+ await fse.remove(bundleStageRoot);
762
+ }
659
763
  }
660
764
 
661
765
  async function packageLinux({ project, buildRoot, unpacked, executableName, productName }) {
@@ -808,4 +912,4 @@ function xml(value) {
808
912
  return String(value).replace(/[<>&'\"]/g, (char) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', "'": '&apos;', '"': '&quot;' })[char]);
809
913
  }
810
914
 
811
- module.exports = { buildCommand, localBuild, createApplicationPayload, readPortableExecutableLayout };
915
+ module.exports = { buildCommand, localBuild, createApplicationPayload, readPortableExecutableLayout, removeMacMetadataFiles };
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.3-alpha.0',
34
- electron: 'npm:@atom-js-org/electron@0.4.3-alpha.0'
33
+ '@atom-js-org/runtime': '0.4.5-alpha.0',
34
+ electron: 'npm:@atom-js-org/electron@0.4.5-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.3-alpha.0'
40
+ '@atom-js-org/cli': '0.4.5-alpha.0'
41
41
  },
42
42
  overrides: {
43
43
  tar: '7.5.20'