@atom-js-org/cli 0.3.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 +138 -50
- package/src/doctor.cjs +35 -9
- package/src/utils.cjs +13 -3
package/package.json
CHANGED
package/src/build.cjs
CHANGED
|
@@ -39,8 +39,8 @@ async function localBuild(project, target, options = {}) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const buildRoot = path.join(project.root, 'build', target);
|
|
42
|
-
const unpacked = path.join(buildRoot, '
|
|
43
|
-
const appDir =
|
|
42
|
+
const unpacked = path.join(buildRoot, 'portable');
|
|
43
|
+
const appDir = null;
|
|
44
44
|
const productName = sanitizeFilename(project.config.productName);
|
|
45
45
|
|
|
46
46
|
console.log(`\nAtomJS build (${target})`);
|
|
@@ -50,7 +50,8 @@ async function localBuild(project, target, options = {}) {
|
|
|
50
50
|
await fse.remove(buildRoot);
|
|
51
51
|
await fse.ensureDir(unpacked);
|
|
52
52
|
|
|
53
|
-
const
|
|
53
|
+
const stageBase = await resolveShortStageBase();
|
|
54
|
+
const stageRoot = await fs.promises.mkdtemp(path.join(stageBase, 's-'));
|
|
54
55
|
const stagedApp = path.join(stageRoot, 'app');
|
|
55
56
|
const payloadPath = path.join(stageRoot, 'atom-app.payload.gz');
|
|
56
57
|
|
|
@@ -59,21 +60,8 @@ async function localBuild(project, target, options = {}) {
|
|
|
59
60
|
await vendorFramework(stagedApp, project, target);
|
|
60
61
|
await installProductionDependencies(stagedApp, options.skipInstall, target);
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
await createApplicationPayload(stagedApp, payloadPath);
|
|
65
|
-
} else {
|
|
66
|
-
await fse.move(stagedApp, appDir, { overwrite: true });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (target !== 'macos') {
|
|
70
|
-
const runtimeDir = path.join(unpacked, 'runtime');
|
|
71
|
-
await fse.ensureDir(runtimeDir);
|
|
72
|
-
const runtimeNodeName = target === 'windows' ? 'node.exe' : 'node';
|
|
73
|
-
const runtimeNodePath = path.join(runtimeDir, runtimeNodeName);
|
|
74
|
-
await fs.promises.copyFile(process.execPath, runtimeNodePath);
|
|
75
|
-
if (target !== 'windows') await fs.promises.chmod(runtimeNodePath, 0o755);
|
|
76
|
-
}
|
|
63
|
+
console.log('Embedding application code and production dependencies into the executable...');
|
|
64
|
+
await createApplicationPayload(stagedApp, payloadPath);
|
|
77
65
|
|
|
78
66
|
const executableName = target === 'windows' ? `${productName}.exe` : productName;
|
|
79
67
|
const executablePath = path.join(unpacked, executableName);
|
|
@@ -82,7 +70,7 @@ async function localBuild(project, target, options = {}) {
|
|
|
82
70
|
appDir,
|
|
83
71
|
target,
|
|
84
72
|
productName,
|
|
85
|
-
payloadPath
|
|
73
|
+
payloadPath
|
|
86
74
|
});
|
|
87
75
|
|
|
88
76
|
const creditPath = path.join(unpacked, 'ATOMJS-CREDIT.txt');
|
|
@@ -138,6 +126,28 @@ async function localBuild(project, target, options = {}) {
|
|
|
138
126
|
}
|
|
139
127
|
}
|
|
140
128
|
|
|
129
|
+
|
|
130
|
+
async function resolveShortStageBase() {
|
|
131
|
+
const configured = process.env.ATOMJS_TEMP_DIR || process.env.ATOM_TEMP_DIR;
|
|
132
|
+
const candidates = configured
|
|
133
|
+
? [path.resolve(configured)]
|
|
134
|
+
: process.platform === 'win32'
|
|
135
|
+
? [path.join(path.parse(process.cwd()).root, '.atomjs-tmp'), path.join(os.tmpdir(), 'atomjs')]
|
|
136
|
+
: [path.join(os.tmpdir(), 'atomjs')];
|
|
137
|
+
|
|
138
|
+
let lastError;
|
|
139
|
+
for (const candidate of candidates) {
|
|
140
|
+
try {
|
|
141
|
+
await fse.ensureDir(candidate);
|
|
142
|
+
await fs.promises.access(candidate, fs.constants.W_OK);
|
|
143
|
+
return candidate;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
lastError = error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
throw new Error(`AtomJS could not create a writable short staging directory: ${lastError ? lastError.message : 'unknown error'}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
141
151
|
async function copyApplication(projectRoot, appDir) {
|
|
142
152
|
await fse.copy(projectRoot, appDir, {
|
|
143
153
|
dereference: false,
|
|
@@ -284,9 +294,8 @@ async function createSeaLauncher({ executablePath, appDir, target, productName,
|
|
|
284
294
|
const blobPath = path.join(work, target === 'windows' ? 'sea-prep.blob.exe' : 'sea-prep.blob');
|
|
285
295
|
const configPath = path.join(work, 'sea-config.json');
|
|
286
296
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
: createLooseLauncherSource();
|
|
297
|
+
if (!payloadPath) throw new Error('AtomJS SEA payload is required.');
|
|
298
|
+
const launcher = createEmbeddedLauncherSource(productName);
|
|
290
299
|
await fs.promises.writeFile(launcherPath, launcher, 'utf8');
|
|
291
300
|
|
|
292
301
|
const seaConfig = {
|
|
@@ -295,13 +304,16 @@ async function createSeaLauncher({ executablePath, appDir, target, productName,
|
|
|
295
304
|
disableExperimentalSEAWarning: true,
|
|
296
305
|
useSnapshot: false,
|
|
297
306
|
useCodeCache: false,
|
|
298
|
-
|
|
307
|
+
assets: { 'atom-app': payloadPath }
|
|
299
308
|
};
|
|
300
309
|
await fs.promises.writeFile(configPath, JSON.stringify(seaConfig, null, 2));
|
|
301
310
|
|
|
302
311
|
await run(process.execPath, ['--experimental-sea-config', configPath], { cwd: work });
|
|
303
312
|
await fs.promises.copyFile(process.execPath, executablePath);
|
|
304
313
|
|
|
314
|
+
if (target === 'windows') {
|
|
315
|
+
await prepareWindowsExecutableForInjection(executablePath);
|
|
316
|
+
}
|
|
305
317
|
if (target === 'macos' && commandExists('codesign', ['--version'])) {
|
|
306
318
|
spawnSync('codesign', ['--remove-signature', executablePath], { stdio: 'ignore' });
|
|
307
319
|
}
|
|
@@ -311,38 +323,64 @@ async function createSeaLauncher({ executablePath, appDir, target, productName,
|
|
|
311
323
|
machoSegmentName: 'NODE_SEA'
|
|
312
324
|
});
|
|
313
325
|
|
|
314
|
-
if (target
|
|
326
|
+
if (target === 'windows') {
|
|
327
|
+
await setWindowsGuiSubsystem(executablePath);
|
|
328
|
+
} else {
|
|
329
|
+
await fs.promises.chmod(executablePath, 0o755);
|
|
330
|
+
}
|
|
315
331
|
if (target === 'macos' && commandExists('codesign', ['--version'])) {
|
|
316
332
|
await run('codesign', ['--force', '--sign', '-', executablePath]);
|
|
317
333
|
}
|
|
318
334
|
await fse.remove(work);
|
|
319
335
|
}
|
|
320
336
|
|
|
321
|
-
function createLooseLauncherSource() {
|
|
322
|
-
return `
|
|
323
|
-
'use strict';
|
|
324
|
-
const path = require('node:path');
|
|
325
|
-
const fs = require('node:fs');
|
|
326
|
-
const { createRequire } = require('node:module');
|
|
327
337
|
|
|
328
|
-
|
|
329
|
-
const
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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);
|
|
334
354
|
}
|
|
335
355
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
+
};
|
|
346
384
|
}
|
|
347
385
|
|
|
348
386
|
function createEmbeddedLauncherSource(productName) {
|
|
@@ -354,6 +392,7 @@ const os = require('node:os');
|
|
|
354
392
|
const crypto = require('node:crypto');
|
|
355
393
|
const zlib = require('node:zlib');
|
|
356
394
|
const { createRequire } = require('node:module');
|
|
395
|
+
const { pathToFileURL } = require('node:url');
|
|
357
396
|
const { getAsset } = require('node:sea');
|
|
358
397
|
|
|
359
398
|
const productName = ${JSON.stringify(productName)};
|
|
@@ -397,7 +436,20 @@ const executableDir = path.dirname(process.execPath);
|
|
|
397
436
|
process.chdir(appDir);
|
|
398
437
|
process.env.ATOM_PROJECT_ROOT = appDir;
|
|
399
438
|
process.env.ATOM_BUILD = '1';
|
|
439
|
+
process.env.ATOM_EMBEDDED_RUNTIME = '1';
|
|
440
|
+
process.env.ATOM_WINDOW_HOST_ENTRY = path.join(appDir, 'vendor', 'atomjs', 'src', 'runtime', 'window-host.mjs');
|
|
400
441
|
process.env.ATOM_MACOS_HOST_EXECUTABLE = path.join(executableDir, 'AtomJSWindowHost');
|
|
442
|
+
const hostModeIndex = process.argv.indexOf('--atomjs-window-host');
|
|
443
|
+
if (hostModeIndex !== -1) {
|
|
444
|
+
const hostEntry = process.env.ATOM_WINDOW_HOST_ENTRY;
|
|
445
|
+
const configPath = process.argv[hostModeIndex + 1];
|
|
446
|
+
process.argv = [process.argv[0], hostEntry, configPath];
|
|
447
|
+
import(pathToFileURL(hostEntry).href).catch((error) => {
|
|
448
|
+
console.error(error && error.stack ? error.stack : error);
|
|
449
|
+
process.exit(1);
|
|
450
|
+
});
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
401
453
|
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
402
454
|
const mainPath = path.resolve(appDir, pkg.main || 'main.js');
|
|
403
455
|
const load = createRequire(packagePath);
|
|
@@ -414,40 +466,65 @@ async function packageWindows({ project, buildRoot, unpacked, executableName, pr
|
|
|
414
466
|
const nsisPath = path.join(buildRoot, 'installer.nsi');
|
|
415
467
|
const installerPath = path.join(buildRoot, `${productName} Installer.exe`);
|
|
416
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)}`;
|
|
417
477
|
const credit = project.config.installerCredit
|
|
418
478
|
? `!define MUI_WELCOMEPAGE_TEXT "This installer will install ${escapeNsis(productName)}.$\\r$\\n$\\r$\\nPowered by AtomJS — https://github.com/Atom-js-org/atom"`
|
|
419
479
|
: '';
|
|
420
480
|
const script = `
|
|
421
481
|
Unicode true
|
|
482
|
+
SetCompressor /SOLID lzma
|
|
422
483
|
!include "MUI2.nsh"
|
|
423
484
|
Name "${escapeNsis(productName)}"
|
|
424
485
|
OutFile "${installerPath.replace(/\\/g, '\\\\')}"
|
|
425
486
|
InstallDir "$LOCALAPPDATA\\Programs\\${escapeNsis(productName)}"
|
|
426
487
|
RequestExecutionLevel user
|
|
488
|
+
ShowInstDetails show
|
|
489
|
+
ShowUninstDetails show
|
|
427
490
|
${credit}
|
|
428
491
|
!insertmacro MUI_PAGE_WELCOME
|
|
429
492
|
!insertmacro MUI_PAGE_DIRECTORY
|
|
430
493
|
!insertmacro MUI_PAGE_INSTFILES
|
|
494
|
+
!define MUI_FINISHPAGE_RUN "$INSTDIR\\${escapeNsis(executableName)}"
|
|
431
495
|
!insertmacro MUI_PAGE_FINISH
|
|
496
|
+
!insertmacro MUI_UNPAGE_CONFIRM
|
|
497
|
+
!insertmacro MUI_UNPAGE_INSTFILES
|
|
432
498
|
!insertmacro MUI_LANGUAGE "English"
|
|
433
499
|
Section "Install"
|
|
500
|
+
SetShellVarContext current
|
|
434
501
|
SetOutPath "$INSTDIR"
|
|
435
502
|
File /r "${escapedSource}\\*"
|
|
436
503
|
CreateDirectory "$SMPROGRAMS\\${escapeNsis(productName)}"
|
|
437
504
|
CreateShortcut "$SMPROGRAMS\\${escapeNsis(productName)}\\${escapeNsis(productName)}.lnk" "$INSTDIR\\${escapeNsis(executableName)}"
|
|
438
505
|
CreateShortcut "$DESKTOP\\${escapeNsis(productName)}.lnk" "$INSTDIR\\${escapeNsis(executableName)}"
|
|
439
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
|
|
440
514
|
SectionEnd
|
|
441
515
|
Section "Uninstall"
|
|
516
|
+
SetShellVarContext current
|
|
442
517
|
Delete "$DESKTOP\\${escapeNsis(productName)}.lnk"
|
|
443
518
|
RMDir /r "$SMPROGRAMS\\${escapeNsis(productName)}"
|
|
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`);
|
|
@@ -555,8 +645,6 @@ async function packageLinux({ project, buildRoot, unpacked, executableName, prod
|
|
|
555
645
|
await fse.ensureDir(usrBin);
|
|
556
646
|
await fse.ensureDir(usrLibApp);
|
|
557
647
|
await fse.copy(path.join(unpacked, executableName), path.join(usrBin, productName));
|
|
558
|
-
await fse.copy(path.join(unpacked, 'app'), path.join(usrBin, 'app'));
|
|
559
|
-
await fse.copy(path.join(unpacked, 'runtime'), path.join(usrBin, 'runtime'));
|
|
560
648
|
await fse.copy(path.join(unpacked, 'ATOMJS-CREDIT.txt'), path.join(usrLibApp, 'ATOMJS-CREDIT.txt'));
|
|
561
649
|
|
|
562
650
|
const appRun = `#!/bin/sh\nHERE="$(dirname "$(readlink -f "$0")")"\nexec "$HERE/usr/bin/${productName}" "$@"\n`;
|
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 };
|
package/src/utils.cjs
CHANGED
|
@@ -54,9 +54,17 @@ function loadProject(projectInput) {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
function normalizeSpawn(command, args) {
|
|
58
|
+
if (process.platform === 'win32' && /\.(?:cmd|bat)$/i.test(command)) {
|
|
59
|
+
return { command: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', command, ...args] };
|
|
60
|
+
}
|
|
61
|
+
return { command, args };
|
|
62
|
+
}
|
|
63
|
+
|
|
57
64
|
function run(command, args, options = {}) {
|
|
58
65
|
return new Promise((resolve, reject) => {
|
|
59
|
-
const
|
|
66
|
+
const normalized = normalizeSpawn(command, args);
|
|
67
|
+
const child = spawn(normalized.command, normalized.args, {
|
|
60
68
|
cwd: options.cwd || process.cwd(),
|
|
61
69
|
env: options.env || process.env,
|
|
62
70
|
stdio: options.stdio || 'inherit',
|
|
@@ -71,7 +79,8 @@ function run(command, args, options = {}) {
|
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
function capture(command, args, options = {}) {
|
|
74
|
-
const
|
|
82
|
+
const normalized = normalizeSpawn(command, args);
|
|
83
|
+
const result = spawnSync(normalized.command, normalized.args, {
|
|
75
84
|
cwd: options.cwd || process.cwd(),
|
|
76
85
|
env: options.env || process.env,
|
|
77
86
|
encoding: 'utf8',
|
|
@@ -86,7 +95,8 @@ function capture(command, args, options = {}) {
|
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
function commandExists(command, args = ['--version']) {
|
|
89
|
-
const
|
|
98
|
+
const normalized = normalizeSpawn(command, args);
|
|
99
|
+
const result = spawnSync(normalized.command, normalized.args, { stdio: 'ignore', shell: false });
|
|
90
100
|
return !result.error && result.status === 0;
|
|
91
101
|
}
|
|
92
102
|
|