@atom-js-org/cli 0.4.5-alpha.0 → 0.5.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/README.md +3 -1
- package/assets/default-icon.ico +0 -0
- package/assets/default-icon.png +0 -0
- package/package.json +4 -2
- package/src/build.cjs +537 -81
- package/src/init.cjs +37 -4
- package/src/utils.cjs +68 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AtomJS CLI
|
|
2
2
|
|
|
3
|
-
Run and
|
|
3
|
+
Run and package AtomJS applications:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
atom run dev
|
|
@@ -11,4 +11,6 @@ atom build linux
|
|
|
11
11
|
atom build all
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
`atom.config.json` controls artifact names, Windows EXE/NSIS branding, macOS app/DMG metadata and Linux AppImage/DEB/RPM output. New projects include default PNG and ICO assets that can be replaced directly.
|
|
15
|
+
|
|
14
16
|
Project: https://github.com/Atom-js-org/atom
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atom-js-org/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0-alpha.0",
|
|
4
4
|
"description": "CLI for running and building AtomJS desktop applications.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"atom": "bin/atom.cjs"
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"bin/",
|
|
11
11
|
"src/",
|
|
12
|
+
"assets/",
|
|
12
13
|
"README.md",
|
|
13
14
|
"LICENSE"
|
|
14
15
|
],
|
|
@@ -19,7 +20,8 @@
|
|
|
19
20
|
"archiver": "7.0.1",
|
|
20
21
|
"commander": "^15.0.0",
|
|
21
22
|
"fs-extra": "^11.3.6",
|
|
22
|
-
"postject": "1.0.0-alpha.6"
|
|
23
|
+
"postject": "1.0.0-alpha.6",
|
|
24
|
+
"rcedit": "4.0.1"
|
|
23
25
|
},
|
|
24
26
|
"repository": {
|
|
25
27
|
"type": "git",
|
package/src/build.cjs
CHANGED
|
@@ -71,6 +71,7 @@ async function localBuild(project, target, options = {}) {
|
|
|
71
71
|
target,
|
|
72
72
|
productName,
|
|
73
73
|
appId: project.config.appId,
|
|
74
|
+
project,
|
|
74
75
|
payloadPath
|
|
75
76
|
});
|
|
76
77
|
|
|
@@ -107,7 +108,7 @@ async function localBuild(project, target, options = {}) {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
const manifest = {
|
|
110
|
-
atomjsVersion: '0.
|
|
111
|
+
atomjsVersion: '0.5.0-alpha.0',
|
|
111
112
|
target,
|
|
112
113
|
productName,
|
|
113
114
|
appId: project.config.appId,
|
|
@@ -288,7 +289,7 @@ async function createApplicationPayload(appDir, outputPath) {
|
|
|
288
289
|
await fs.promises.writeFile(outputPath, compressed);
|
|
289
290
|
}
|
|
290
291
|
|
|
291
|
-
async function createSeaLauncher({ executablePath, appDir, target, productName, appId, payloadPath = null }) {
|
|
292
|
+
async function createSeaLauncher({ executablePath, appDir, target, productName, appId, project = null, payloadPath = null }) {
|
|
292
293
|
const work = path.join(path.dirname(executablePath), '.sea-' + crypto.randomBytes(5).toString('hex'));
|
|
293
294
|
await fse.ensureDir(work);
|
|
294
295
|
const launcherPath = path.join(work, 'launcher.cjs');
|
|
@@ -313,6 +314,8 @@ async function createSeaLauncher({ executablePath, appDir, target, productName,
|
|
|
313
314
|
await fs.promises.copyFile(process.execPath, executablePath);
|
|
314
315
|
|
|
315
316
|
if (target === 'windows') {
|
|
317
|
+
if (!project) throw new Error('AtomJS requires project metadata to customize a Windows executable.');
|
|
318
|
+
await customizeWindowsExecutable({ project, executablePath, productName });
|
|
316
319
|
await prepareWindowsExecutableForInjection(executablePath);
|
|
317
320
|
}
|
|
318
321
|
if (target === 'macos' && hasMacCodeSigningTool()) {
|
|
@@ -478,66 +481,326 @@ load(mainPath);
|
|
|
478
481
|
`;
|
|
479
482
|
}
|
|
480
483
|
|
|
484
|
+
|
|
485
|
+
function normalizeWindowsVersion(value) {
|
|
486
|
+
const parts = String(value || '0.0.0').match(/\d+/g) || ['0'];
|
|
487
|
+
return [...parts.slice(0, 4), '0', '0', '0', '0'].slice(0, 4).join('.');
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function resolveProjectAsset(project, configured, expectedExtension = null) {
|
|
491
|
+
if (!configured) return null;
|
|
492
|
+
const resolved = path.resolve(project.root, configured);
|
|
493
|
+
if (!fs.existsSync(resolved)) {
|
|
494
|
+
throw new Error(`AtomJS build asset was not found: ${resolved}`);
|
|
495
|
+
}
|
|
496
|
+
if (expectedExtension && path.extname(resolved).toLowerCase() !== expectedExtension.toLowerCase()) {
|
|
497
|
+
throw new Error(`AtomJS expected a ${expectedExtension} asset: ${resolved}`);
|
|
498
|
+
}
|
|
499
|
+
return resolved;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function platformArchitecture() {
|
|
503
|
+
if (process.arch === 'x64') return 'x64';
|
|
504
|
+
if (process.arch === 'arm64') return 'arm64';
|
|
505
|
+
if (process.arch === 'ia32') return 'ia32';
|
|
506
|
+
return process.arch;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function renderArtifactBase(project, target, templateOverride = null) {
|
|
510
|
+
const template = templateOverride || project.config.build.artifactName;
|
|
511
|
+
const variables = {
|
|
512
|
+
productName: project.config.productName,
|
|
513
|
+
version: String(project.packageJson.version || '0.0.0'),
|
|
514
|
+
target,
|
|
515
|
+
arch: platformArchitecture(),
|
|
516
|
+
appId: project.config.appId
|
|
517
|
+
};
|
|
518
|
+
const rendered = String(template || '${productName}-${version}-${target}-${arch}')
|
|
519
|
+
.replace(/\$\{(productName|version|target|arch|appId)\}/g, (_, key) => variables[key]);
|
|
520
|
+
return sanitizeFilename(rendered);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function packageAuthor(project) {
|
|
524
|
+
const author = project.packageJson.author;
|
|
525
|
+
if (typeof author === 'string' && author.trim()) return author.trim();
|
|
526
|
+
if (author && typeof author === 'object') {
|
|
527
|
+
const name = String(author.name || '').trim();
|
|
528
|
+
const email = String(author.email || '').trim();
|
|
529
|
+
if (name && email) return `${name} <${email}>`;
|
|
530
|
+
if (name) return name;
|
|
531
|
+
}
|
|
532
|
+
return 'AtomJS application';
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function normalizeDebVersion(value) {
|
|
536
|
+
const normalized = String(value || '0.0.0')
|
|
537
|
+
.replace(/[^0-9A-Za-z.+:~\-]/g, '-')
|
|
538
|
+
.replace(/^-+/, '');
|
|
539
|
+
return normalized || '0.0.0';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function debArchitecture() {
|
|
543
|
+
if (process.arch === 'x64') return 'amd64';
|
|
544
|
+
if (process.arch === 'arm64') return 'arm64';
|
|
545
|
+
if (process.arch === 'ia32') return 'i386';
|
|
546
|
+
return process.arch;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function normalizeRpmVersion(value) {
|
|
550
|
+
const normalized = String(value || '0.0.0')
|
|
551
|
+
.replace(/[^0-9A-Za-z.+~]/g, '.')
|
|
552
|
+
.replace(/^\.+|\.+$/g, '');
|
|
553
|
+
return normalized || '0.0.0';
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function rpmArchitecture() {
|
|
557
|
+
if (process.arch === 'x64') return 'x86_64';
|
|
558
|
+
if (process.arch === 'arm64') return 'aarch64';
|
|
559
|
+
if (process.arch === 'ia32') return 'i686';
|
|
560
|
+
return process.arch;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function rpmText(value) {
|
|
564
|
+
return String(value || '').replace(/%/g, '%%').replace(/\r?\n/g, ' ').trim();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async function writeArArchive(outputPath, members) {
|
|
568
|
+
const chunks = [Buffer.from('!<arch>\n', 'ascii')];
|
|
569
|
+
for (const member of members) {
|
|
570
|
+
const data = Buffer.isBuffer(member.data) ? member.data : Buffer.from(member.data);
|
|
571
|
+
const name = `${String(member.name).slice(0, 15)}/`.padEnd(16, ' ');
|
|
572
|
+
const timestamp = String(Math.floor(Date.now() / 1000)).padEnd(12, ' ');
|
|
573
|
+
const owner = '0'.padEnd(6, ' ');
|
|
574
|
+
const group = '0'.padEnd(6, ' ');
|
|
575
|
+
const mode = '100644'.padEnd(8, ' ');
|
|
576
|
+
const size = String(data.length).padEnd(10, ' ');
|
|
577
|
+
const header = Buffer.from(`${name}${timestamp}${owner}${group}${mode}${size}\x60\n`, 'ascii');
|
|
578
|
+
chunks.push(header, data);
|
|
579
|
+
if (data.length % 2 !== 0) chunks.push(Buffer.from('\n'));
|
|
580
|
+
}
|
|
581
|
+
await fs.promises.writeFile(outputPath, Buffer.concat(chunks));
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function prepareMacIcon(project, resources, workRoot) {
|
|
585
|
+
const configured = project.config.build.macos.icon;
|
|
586
|
+
if (!configured) return null;
|
|
587
|
+
const source = resolveProjectAsset(project, configured);
|
|
588
|
+
const extension = path.extname(source).toLowerCase();
|
|
589
|
+
const destination = path.join(resources, 'AppIcon.icns');
|
|
590
|
+
|
|
591
|
+
if (extension === '.icns') {
|
|
592
|
+
await fse.copy(source, destination);
|
|
593
|
+
return destination;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (extension !== '.png') {
|
|
597
|
+
throw new Error(`AtomJS macOS icons must be .icns or .png: ${source}`);
|
|
598
|
+
}
|
|
599
|
+
if (!commandExists('/usr/bin/sips', ['--help']) || !commandExists('/usr/bin/iconutil', ['--help'])) {
|
|
600
|
+
throw new Error('Converting a PNG macOS icon requires the system sips and iconutil tools.');
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const iconset = path.join(workRoot, 'AppIcon.iconset');
|
|
604
|
+
await fse.remove(iconset);
|
|
605
|
+
await fse.ensureDir(iconset);
|
|
606
|
+
const entries = [
|
|
607
|
+
[16, 'icon_16x16.png'],
|
|
608
|
+
[32, 'icon_16x16@2x.png'],
|
|
609
|
+
[32, 'icon_32x32.png'],
|
|
610
|
+
[64, 'icon_32x32@2x.png'],
|
|
611
|
+
[128, 'icon_128x128.png'],
|
|
612
|
+
[256, 'icon_128x128@2x.png'],
|
|
613
|
+
[256, 'icon_256x256.png'],
|
|
614
|
+
[512, 'icon_256x256@2x.png'],
|
|
615
|
+
[512, 'icon_512x512.png'],
|
|
616
|
+
[1024, 'icon_512x512@2x.png']
|
|
617
|
+
];
|
|
618
|
+
for (const [size, filename] of entries) {
|
|
619
|
+
await run('/usr/bin/sips', ['-z', String(size), String(size), source, '--out', path.join(iconset, filename)]);
|
|
620
|
+
}
|
|
621
|
+
await run('/usr/bin/iconutil', ['-c', 'icns', iconset, '-o', destination]);
|
|
622
|
+
return destination;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function codesignArguments(project, targetPath, deep = false) {
|
|
626
|
+
const config = project.config.build.macos;
|
|
627
|
+
const args = ['--force'];
|
|
628
|
+
if (deep) args.push('--deep');
|
|
629
|
+
if (config.hardenedRuntime) args.push('--options', 'runtime');
|
|
630
|
+
const entitlements = config.entitlements ? resolveProjectAsset(project, config.entitlements) : null;
|
|
631
|
+
if (entitlements) args.push('--entitlements', entitlements);
|
|
632
|
+
args.push('--sign', config.signingIdentity || '-', targetPath);
|
|
633
|
+
return args;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async function customizeWindowsExecutable({ project, executablePath, productName }) {
|
|
637
|
+
const config = project.config.build.windows;
|
|
638
|
+
let rceditModule;
|
|
639
|
+
try {
|
|
640
|
+
rceditModule = require('rcedit');
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.warn(`AtomJS could not load rcedit, so Windows executable metadata was not customized: ${error.message}`);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const edit = rceditModule.rcedit || rceditModule.default || rceditModule;
|
|
647
|
+
const version = normalizeWindowsVersion(project.packageJson.version);
|
|
648
|
+
const author = project.packageJson.author;
|
|
649
|
+
const packagePublisher = typeof author === 'string'
|
|
650
|
+
? author
|
|
651
|
+
: author && typeof author === 'object' && author.name
|
|
652
|
+
? author.name
|
|
653
|
+
: 'AtomJS application';
|
|
654
|
+
const publisher = config.publisher || packagePublisher;
|
|
655
|
+
const iconSource = resolveProjectAsset(project, config.icon, '.ico');
|
|
656
|
+
const options = {
|
|
657
|
+
'file-version': version,
|
|
658
|
+
'product-version': version,
|
|
659
|
+
'requested-execution-level': config.requestedExecutionLevel,
|
|
660
|
+
'version-string': {
|
|
661
|
+
CompanyName: publisher,
|
|
662
|
+
FileDescription: productName,
|
|
663
|
+
ProductName: productName,
|
|
664
|
+
InternalName: productName,
|
|
665
|
+
OriginalFilename: path.basename(executablePath),
|
|
666
|
+
LegalCopyright: typeof project.packageJson.license === 'string' ? project.packageJson.license : ''
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
if (iconSource) options.icon = iconSource;
|
|
670
|
+
|
|
671
|
+
try {
|
|
672
|
+
await new Promise((resolve, reject) => {
|
|
673
|
+
let settled = false;
|
|
674
|
+
const finish = (error) => {
|
|
675
|
+
if (settled) return;
|
|
676
|
+
settled = true;
|
|
677
|
+
if (error) reject(error);
|
|
678
|
+
else resolve();
|
|
679
|
+
};
|
|
680
|
+
let result;
|
|
681
|
+
try {
|
|
682
|
+
result = edit(executablePath, options, finish);
|
|
683
|
+
} catch (error) {
|
|
684
|
+
finish(error);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (result && typeof result.then === 'function') result.then(() => finish(), finish);
|
|
688
|
+
});
|
|
689
|
+
} catch (error) {
|
|
690
|
+
throw new Error(`AtomJS could not customize the Windows executable: ${error.message}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
481
694
|
async function packageWindows({ project, buildRoot, unpacked, executableName, productName }) {
|
|
482
695
|
const outputs = [];
|
|
483
|
-
const
|
|
696
|
+
const config = project.config.build.windows;
|
|
697
|
+
const artifactBase = renderArtifactBase(project, 'windows');
|
|
698
|
+
const zipPath = path.join(buildRoot, `${artifactBase}-portable.zip`);
|
|
484
699
|
await archiveDirectory(unpacked, zipPath, 'zip');
|
|
485
700
|
outputs.push(zipPath);
|
|
486
701
|
|
|
487
702
|
const nsisPath = path.join(buildRoot, 'installer.nsi');
|
|
488
|
-
const installerPath = path.join(buildRoot, `${
|
|
703
|
+
const installerPath = path.join(buildRoot, `${artifactBase}-setup.exe`);
|
|
489
704
|
const escapedSource = unpacked.replace(/\\/g, '\\\\');
|
|
490
705
|
const version = String(project.packageJson.version || '0.0.0');
|
|
491
706
|
const author = project.packageJson.author;
|
|
492
|
-
const
|
|
707
|
+
const packagePublisher = typeof author === 'string'
|
|
493
708
|
? author
|
|
494
709
|
: author && typeof author === 'object' && author.name
|
|
495
710
|
? author.name
|
|
496
711
|
: 'AtomJS application';
|
|
712
|
+
const publisher = config.publisher || packagePublisher;
|
|
497
713
|
const uninstallKey = `Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${escapeNsis(project.config.appId)}`;
|
|
714
|
+
const installerIcon = resolveProjectAsset(project, config.installerIcon, '.ico');
|
|
715
|
+
const headerImage = resolveProjectAsset(project, config.headerImage, '.bmp');
|
|
716
|
+
const sidebarImage = resolveProjectAsset(project, config.sidebarImage, '.bmp');
|
|
717
|
+
const installMode = config.installMode === 'machine' ? 'machine' : 'user';
|
|
718
|
+
const requestLevel = installMode === 'machine' ? 'admin' : 'user';
|
|
719
|
+
const defaultInstallDir = installMode === 'machine'
|
|
720
|
+
? `$PROGRAMFILES64\\${escapeNsis(productName)}`
|
|
721
|
+
: `$LOCALAPPDATA\\Programs\\${escapeNsis(productName)}`;
|
|
722
|
+
const installDir = config.installDirectory
|
|
723
|
+
? escapeNsis(config.installDirectory)
|
|
724
|
+
: defaultInstallDir;
|
|
725
|
+
const registryRoot = installMode === 'machine' ? 'HKLM' : 'HKCU';
|
|
726
|
+
const shellContext = installMode === 'machine' ? 'all' : 'current';
|
|
498
727
|
const credit = project.config.installerCredit
|
|
499
728
|
? `!define MUI_WELCOMEPAGE_TEXT "This installer will install ${escapeNsis(productName)}.$\\r$\\n$\\r$\\nPowered by AtomJS — https://github.com/Atom-js-org/atom"`
|
|
500
729
|
: '';
|
|
730
|
+
const welcomeText = config.welcomeText
|
|
731
|
+
? `!define MUI_WELCOMEPAGE_TEXT "${escapeNsis(config.welcomeText)}"`
|
|
732
|
+
: credit;
|
|
733
|
+
const finishText = config.finishText
|
|
734
|
+
? `!define MUI_FINISHPAGE_TEXT "${escapeNsis(config.finishText)}"`
|
|
735
|
+
: '';
|
|
736
|
+
const iconDirectives = installerIcon
|
|
737
|
+
? `Icon "${installerIcon.replace(/\\/g, '\\\\')}"\n!define MUI_ICON "${installerIcon.replace(/\\/g, '\\\\')}"\n!define MUI_UNICON "${installerIcon.replace(/\\/g, '\\\\')}"`
|
|
738
|
+
: '';
|
|
739
|
+
const headerDirectives = headerImage
|
|
740
|
+
? `!define MUI_HEADERIMAGE\n!define MUI_HEADERIMAGE_BITMAP "${headerImage.replace(/\\/g, '\\\\')}"`
|
|
741
|
+
: '';
|
|
742
|
+
const sidebarDirectives = sidebarImage
|
|
743
|
+
? `!define MUI_WELCOMEFINISHPAGE_BITMAP "${sidebarImage.replace(/\\/g, '\\\\')}"`
|
|
744
|
+
: '';
|
|
745
|
+
const directoryPage = config.allowDirectorySelection ? '!insertmacro MUI_PAGE_DIRECTORY' : '';
|
|
746
|
+
const finishRun = config.runAfterFinish
|
|
747
|
+
? `!define MUI_FINISHPAGE_RUN "$INSTDIR\\${escapeNsis(executableName)}"`
|
|
748
|
+
: '';
|
|
749
|
+
const desktopShortcut = config.createDesktopShortcut
|
|
750
|
+
? `CreateShortcut "$DESKTOP\\${escapeNsis(productName)}.lnk" "$INSTDIR\\${escapeNsis(executableName)}"`
|
|
751
|
+
: '';
|
|
752
|
+
const startMenuInstall = config.createStartMenuShortcut
|
|
753
|
+
? `CreateDirectory "$SMPROGRAMS\\${escapeNsis(productName)}"\n CreateShortcut "$SMPROGRAMS\\${escapeNsis(productName)}\\${escapeNsis(productName)}.lnk" "$INSTDIR\\${escapeNsis(executableName)}"`
|
|
754
|
+
: '';
|
|
755
|
+
const startMenuRemove = config.createStartMenuShortcut
|
|
756
|
+
? `RMDir /r "$SMPROGRAMS\\${escapeNsis(productName)}"`
|
|
757
|
+
: '';
|
|
758
|
+
const desktopRemove = config.createDesktopShortcut
|
|
759
|
+
? `Delete "$DESKTOP\\${escapeNsis(productName)}.lnk"`
|
|
760
|
+
: '';
|
|
501
761
|
const script = `
|
|
502
762
|
Unicode true
|
|
503
763
|
SetCompressor /SOLID lzma
|
|
504
764
|
!include "MUI2.nsh"
|
|
505
765
|
Name "${escapeNsis(productName)}"
|
|
506
766
|
OutFile "${installerPath.replace(/\\/g, '\\\\')}"
|
|
507
|
-
InstallDir "$
|
|
508
|
-
RequestExecutionLevel
|
|
767
|
+
InstallDir "${installDir}"
|
|
768
|
+
RequestExecutionLevel ${requestLevel}
|
|
509
769
|
ShowInstDetails show
|
|
510
770
|
ShowUninstDetails show
|
|
511
|
-
${
|
|
771
|
+
${iconDirectives}
|
|
772
|
+
${headerDirectives}
|
|
773
|
+
${sidebarDirectives}
|
|
774
|
+
${welcomeText}
|
|
775
|
+
${finishText}
|
|
512
776
|
!insertmacro MUI_PAGE_WELCOME
|
|
513
|
-
|
|
777
|
+
${directoryPage}
|
|
514
778
|
!insertmacro MUI_PAGE_INSTFILES
|
|
515
|
-
|
|
779
|
+
${finishRun}
|
|
516
780
|
!insertmacro MUI_PAGE_FINISH
|
|
517
781
|
!insertmacro MUI_UNPAGE_CONFIRM
|
|
518
782
|
!insertmacro MUI_UNPAGE_INSTFILES
|
|
519
|
-
!insertmacro MUI_LANGUAGE "
|
|
783
|
+
!insertmacro MUI_LANGUAGE "${escapeNsis(config.language)}"
|
|
520
784
|
Section "Install"
|
|
521
|
-
SetShellVarContext
|
|
785
|
+
SetShellVarContext ${shellContext}
|
|
522
786
|
SetOutPath "$INSTDIR"
|
|
523
787
|
File /r "${escapedSource}\\*"
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
CreateShortcut "$DESKTOP\\${escapeNsis(productName)}.lnk" "$INSTDIR\\${escapeNsis(executableName)}"
|
|
788
|
+
${startMenuInstall}
|
|
789
|
+
${desktopShortcut}
|
|
527
790
|
WriteUninstaller "$INSTDIR\\Uninstall.exe"
|
|
528
|
-
WriteRegStr
|
|
529
|
-
WriteRegStr
|
|
530
|
-
WriteRegStr
|
|
531
|
-
WriteRegStr
|
|
532
|
-
WriteRegStr
|
|
533
|
-
WriteRegDWORD
|
|
534
|
-
WriteRegDWORD
|
|
791
|
+
WriteRegStr ${registryRoot} "${uninstallKey}" "DisplayName" "${escapeNsis(productName)}"
|
|
792
|
+
WriteRegStr ${registryRoot} "${uninstallKey}" "DisplayVersion" "${escapeNsis(version)}"
|
|
793
|
+
WriteRegStr ${registryRoot} "${uninstallKey}" "Publisher" "${escapeNsis(publisher)}"
|
|
794
|
+
WriteRegStr ${registryRoot} "${uninstallKey}" "DisplayIcon" "$INSTDIR\\${escapeNsis(executableName)}"
|
|
795
|
+
WriteRegStr ${registryRoot} "${uninstallKey}" "UninstallString" "$\\\"$INSTDIR\\Uninstall.exe$\\\""
|
|
796
|
+
WriteRegDWORD ${registryRoot} "${uninstallKey}" "NoModify" 1
|
|
797
|
+
WriteRegDWORD ${registryRoot} "${uninstallKey}" "NoRepair" 1
|
|
535
798
|
SectionEnd
|
|
536
799
|
Section "Uninstall"
|
|
537
|
-
SetShellVarContext
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
DeleteRegKey
|
|
800
|
+
SetShellVarContext ${shellContext}
|
|
801
|
+
${desktopRemove}
|
|
802
|
+
${startMenuRemove}
|
|
803
|
+
DeleteRegKey ${registryRoot} "${uninstallKey}"
|
|
541
804
|
RMDir /r "$INSTDIR"
|
|
542
805
|
SectionEnd
|
|
543
806
|
`;
|
|
@@ -619,10 +882,12 @@ async function sanitizeMacBundle(appBundle) {
|
|
|
619
882
|
|
|
620
883
|
async function packageMacOS({ project, buildRoot, unpacked, executableName, productName, hostSource }) {
|
|
621
884
|
const outputs = [];
|
|
622
|
-
const
|
|
885
|
+
const config = project.config.build.macos;
|
|
886
|
+
const bundleName = sanitizeFilename(config.bundleName || productName);
|
|
887
|
+
const finalAppBundle = path.join(buildRoot, `${bundleName}.app`);
|
|
623
888
|
const stageBase = await resolveShortStageBase();
|
|
624
889
|
const bundleStageRoot = await fs.promises.mkdtemp(path.join(stageBase, 'macos-app-'));
|
|
625
|
-
const appBundle = path.join(bundleStageRoot, `${
|
|
890
|
+
const appBundle = path.join(bundleStageRoot, `${bundleName}.app`);
|
|
626
891
|
const contents = path.join(appBundle, 'Contents');
|
|
627
892
|
const macosDir = path.join(contents, 'MacOS');
|
|
628
893
|
const resources = path.join(contents, 'Resources');
|
|
@@ -647,7 +912,7 @@ async function packageMacOS({ project, buildRoot, unpacked, executableName, prod
|
|
|
647
912
|
'clang',
|
|
648
913
|
'-fobjc-arc',
|
|
649
914
|
'-fmodules',
|
|
650
|
-
|
|
915
|
+
`-mmacosx-version-min=${config.minimumSystemVersion}`,
|
|
651
916
|
'-framework', 'Cocoa',
|
|
652
917
|
'-framework', 'WebKit',
|
|
653
918
|
hostSource,
|
|
@@ -655,12 +920,16 @@ async function packageMacOS({ project, buildRoot, unpacked, executableName, prod
|
|
|
655
920
|
]);
|
|
656
921
|
await fs.promises.chmod(nativeHost, 0o755);
|
|
657
922
|
|
|
658
|
-
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
923
|
+
const preparedIcon = await prepareMacIcon(project, resources, bundleStageRoot);
|
|
924
|
+
const iconEntry = preparedIcon
|
|
925
|
+
? '<key>CFBundleIconFile</key><string>AppIcon</string>'
|
|
926
|
+
: '';
|
|
927
|
+
const categoryEntry = config.category
|
|
928
|
+
? `<key>LSApplicationCategoryType</key><string>${xml(config.category)}</string>`
|
|
929
|
+
: '';
|
|
930
|
+
const copyrightEntry = config.copyright
|
|
931
|
+
? `<key>NSHumanReadableCopyright</key><string>${xml(config.copyright)}</string>`
|
|
932
|
+
: '';
|
|
664
933
|
|
|
665
934
|
const version = String(project.packageJson.version || '0.0.0');
|
|
666
935
|
const bundleVersion = (version.match(/\d+/g) || ['1']).slice(0, 3).join('.');
|
|
@@ -669,13 +938,15 @@ async function packageMacOS({ project, buildRoot, unpacked, executableName, prod
|
|
|
669
938
|
<plist version="1.0"><dict>
|
|
670
939
|
<key>CFBundleExecutable</key><string>${xml(productName)}</string>
|
|
671
940
|
<key>CFBundleIdentifier</key><string>${xml(project.config.appId)}</string>
|
|
672
|
-
<key>CFBundleName</key><string>${xml(
|
|
941
|
+
<key>CFBundleName</key><string>${xml(bundleName)}</string>
|
|
673
942
|
<key>CFBundleDisplayName</key><string>${xml(productName)}</string>
|
|
674
943
|
<key>CFBundlePackageType</key><string>APPL</string>
|
|
675
944
|
<key>CFBundleShortVersionString</key><string>${xml(version)}</string>
|
|
676
945
|
<key>CFBundleVersion</key><string>${xml(bundleVersion)}</string>
|
|
677
|
-
<key>LSMinimumSystemVersion</key><string
|
|
946
|
+
<key>LSMinimumSystemVersion</key><string>${xml(config.minimumSystemVersion)}</string>
|
|
678
947
|
<key>NSHighResolutionCapable</key><true/>
|
|
948
|
+
${categoryEntry}
|
|
949
|
+
${copyrightEntry}
|
|
679
950
|
${iconEntry}
|
|
680
951
|
</dict></plist>`;
|
|
681
952
|
const plistPath = path.join(contents, 'Info.plist');
|
|
@@ -689,19 +960,13 @@ ${iconEntry}
|
|
|
689
960
|
|
|
690
961
|
if (hasMacCodeSigningTool()) {
|
|
691
962
|
const signingEnvironment = { ...process.env, COPYFILE_DISABLE: '1' };
|
|
692
|
-
await run('/usr/bin/codesign',
|
|
693
|
-
await run('/usr/bin/codesign',
|
|
694
|
-
|
|
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.
|
|
963
|
+
await run('/usr/bin/codesign', codesignArguments(project, nativeHost), { env: signingEnvironment });
|
|
964
|
+
await run('/usr/bin/codesign', codesignArguments(project, mainExecutable), { env: signingEnvironment });
|
|
697
965
|
await sanitizeMacBundle(appBundle);
|
|
698
|
-
await run('/usr/bin/codesign',
|
|
966
|
+
await run('/usr/bin/codesign', codesignArguments(project, appBundle, true), { env: signingEnvironment });
|
|
699
967
|
await run('/usr/bin/codesign', ['--verify', '--deep', '--strict', '--verbose=2', appBundle], { env: signingEnvironment });
|
|
700
968
|
}
|
|
701
969
|
|
|
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
970
|
await fse.remove(finalAppBundle);
|
|
706
971
|
await fse.copy(appBundle, finalAppBundle);
|
|
707
972
|
await fs.promises.chmod(path.join(finalAppBundle, 'Contents', 'MacOS', productName), 0o755);
|
|
@@ -714,35 +979,44 @@ ${iconEntry}
|
|
|
714
979
|
});
|
|
715
980
|
}
|
|
716
981
|
|
|
717
|
-
const
|
|
982
|
+
const artifactBase = renderArtifactBase(project, 'macos');
|
|
983
|
+
const zipPath = path.join(buildRoot, `${artifactBase}.zip`);
|
|
718
984
|
if (commandExists('ditto', ['-h'])) {
|
|
719
985
|
await run('ditto', ['--norsrc', '-c', '-k', '--keepParent', finalAppBundle, zipPath], {
|
|
720
986
|
env: { ...process.env, COPYFILE_DISABLE: '1' }
|
|
721
987
|
});
|
|
722
988
|
} else {
|
|
723
|
-
await archiveDirectory(finalAppBundle, zipPath, 'zip', `${
|
|
989
|
+
await archiveDirectory(finalAppBundle, zipPath, 'zip', `${bundleName}.app`);
|
|
724
990
|
}
|
|
725
991
|
outputs.push(zipPath);
|
|
726
992
|
|
|
727
|
-
if (commandExists('/usr/bin/hdiutil', ['help'])) {
|
|
728
|
-
const
|
|
729
|
-
const
|
|
993
|
+
if (config.dmg.enabled && commandExists('/usr/bin/hdiutil', ['help'])) {
|
|
994
|
+
const dmgBase = renderArtifactBase(project, 'macos', config.dmg.artifactName || `${artifactBase}-installer`);
|
|
995
|
+
const dmgPath = path.join(buildRoot, `${dmgBase}.dmg`);
|
|
996
|
+
const temporaryDmgPath = path.join(bundleStageRoot, `${dmgBase}.dmg`);
|
|
997
|
+
const dmgRoot = path.join(bundleStageRoot, 'dmg-root');
|
|
730
998
|
|
|
731
999
|
try {
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
1000
|
+
await fse.remove(dmgRoot);
|
|
1001
|
+
await fse.ensureDir(dmgRoot);
|
|
1002
|
+
await fse.copy(appBundle, path.join(dmgRoot, `${bundleName}.app`));
|
|
1003
|
+
await fs.promises.symlink('/Applications', path.join(dmgRoot, 'Applications'));
|
|
1004
|
+
if (config.dmg.background) {
|
|
1005
|
+
const background = resolveProjectAsset(project, config.dmg.background);
|
|
1006
|
+
const backgroundDirectory = path.join(dmgRoot, '.background');
|
|
1007
|
+
await fse.ensureDir(backgroundDirectory);
|
|
1008
|
+
await fse.copy(background, path.join(backgroundDirectory, path.basename(background)));
|
|
1009
|
+
}
|
|
1010
|
+
await sanitizeMacBundle(dmgRoot);
|
|
735
1011
|
await fse.remove(temporaryDmgPath);
|
|
736
1012
|
await run('/usr/bin/hdiutil', [
|
|
737
1013
|
'create',
|
|
738
|
-
'-volname',
|
|
739
|
-
'-srcfolder',
|
|
1014
|
+
'-volname', config.dmg.volumeName,
|
|
1015
|
+
'-srcfolder', dmgRoot,
|
|
740
1016
|
'-ov',
|
|
741
1017
|
'-format', 'UDZO',
|
|
742
1018
|
temporaryDmgPath
|
|
743
|
-
], {
|
|
744
|
-
env: { ...process.env, COPYFILE_DISABLE: '1' }
|
|
745
|
-
});
|
|
1019
|
+
], { env: { ...process.env, COPYFILE_DISABLE: '1' } });
|
|
746
1020
|
|
|
747
1021
|
await fse.remove(dmgPath);
|
|
748
1022
|
await fs.promises.copyFile(temporaryDmgPath, dmgPath);
|
|
@@ -750,7 +1024,6 @@ ${iconEntry}
|
|
|
750
1024
|
} catch (error) {
|
|
751
1025
|
await fse.remove(temporaryDmgPath);
|
|
752
1026
|
await fse.remove(dmgPath);
|
|
753
|
-
|
|
754
1027
|
if (process.env.ATOM_REQUIRE_DMG === '1') throw error;
|
|
755
1028
|
console.warn(`AtomJS could not create the optional macOS DMG; the signed .app and ZIP are still valid. ${error.message}`);
|
|
756
1029
|
}
|
|
@@ -764,40 +1037,223 @@ ${iconEntry}
|
|
|
764
1037
|
|
|
765
1038
|
async function packageLinux({ project, buildRoot, unpacked, executableName, productName }) {
|
|
766
1039
|
const outputs = [];
|
|
767
|
-
const
|
|
768
|
-
|
|
1040
|
+
const config = project.config.build.linux;
|
|
1041
|
+
const artifactBase = renderArtifactBase(project, 'linux');
|
|
1042
|
+
const binaryName = sanitizeFilename(config.binaryName).replace(/\s+/g, '-');
|
|
1043
|
+
const packageName = String(config.packageName).toLowerCase().replace(/[^a-z0-9+.-]+/g, '-') || 'atomjs-app';
|
|
1044
|
+
const sourceExecutable = path.join(unpacked, executableName);
|
|
1045
|
+
|
|
1046
|
+
const portableBinary = path.join(buildRoot, binaryName);
|
|
1047
|
+
await fse.copy(sourceExecutable, portableBinary);
|
|
1048
|
+
await fs.promises.chmod(portableBinary, 0o755);
|
|
1049
|
+
outputs.push(portableBinary);
|
|
1050
|
+
|
|
1051
|
+
const tarPath = path.join(buildRoot, `${artifactBase}-portable.tar.gz`);
|
|
1052
|
+
await archiveDirectory(unpacked, tarPath, 'tar', productName);
|
|
769
1053
|
outputs.push(tarPath);
|
|
770
1054
|
|
|
771
1055
|
const appDir = path.join(buildRoot, `${productName}.AppDir`);
|
|
772
1056
|
const usrBin = path.join(appDir, 'usr', 'bin');
|
|
773
|
-
const
|
|
1057
|
+
const applicationsDir = path.join(appDir, 'usr', 'share', 'applications');
|
|
1058
|
+
const iconsDir = path.join(appDir, 'usr', 'share', 'icons', 'hicolor', '512x512', 'apps');
|
|
1059
|
+
const docsDir = path.join(appDir, 'usr', 'share', 'doc', packageName);
|
|
1060
|
+
await fse.remove(appDir);
|
|
774
1061
|
await fse.ensureDir(usrBin);
|
|
775
|
-
await fse.ensureDir(
|
|
776
|
-
await fse.
|
|
777
|
-
await fse.copy(
|
|
1062
|
+
await fse.ensureDir(applicationsDir);
|
|
1063
|
+
await fse.ensureDir(docsDir);
|
|
1064
|
+
await fse.copy(sourceExecutable, path.join(usrBin, binaryName));
|
|
1065
|
+
await fs.promises.chmod(path.join(usrBin, binaryName), 0o755);
|
|
1066
|
+
await fse.copy(path.join(unpacked, 'ATOMJS-CREDIT.txt'), path.join(docsDir, 'ATOMJS-CREDIT.txt'));
|
|
778
1067
|
|
|
779
|
-
const appRun = `#!/bin/sh\nHERE="$(dirname "$(readlink -f "$0")")"\nexec "$HERE/usr/bin/${
|
|
1068
|
+
const appRun = `#!/bin/sh\nHERE="$(dirname "$(readlink -f "$0")")"\nexec "$HERE/usr/bin/${binaryName}" "$@"\n`;
|
|
780
1069
|
await fs.promises.writeFile(path.join(appDir, 'AppRun'), appRun, { mode: 0o755 });
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
await
|
|
1070
|
+
const desktop = `[Desktop Entry]\nType=Application\nName=${productName}\nComment=${config.description}\nExec=${binaryName}\nIcon=${packageName}\nCategories=${config.category};\nTerminal=false\nStartupNotify=true\n`;
|
|
1071
|
+
await fs.promises.writeFile(path.join(applicationsDir, `${packageName}.desktop`), desktop, 'utf8');
|
|
1072
|
+
await fse.copy(path.join(applicationsDir, `${packageName}.desktop`), path.join(appDir, `${packageName}.desktop`));
|
|
1073
|
+
|
|
1074
|
+
const iconSource = config.icon ? resolveProjectAsset(project, config.icon) : null;
|
|
1075
|
+
if (iconSource) {
|
|
1076
|
+
if (path.extname(iconSource).toLowerCase() !== '.png') {
|
|
1077
|
+
throw new Error(`AtomJS Linux icons must be PNG files: ${iconSource}`);
|
|
1078
|
+
}
|
|
1079
|
+
await fse.ensureDir(iconsDir);
|
|
1080
|
+
await fse.copy(iconSource, path.join(iconsDir, `${packageName}.png`));
|
|
1081
|
+
await fse.copy(iconSource, path.join(appDir, `${packageName}.png`));
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
if (config.appImage) {
|
|
1085
|
+
const tool = findAppImageTool();
|
|
1086
|
+
if (tool) {
|
|
1087
|
+
const appImagePath = path.join(buildRoot, `${artifactBase}.AppImage`);
|
|
1088
|
+
await run(tool, [appDir, appImagePath], {
|
|
1089
|
+
env: { ...process.env, ARCH: process.arch === 'arm64' ? 'aarch64' : 'x86_64' }
|
|
1090
|
+
});
|
|
1091
|
+
outputs.push(appImagePath);
|
|
1092
|
+
} else {
|
|
1093
|
+
console.warn('appimagetool was not found; the portable binary, AppDir and .deb are still available.');
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
784
1096
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1097
|
+
if (config.deb) {
|
|
1098
|
+
const debStage = path.join(buildRoot, '.deb-stage');
|
|
1099
|
+
const controlRoot = path.join(debStage, 'control');
|
|
1100
|
+
const dataRoot = path.join(debStage, 'data');
|
|
1101
|
+
await fse.remove(debStage);
|
|
1102
|
+
await fse.ensureDir(controlRoot);
|
|
1103
|
+
await fse.ensureDir(path.join(dataRoot, 'usr', 'bin'));
|
|
1104
|
+
await fse.ensureDir(path.join(dataRoot, 'usr', 'share', 'applications'));
|
|
1105
|
+
await fse.ensureDir(path.join(dataRoot, 'usr', 'share', 'doc', packageName));
|
|
1106
|
+
|
|
1107
|
+
await fse.copy(sourceExecutable, path.join(dataRoot, 'usr', 'bin', binaryName));
|
|
1108
|
+
await fs.promises.chmod(path.join(dataRoot, 'usr', 'bin', binaryName), 0o755);
|
|
1109
|
+
await fse.copy(
|
|
1110
|
+
path.join(applicationsDir, `${packageName}.desktop`),
|
|
1111
|
+
path.join(dataRoot, 'usr', 'share', 'applications', `${packageName}.desktop`)
|
|
1112
|
+
);
|
|
1113
|
+
await fse.copy(
|
|
1114
|
+
path.join(unpacked, 'ATOMJS-CREDIT.txt'),
|
|
1115
|
+
path.join(dataRoot, 'usr', 'share', 'doc', packageName, 'ATOMJS-CREDIT.txt')
|
|
1116
|
+
);
|
|
1117
|
+
if (iconSource) {
|
|
1118
|
+
const debIconDir = path.join(dataRoot, 'usr', 'share', 'icons', 'hicolor', '512x512', 'apps');
|
|
1119
|
+
await fse.ensureDir(debIconDir);
|
|
1120
|
+
await fse.copy(iconSource, path.join(debIconDir, `${packageName}.png`));
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const dependencies = config.dependencies.length > 0 ? `\nDepends: ${config.dependencies.join(', ')}` : '';
|
|
1124
|
+
const control = [
|
|
1125
|
+
`Package: ${packageName}`,
|
|
1126
|
+
`Version: ${normalizeDebVersion(project.packageJson.version)}`,
|
|
1127
|
+
'Section: utils',
|
|
1128
|
+
'Priority: optional',
|
|
1129
|
+
`Architecture: ${debArchitecture()}`,
|
|
1130
|
+
`Maintainer: ${config.maintainer || packageAuthor(project)}`,
|
|
1131
|
+
`Description: ${String(config.description).replace(/\r?\n/g, '\n ')}`
|
|
1132
|
+
].join('\n') + dependencies + '\n';
|
|
1133
|
+
await fs.promises.writeFile(path.join(controlRoot, 'control'), control, 'utf8');
|
|
1134
|
+
|
|
1135
|
+
const controlTar = path.join(debStage, 'control.tar.gz');
|
|
1136
|
+
const dataTar = path.join(debStage, 'data.tar.gz');
|
|
1137
|
+
await archiveDirectory(controlRoot, controlTar, 'tar');
|
|
1138
|
+
await archiveDirectory(dataRoot, dataTar, 'tar');
|
|
1139
|
+
const debPath = path.join(buildRoot, `${artifactBase}.deb`);
|
|
1140
|
+
await writeArArchive(debPath, [
|
|
1141
|
+
{ name: 'debian-binary', data: Buffer.from('2.0\n') },
|
|
1142
|
+
{ name: 'control.tar.gz', data: await fs.promises.readFile(controlTar) },
|
|
1143
|
+
{ name: 'data.tar.gz', data: await fs.promises.readFile(dataTar) }
|
|
1144
|
+
]);
|
|
1145
|
+
outputs.push(debPath);
|
|
1146
|
+
await fse.remove(debStage);
|
|
788
1147
|
}
|
|
789
1148
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1149
|
+
if (config.rpm) {
|
|
1150
|
+
if (commandExists('rpmbuild', ['--version'])) {
|
|
1151
|
+
const rpmStage = path.join(buildRoot, '.rpm-stage');
|
|
1152
|
+
const rpmTop = path.join(rpmStage, 'rpmbuild');
|
|
1153
|
+
const rpmSourceRoot = path.join(rpmStage, 'payload');
|
|
1154
|
+
const rpmSpecPath = path.join(rpmTop, 'SPECS', `${packageName}.spec`);
|
|
1155
|
+
const rpmSourcePath = path.join(rpmTop, 'SOURCES', `${packageName}-payload.tar.gz`);
|
|
1156
|
+
const rpmBinaryPath = path.join(rpmSourceRoot, 'usr', 'bin', binaryName);
|
|
1157
|
+
const rpmDesktopPath = path.join(rpmSourceRoot, 'usr', 'share', 'applications', `${packageName}.desktop`);
|
|
1158
|
+
const rpmDocsPath = path.join(rpmSourceRoot, 'usr', 'share', 'doc', packageName, 'ATOMJS-CREDIT.txt');
|
|
1159
|
+
|
|
1160
|
+
await fse.remove(rpmStage);
|
|
1161
|
+
for (const directory of ['BUILD', 'BUILDROOT', 'RPMS', 'SOURCES', 'SPECS', 'SRPMS']) {
|
|
1162
|
+
await fse.ensureDir(path.join(rpmTop, directory));
|
|
1163
|
+
}
|
|
1164
|
+
await fse.ensureDir(path.dirname(rpmBinaryPath));
|
|
1165
|
+
await fse.ensureDir(path.dirname(rpmDesktopPath));
|
|
1166
|
+
await fse.ensureDir(path.dirname(rpmDocsPath));
|
|
1167
|
+
await fse.copy(sourceExecutable, rpmBinaryPath);
|
|
1168
|
+
await fs.promises.chmod(rpmBinaryPath, 0o755);
|
|
1169
|
+
await fse.copy(path.join(applicationsDir, `${packageName}.desktop`), rpmDesktopPath);
|
|
1170
|
+
await fse.copy(path.join(unpacked, 'ATOMJS-CREDIT.txt'), rpmDocsPath);
|
|
1171
|
+
|
|
1172
|
+
const rpmFiles = [
|
|
1173
|
+
`/usr/bin/${binaryName}`,
|
|
1174
|
+
`/usr/share/applications/${packageName}.desktop`,
|
|
1175
|
+
`/usr/share/doc/${packageName}/ATOMJS-CREDIT.txt`
|
|
1176
|
+
];
|
|
1177
|
+
if (iconSource) {
|
|
1178
|
+
const rpmIconPath = path.join(rpmSourceRoot, 'usr', 'share', 'icons', 'hicolor', '512x512', 'apps', `${packageName}.png`);
|
|
1179
|
+
await fse.ensureDir(path.dirname(rpmIconPath));
|
|
1180
|
+
await fse.copy(iconSource, rpmIconPath);
|
|
1181
|
+
rpmFiles.push(`/usr/share/icons/hicolor/512x512/apps/${packageName}.png`);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
await archiveDirectory(rpmSourceRoot, rpmSourcePath, 'tar');
|
|
1185
|
+
const rpmRequires = config.rpmDependencies.length > 0
|
|
1186
|
+
? `Requires: ${config.rpmDependencies.join(', ')}`
|
|
1187
|
+
: '';
|
|
1188
|
+
const rpmSpec = [
|
|
1189
|
+
`Name: ${packageName}`,
|
|
1190
|
+
`Version: ${normalizeRpmVersion(project.packageJson.version)}`,
|
|
1191
|
+
'Release: 1%{?dist}',
|
|
1192
|
+
`Summary: ${rpmText(config.description || productName)}`,
|
|
1193
|
+
`License: ${rpmText(project.packageJson.license || 'Proprietary')}`,
|
|
1194
|
+
`BuildArch: ${rpmArchitecture()}`,
|
|
1195
|
+
'Source0: ' + path.basename(rpmSourcePath),
|
|
1196
|
+
'AutoReqProv: no',
|
|
1197
|
+
rpmRequires,
|
|
1198
|
+
'',
|
|
1199
|
+
'%description',
|
|
1200
|
+
rpmText(config.description || productName),
|
|
1201
|
+
'',
|
|
1202
|
+
'%prep',
|
|
1203
|
+
'',
|
|
1204
|
+
'%build',
|
|
1205
|
+
'',
|
|
1206
|
+
'%install',
|
|
1207
|
+
'rm -rf %{buildroot}',
|
|
1208
|
+
'mkdir -p %{buildroot}',
|
|
1209
|
+
'tar -xzf %{SOURCE0} -C %{buildroot}',
|
|
1210
|
+
'',
|
|
1211
|
+
'%files',
|
|
1212
|
+
...rpmFiles,
|
|
1213
|
+
''
|
|
1214
|
+
].filter((line, index, lines) => line !== '' || lines[index - 1] !== '').join('\n');
|
|
1215
|
+
await fs.promises.writeFile(rpmSpecPath, rpmSpec, 'utf8');
|
|
1216
|
+
|
|
1217
|
+
await run('rpmbuild', ['--define', `_topdir ${rpmTop}`, '-bb', rpmSpecPath]);
|
|
1218
|
+
const rpmCandidates = [];
|
|
1219
|
+
async function findRpmFiles(directory) {
|
|
1220
|
+
for (const entry of await fs.promises.readdir(directory, { withFileTypes: true })) {
|
|
1221
|
+
const absolute = path.join(directory, entry.name);
|
|
1222
|
+
if (entry.isDirectory()) await findRpmFiles(absolute);
|
|
1223
|
+
else if (entry.isFile() && entry.name.endsWith('.rpm')) rpmCandidates.push(absolute);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
await findRpmFiles(path.join(rpmTop, 'RPMS'));
|
|
1227
|
+
if (rpmCandidates.length === 0) throw new Error('rpmbuild completed without producing an RPM package.');
|
|
1228
|
+
const rpmPath = path.join(buildRoot, `${artifactBase}.rpm`);
|
|
1229
|
+
await fse.copy(rpmCandidates[0], rpmPath);
|
|
1230
|
+
outputs.push(rpmPath);
|
|
1231
|
+
await fse.remove(rpmStage);
|
|
1232
|
+
} else {
|
|
1233
|
+
console.warn('rpmbuild was not found; the portable binary, AppImage and .deb remain available.');
|
|
1234
|
+
}
|
|
797
1235
|
}
|
|
1236
|
+
|
|
1237
|
+
const distro = detectLinuxDistribution();
|
|
1238
|
+
if (distro) console.log(`Linux packaging host: ${distro}`);
|
|
798
1239
|
return outputs;
|
|
799
1240
|
}
|
|
800
1241
|
|
|
1242
|
+
function detectLinuxDistribution() {
|
|
1243
|
+
if (process.platform !== 'linux' || !fs.existsSync('/etc/os-release')) return null;
|
|
1244
|
+
try {
|
|
1245
|
+
const values = {};
|
|
1246
|
+
for (const line of fs.readFileSync('/etc/os-release', 'utf8').split(/\r?\n/)) {
|
|
1247
|
+
const match = line.match(/^([A-Z_]+)=(.*)$/);
|
|
1248
|
+
if (!match) continue;
|
|
1249
|
+
values[match[1]] = match[2].replace(/^"|"$/g, '');
|
|
1250
|
+
}
|
|
1251
|
+
return values.PRETTY_NAME || values.NAME || values.ID || null;
|
|
1252
|
+
} catch {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
801
1257
|
function findAppImageTool() {
|
|
802
1258
|
for (const name of ['appimagetool', 'appimagetool.AppImage']) {
|
|
803
1259
|
if (commandExists(name, ['--version'])) return name;
|
|
@@ -912,4 +1368,4 @@ function xml(value) {
|
|
|
912
1368
|
return String(value).replace(/[<>&'\"]/g, (char) => ({ '<': '<', '>': '>', '&': '&', "'": ''', '"': '"' })[char]);
|
|
913
1369
|
}
|
|
914
1370
|
|
|
915
|
-
module.exports = { buildCommand, localBuild, createApplicationPayload, readPortableExecutableLayout, removeMacMetadataFiles };
|
|
1371
|
+
module.exports = { buildCommand, localBuild, createApplicationPayload, readPortableExecutableLayout, removeMacMetadataFiles, writeArArchive, normalizeDebVersion, normalizeRpmVersion };
|
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.5.0-alpha.0',
|
|
34
|
+
electron: 'npm:@atom-js-org/electron@0.5.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.5.0-alpha.0'
|
|
41
41
|
},
|
|
42
42
|
overrides: {
|
|
43
43
|
tar: '7.5.20'
|
|
@@ -50,12 +50,45 @@ async function initCommand(directory, options = {}) {
|
|
|
50
50
|
main: 'src/main.js',
|
|
51
51
|
icon: 'assets/icon.png',
|
|
52
52
|
installerCredit: true,
|
|
53
|
+
build: {
|
|
54
|
+
artifactName: '${productName}-${version}-${target}-${arch}',
|
|
55
|
+
windows: {
|
|
56
|
+
icon: 'assets/icon.ico',
|
|
57
|
+
installerIcon: 'assets/icon.ico',
|
|
58
|
+
installMode: 'user',
|
|
59
|
+
createDesktopShortcut: true,
|
|
60
|
+
createStartMenuShortcut: true,
|
|
61
|
+
allowDirectorySelection: true,
|
|
62
|
+
runAfterFinish: true
|
|
63
|
+
},
|
|
64
|
+
macos: {
|
|
65
|
+
icon: 'assets/icon.png',
|
|
66
|
+
bundleName: productName,
|
|
67
|
+
category: 'public.app-category.utilities',
|
|
68
|
+
minimumSystemVersion: '12.0',
|
|
69
|
+
dmg: {
|
|
70
|
+
enabled: true,
|
|
71
|
+
volumeName: productName
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
linux: {
|
|
75
|
+
icon: 'assets/icon.png',
|
|
76
|
+
binaryName: packageName,
|
|
77
|
+
packageName,
|
|
78
|
+
category: 'Utility',
|
|
79
|
+
appImage: true,
|
|
80
|
+
deb: true,
|
|
81
|
+
rpm: true
|
|
82
|
+
}
|
|
83
|
+
},
|
|
53
84
|
github: {
|
|
54
85
|
workflow: 'atom-build.yml'
|
|
55
86
|
}
|
|
56
87
|
}, null, 2));
|
|
57
88
|
|
|
58
89
|
await fse.ensureDir(path.join(root, 'assets'));
|
|
90
|
+
await fse.copy(path.join(__dirname, '..', 'assets', 'default-icon.png'), path.join(root, 'assets', 'icon.png'));
|
|
91
|
+
await fse.copy(path.join(__dirname, '..', 'assets', 'default-icon.ico'), path.join(root, 'assets', 'icon.ico'));
|
|
59
92
|
await fs.promises.writeFile(path.join(root, 'src', 'main.js'), mainTemplate());
|
|
60
93
|
await fs.promises.writeFile(path.join(root, 'src', 'preload.js'), preloadTemplate());
|
|
61
94
|
await fs.promises.writeFile(path.join(root, 'src', 'index.html'), htmlTemplate(productName));
|
|
@@ -213,7 +246,7 @@ jobs:
|
|
|
213
246
|
runs-on: ubuntu-latest
|
|
214
247
|
steps:
|
|
215
248
|
- uses: actions/checkout@v4
|
|
216
|
-
- run: sudo apt-get update && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev zenity
|
|
249
|
+
- run: sudo apt-get update && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev zenity rpm
|
|
217
250
|
- uses: actions/setup-node@v4
|
|
218
251
|
with:
|
|
219
252
|
node-version: 22
|
package/src/utils.cjs
CHANGED
|
@@ -38,22 +38,88 @@ function loadProject(projectInput) {
|
|
|
38
38
|
const packageJson = readJson(packagePath);
|
|
39
39
|
const configPath = path.join(root, 'atom.config.json');
|
|
40
40
|
const config = fs.existsSync(configPath) ? readJson(configPath) : {};
|
|
41
|
+
const build = objectOrEmpty(config.build);
|
|
42
|
+
const windows = objectOrEmpty(build.windows);
|
|
43
|
+
const macos = objectOrEmpty(build.macos);
|
|
44
|
+
const linux = objectOrEmpty(build.linux);
|
|
45
|
+
const dmg = objectOrEmpty(macos.dmg);
|
|
46
|
+
const productName = config.productName || packageJson.productName || packageJson.name || 'AtomJS App';
|
|
47
|
+
const icon = config.icon || null;
|
|
41
48
|
|
|
42
49
|
return {
|
|
43
50
|
root,
|
|
44
51
|
packageJson,
|
|
45
52
|
config: {
|
|
46
53
|
appId: config.appId || `com.atomjs.${sanitizeId(packageJson.name || 'app')}`,
|
|
47
|
-
productName
|
|
54
|
+
productName,
|
|
48
55
|
main: config.main || packageJson.main || 'main.js',
|
|
49
|
-
icon
|
|
56
|
+
icon,
|
|
50
57
|
files: config.files || ['**/*'],
|
|
51
58
|
installerCredit: config.installerCredit !== false,
|
|
59
|
+
build: {
|
|
60
|
+
artifactName: build.artifactName || '${productName}-${version}-${target}-${arch}',
|
|
61
|
+
windows: {
|
|
62
|
+
icon: windows.icon || icon,
|
|
63
|
+
installerIcon: windows.installerIcon || windows.icon || icon,
|
|
64
|
+
headerImage: windows.headerImage || null,
|
|
65
|
+
sidebarImage: windows.sidebarImage || null,
|
|
66
|
+
language: windows.language || 'English',
|
|
67
|
+
installMode: windows.installMode === 'machine' ? 'machine' : 'user',
|
|
68
|
+
installDirectory: windows.installDirectory || null,
|
|
69
|
+
createDesktopShortcut: windows.createDesktopShortcut !== false,
|
|
70
|
+
createStartMenuShortcut: windows.createStartMenuShortcut !== false,
|
|
71
|
+
allowDirectorySelection: windows.allowDirectorySelection !== false,
|
|
72
|
+
runAfterFinish: windows.runAfterFinish !== false,
|
|
73
|
+
welcomeText: windows.welcomeText || null,
|
|
74
|
+
finishText: windows.finishText || null,
|
|
75
|
+
publisher: windows.publisher || null,
|
|
76
|
+
requestedExecutionLevel: ['asInvoker', 'highestAvailable', 'requireAdministrator'].includes(windows.requestedExecutionLevel)
|
|
77
|
+
? windows.requestedExecutionLevel
|
|
78
|
+
: 'asInvoker'
|
|
79
|
+
},
|
|
80
|
+
macos: {
|
|
81
|
+
icon: macos.icon || icon,
|
|
82
|
+
bundleName: macos.bundleName || productName,
|
|
83
|
+
category: macos.category || 'public.app-category.utilities',
|
|
84
|
+
minimumSystemVersion: macos.minimumSystemVersion || '12.0',
|
|
85
|
+
copyright: macos.copyright || null,
|
|
86
|
+
signingIdentity: macos.signingIdentity || '-',
|
|
87
|
+
entitlements: macos.entitlements || null,
|
|
88
|
+
hardenedRuntime: macos.hardenedRuntime === true,
|
|
89
|
+
dmg: {
|
|
90
|
+
enabled: dmg.enabled !== false,
|
|
91
|
+
artifactName: dmg.artifactName || null,
|
|
92
|
+
volumeName: dmg.volumeName || productName,
|
|
93
|
+
background: dmg.background || null
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
linux: {
|
|
97
|
+
icon: linux.icon || icon,
|
|
98
|
+
binaryName: linux.binaryName || sanitizeFilename(packageJson.name || productName).toLowerCase().replace(/\s+/g, '-'),
|
|
99
|
+
packageName: linux.packageName || sanitizeId(packageJson.name || productName),
|
|
100
|
+
category: linux.category || 'Utility',
|
|
101
|
+
maintainer: linux.maintainer || null,
|
|
102
|
+
description: linux.description || packageJson.description || productName,
|
|
103
|
+
dependencies: Array.isArray(linux.dependencies)
|
|
104
|
+
? linux.dependencies.map(String)
|
|
105
|
+
: ['libgtk-3-0', 'libwebkit2gtk-4.1-0'],
|
|
106
|
+
rpmDependencies: Array.isArray(linux.rpmDependencies)
|
|
107
|
+
? linux.rpmDependencies.map(String)
|
|
108
|
+
: ['gtk3', 'webkit2gtk4.1'],
|
|
109
|
+
appImage: linux.appImage !== false,
|
|
110
|
+
deb: linux.deb !== false,
|
|
111
|
+
rpm: linux.rpm !== false
|
|
112
|
+
}
|
|
113
|
+
},
|
|
52
114
|
github: config.github || {}
|
|
53
115
|
}
|
|
54
116
|
};
|
|
55
117
|
}
|
|
56
118
|
|
|
119
|
+
function objectOrEmpty(value) {
|
|
120
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
121
|
+
}
|
|
122
|
+
|
|
57
123
|
function normalizeSpawn(command, args) {
|
|
58
124
|
if (process.platform === 'win32' && /\.(?:cmd|bat)$/i.test(command)) {
|
|
59
125
|
return { command: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', command, ...args] };
|