@atom-js-org/cli 0.4.0-alpha.0 → 0.4.1-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 +94 -4
- package/src/doctor.cjs +35 -9
package/package.json
CHANGED
package/src/build.cjs
CHANGED
|
@@ -311,6 +311,9 @@ async function createSeaLauncher({ executablePath, appDir, target, productName,
|
|
|
311
311
|
await run(process.execPath, ['--experimental-sea-config', configPath], { cwd: work });
|
|
312
312
|
await fs.promises.copyFile(process.execPath, executablePath);
|
|
313
313
|
|
|
314
|
+
if (target === 'windows') {
|
|
315
|
+
await prepareWindowsExecutableForInjection(executablePath);
|
|
316
|
+
}
|
|
314
317
|
if (target === 'macos' && commandExists('codesign', ['--version'])) {
|
|
315
318
|
spawnSync('codesign', ['--remove-signature', executablePath], { stdio: 'ignore' });
|
|
316
319
|
}
|
|
@@ -320,13 +323,66 @@ async function createSeaLauncher({ executablePath, appDir, target, productName,
|
|
|
320
323
|
machoSegmentName: 'NODE_SEA'
|
|
321
324
|
});
|
|
322
325
|
|
|
323
|
-
if (target
|
|
326
|
+
if (target === 'windows') {
|
|
327
|
+
await setWindowsGuiSubsystem(executablePath);
|
|
328
|
+
} else {
|
|
329
|
+
await fs.promises.chmod(executablePath, 0o755);
|
|
330
|
+
}
|
|
324
331
|
if (target === 'macos' && commandExists('codesign', ['--version'])) {
|
|
325
332
|
await run('codesign', ['--force', '--sign', '-', executablePath]);
|
|
326
333
|
}
|
|
327
334
|
await fse.remove(work);
|
|
328
335
|
}
|
|
329
336
|
|
|
337
|
+
|
|
338
|
+
async function prepareWindowsExecutableForInjection(executablePath) {
|
|
339
|
+
const image = await fs.promises.readFile(executablePath);
|
|
340
|
+
const pe = readPortableExecutableLayout(image);
|
|
341
|
+
const certificateDirectory = pe.dataDirectoryOffset + (8 * 4);
|
|
342
|
+
const certificateOffset = image.readUInt32LE(certificateDirectory);
|
|
343
|
+
const certificateSize = image.readUInt32LE(certificateDirectory + 4);
|
|
344
|
+
|
|
345
|
+
image.writeUInt32LE(0, certificateDirectory);
|
|
346
|
+
image.writeUInt32LE(0, certificateDirectory + 4);
|
|
347
|
+
image.writeUInt32LE(0, pe.optionalHeaderOffset + 64);
|
|
348
|
+
|
|
349
|
+
const certificateEnd = certificateOffset + certificateSize;
|
|
350
|
+
const output = certificateOffset > 0 && certificateSize > 0 && certificateEnd === image.length
|
|
351
|
+
? image.subarray(0, certificateOffset)
|
|
352
|
+
: image;
|
|
353
|
+
await fs.promises.writeFile(executablePath, output);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function setWindowsGuiSubsystem(executablePath) {
|
|
357
|
+
const image = await fs.promises.readFile(executablePath);
|
|
358
|
+
const pe = readPortableExecutableLayout(image);
|
|
359
|
+
image.writeUInt16LE(2, pe.optionalHeaderOffset + 68);
|
|
360
|
+
image.writeUInt32LE(0, pe.optionalHeaderOffset + 64);
|
|
361
|
+
await fs.promises.writeFile(executablePath, image);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function readPortableExecutableLayout(image) {
|
|
365
|
+
if (image.length < 0x100 || image.toString('ascii', 0, 2) !== 'MZ') {
|
|
366
|
+
throw new Error('AtomJS expected a Windows PE executable but the DOS header is missing.');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const peOffset = image.readUInt32LE(0x3c);
|
|
370
|
+
if (peOffset < 0x40 || peOffset + 0x100 > image.length || image.toString('ascii', peOffset, peOffset + 4) !== 'PE\\0\\0') {
|
|
371
|
+
throw new Error('AtomJS expected a Windows PE executable but the PE header is invalid.');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const optionalHeaderOffset = peOffset + 24;
|
|
375
|
+
const magic = image.readUInt16LE(optionalHeaderOffset);
|
|
376
|
+
if (magic !== 0x10b && magic !== 0x20b) {
|
|
377
|
+
throw new Error(`AtomJS does not support Windows PE optional-header magic 0x${magic.toString(16)}.`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
optionalHeaderOffset,
|
|
382
|
+
dataDirectoryOffset: optionalHeaderOffset + (magic === 0x20b ? 112 : 96)
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
330
386
|
function createEmbeddedLauncherSource(productName) {
|
|
331
387
|
return `
|
|
332
388
|
'use strict';
|
|
@@ -410,6 +466,14 @@ async function packageWindows({ project, buildRoot, unpacked, executableName, pr
|
|
|
410
466
|
const nsisPath = path.join(buildRoot, 'installer.nsi');
|
|
411
467
|
const installerPath = path.join(buildRoot, `${productName} Installer.exe`);
|
|
412
468
|
const escapedSource = unpacked.replace(/\\/g, '\\\\');
|
|
469
|
+
const version = String(project.packageJson.version || '0.0.0');
|
|
470
|
+
const author = project.packageJson.author;
|
|
471
|
+
const publisher = typeof author === 'string'
|
|
472
|
+
? author
|
|
473
|
+
: author && typeof author === 'object' && author.name
|
|
474
|
+
? author.name
|
|
475
|
+
: 'AtomJS application';
|
|
476
|
+
const uninstallKey = `Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${escapeNsis(project.config.appId)}`;
|
|
413
477
|
const credit = project.config.installerCredit
|
|
414
478
|
? `!define MUI_WELCOMEPAGE_TEXT "This installer will install ${escapeNsis(productName)}.$\\r$\\n$\\r$\\nPowered by AtomJS — https://github.com/Atom-js-org/atom"`
|
|
415
479
|
: '';
|
|
@@ -427,27 +491,40 @@ ${credit}
|
|
|
427
491
|
!insertmacro MUI_PAGE_WELCOME
|
|
428
492
|
!insertmacro MUI_PAGE_DIRECTORY
|
|
429
493
|
!insertmacro MUI_PAGE_INSTFILES
|
|
494
|
+
!define MUI_FINISHPAGE_RUN "$INSTDIR\\${escapeNsis(executableName)}"
|
|
430
495
|
!insertmacro MUI_PAGE_FINISH
|
|
496
|
+
!insertmacro MUI_UNPAGE_CONFIRM
|
|
497
|
+
!insertmacro MUI_UNPAGE_INSTFILES
|
|
431
498
|
!insertmacro MUI_LANGUAGE "English"
|
|
432
499
|
Section "Install"
|
|
500
|
+
SetShellVarContext current
|
|
433
501
|
SetOutPath "$INSTDIR"
|
|
434
502
|
File /r "${escapedSource}\\*"
|
|
435
503
|
CreateDirectory "$SMPROGRAMS\\${escapeNsis(productName)}"
|
|
436
504
|
CreateShortcut "$SMPROGRAMS\\${escapeNsis(productName)}\\${escapeNsis(productName)}.lnk" "$INSTDIR\\${escapeNsis(executableName)}"
|
|
437
505
|
CreateShortcut "$DESKTOP\\${escapeNsis(productName)}.lnk" "$INSTDIR\\${escapeNsis(executableName)}"
|
|
438
506
|
WriteUninstaller "$INSTDIR\\Uninstall.exe"
|
|
507
|
+
WriteRegStr HKCU "${uninstallKey}" "DisplayName" "${escapeNsis(productName)}"
|
|
508
|
+
WriteRegStr HKCU "${uninstallKey}" "DisplayVersion" "${escapeNsis(version)}"
|
|
509
|
+
WriteRegStr HKCU "${uninstallKey}" "Publisher" "${escapeNsis(publisher)}"
|
|
510
|
+
WriteRegStr HKCU "${uninstallKey}" "DisplayIcon" "$INSTDIR\\${escapeNsis(executableName)}"
|
|
511
|
+
WriteRegStr HKCU "${uninstallKey}" "UninstallString" "$\\\"$INSTDIR\\Uninstall.exe$\\\""
|
|
512
|
+
WriteRegDWORD HKCU "${uninstallKey}" "NoModify" 1
|
|
513
|
+
WriteRegDWORD HKCU "${uninstallKey}" "NoRepair" 1
|
|
439
514
|
SectionEnd
|
|
440
515
|
Section "Uninstall"
|
|
516
|
+
SetShellVarContext current
|
|
441
517
|
Delete "$DESKTOP\\${escapeNsis(productName)}.lnk"
|
|
442
518
|
RMDir /r "$SMPROGRAMS\\${escapeNsis(productName)}"
|
|
443
|
-
DeleteRegKey HKCU "
|
|
519
|
+
DeleteRegKey HKCU "${uninstallKey}"
|
|
444
520
|
RMDir /r "$INSTDIR"
|
|
445
521
|
SectionEnd
|
|
446
522
|
`;
|
|
447
523
|
await fs.promises.writeFile(nsisPath, script.trimStart(), 'utf8');
|
|
448
524
|
|
|
449
|
-
|
|
450
|
-
|
|
525
|
+
const makensis = resolveNsisExecutable();
|
|
526
|
+
if (makensis) {
|
|
527
|
+
await run(makensis, [nsisPath]);
|
|
451
528
|
if (fs.existsSync(installerPath)) outputs.push(installerPath);
|
|
452
529
|
} else {
|
|
453
530
|
console.warn('NSIS was not found; installer.nsi was generated but the .exe installer was skipped.');
|
|
@@ -455,6 +532,19 @@ SectionEnd
|
|
|
455
532
|
return outputs;
|
|
456
533
|
}
|
|
457
534
|
|
|
535
|
+
function resolveNsisExecutable() {
|
|
536
|
+
const candidates = [
|
|
537
|
+
process.env.MAKENSIS_PATH,
|
|
538
|
+
process.env.NSIS_HOME && path.join(process.env.NSIS_HOME, 'makensis.exe'),
|
|
539
|
+
process.env['ProgramFiles(x86)'] && path.join(process.env['ProgramFiles(x86)'], 'NSIS', 'makensis.exe'),
|
|
540
|
+
process.env.ProgramFiles && path.join(process.env.ProgramFiles, 'NSIS', 'makensis.exe'),
|
|
541
|
+
process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, 'Programs', 'NSIS', 'makensis.exe')
|
|
542
|
+
].filter(Boolean);
|
|
543
|
+
|
|
544
|
+
if (commandExists('makensis', ['/VERSION'])) return 'makensis';
|
|
545
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) || null;
|
|
546
|
+
}
|
|
547
|
+
|
|
458
548
|
async function packageMacOS({ project, buildRoot, unpacked, executableName, productName, hostSource }) {
|
|
459
549
|
const outputs = [];
|
|
460
550
|
const appBundle = path.join(buildRoot, `${productName}.app`);
|
package/src/doctor.cjs
CHANGED
|
@@ -12,8 +12,9 @@ async function doctorCommand(options = {}) {
|
|
|
12
12
|
|
|
13
13
|
if (process.platform === 'win32') {
|
|
14
14
|
rows.push(check('PowerShell', commandExists('powershell.exe', ['-NoProfile', '-Command', '$PSVersionTable.PSVersion.ToString()'])));
|
|
15
|
-
|
|
16
|
-
rows.push(check('
|
|
15
|
+
const webView2Version = getWindowsWebView2Version();
|
|
16
|
+
rows.push(check('WebView2 Runtime', Boolean(webView2Version), webView2Version || 'required by the native WebView'));
|
|
17
|
+
rows.push(check('NSIS (optional installer)', Boolean(resolveNsisExecutable())));
|
|
17
18
|
} else if (process.platform === 'darwin') {
|
|
18
19
|
rows.push(check('Xcode Command Line Tools', commandExists('/usr/bin/xcrun', ['--version']), 'required to compile the native Cocoa host'));
|
|
19
20
|
rows.push(check('Clang', commandExists('/usr/bin/xcrun', ['clang', '--version'])));
|
|
@@ -69,18 +70,43 @@ function pkgConfigExists(name) {
|
|
|
69
70
|
return !result.error && result.status === 0;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
function
|
|
73
|
+
function getWindowsWebView2Version() {
|
|
73
74
|
const script = `
|
|
74
75
|
$paths = @(
|
|
75
|
-
'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{
|
|
76
|
-
'HKLM:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{
|
|
77
|
-
'HKCU:\\Software\\Microsoft\\EdgeUpdate\\Clients\\{
|
|
76
|
+
'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}',
|
|
77
|
+
'HKLM:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}',
|
|
78
|
+
'HKCU:\\Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}'
|
|
78
79
|
)
|
|
79
|
-
foreach ($p in $paths) {
|
|
80
|
+
foreach ($p in $paths) {
|
|
81
|
+
try {
|
|
82
|
+
$version = Get-ItemPropertyValue -Path $p -Name 'pv' -ErrorAction Stop
|
|
83
|
+
if ($version -and $version -ne '0.0.0.0') {
|
|
84
|
+
Write-Output $version
|
|
85
|
+
exit 0
|
|
86
|
+
}
|
|
87
|
+
} catch {}
|
|
88
|
+
}
|
|
80
89
|
exit 1
|
|
81
90
|
`;
|
|
82
|
-
const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', script], {
|
|
83
|
-
|
|
91
|
+
const result = spawnSync('powershell.exe', ['-NoProfile', '-Command', script], {
|
|
92
|
+
encoding: 'utf8',
|
|
93
|
+
windowsHide: true
|
|
94
|
+
});
|
|
95
|
+
if (result.error || result.status !== 0) return null;
|
|
96
|
+
return String(result.stdout || '').trim() || null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function resolveNsisExecutable() {
|
|
100
|
+
const candidates = [
|
|
101
|
+
process.env.MAKENSIS_PATH,
|
|
102
|
+
process.env.NSIS_HOME && path.join(process.env.NSIS_HOME, 'makensis.exe'),
|
|
103
|
+
process.env['ProgramFiles(x86)'] && path.join(process.env['ProgramFiles(x86)'], 'NSIS', 'makensis.exe'),
|
|
104
|
+
process.env.ProgramFiles && path.join(process.env.ProgramFiles, 'NSIS', 'makensis.exe'),
|
|
105
|
+
process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, 'Programs', 'NSIS', 'makensis.exe')
|
|
106
|
+
].filter(Boolean);
|
|
107
|
+
|
|
108
|
+
if (commandExists('makensis', ['/VERSION'])) return 'makensis';
|
|
109
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) || null;
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
module.exports = { doctorCommand };
|