81 lines
3.4 KiB
JavaScript
81 lines
3.4 KiB
JavaScript
import { spawn } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { config } from '../config.js';
|
|
|
|
// Wraps a PSADT/package source folder into a `.intunewin` using the Microsoft
|
|
// Win32 Content Prep Tool (IntuneWinAppUtil.exe) on Windows, or an optional
|
|
// cross-platform packager via INTUNEWIN_BUILD_COMMAND. The command construction
|
|
// is pure and unit-tested; the spawn wrapper is thin.
|
|
|
|
export function resolveToolPath() {
|
|
return config.intunewinUtilPath || path.join(config.toolsDir, 'IntuneWinAppUtil.exe');
|
|
}
|
|
|
|
// Where to put the tool and whether we can build here.
|
|
export function builderAvailability() {
|
|
if (config.intunewinBuildCommand) {
|
|
return { available: true, mode: 'external-command', toolPath: null };
|
|
}
|
|
const toolPath = resolveToolPath();
|
|
if (!fs.existsSync(toolPath)) {
|
|
return { available: false, mode: 'bundled', toolPath, reason: `IntuneWinAppUtil.exe not found at ${toolPath}. Place it in the tools directory or set INTUNEWIN_UTIL_PATH / INTUNEWIN_BUILD_COMMAND.` };
|
|
}
|
|
if (process.platform !== 'win32') {
|
|
return { available: false, mode: 'bundled', toolPath, reason: 'IntuneWinAppUtil.exe requires Windows. Set INTUNEWIN_BUILD_COMMAND to use a cross-platform packager.' };
|
|
}
|
|
return { available: true, mode: 'bundled', toolPath };
|
|
}
|
|
|
|
// Pure: produce the spawn descriptor for the configured packager.
|
|
export function buildPackagerInvocation({ toolPath, sourceFolder, setupFile, outputDir, commandTemplate }) {
|
|
if (commandTemplate) {
|
|
const command = commandTemplate
|
|
.split('{source}').join(sourceFolder)
|
|
.split('{setup}').join(setupFile)
|
|
.split('{output}').join(outputDir);
|
|
return { shell: true, command };
|
|
}
|
|
return {
|
|
shell: false,
|
|
command: toolPath,
|
|
args: ['-c', sourceFolder, '-s', setupFile, '-o', outputDir, '-q']
|
|
};
|
|
}
|
|
|
|
// IntuneWinAppUtil names the output after the setup file's base name.
|
|
export function expectedOutputFile(outputDir, setupFile) {
|
|
return path.join(outputDir, `${path.parse(setupFile).name}.intunewin`);
|
|
}
|
|
|
|
export function buildIntuneWin({ sourceFolder, setupFile, outputDir }) {
|
|
return new Promise((resolve, reject) => {
|
|
const availability = builderAvailability();
|
|
if (!availability.available) return reject(new Error(availability.reason || 'Packager not available'));
|
|
if (!sourceFolder || !fs.existsSync(sourceFolder)) return reject(new Error(`Source folder not found: ${sourceFolder}`));
|
|
if (!setupFile) return reject(new Error('A setup file (entry installer) is required'));
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
|
|
const invocation = buildPackagerInvocation({
|
|
toolPath: availability.toolPath,
|
|
sourceFolder,
|
|
setupFile,
|
|
outputDir,
|
|
commandTemplate: config.intunewinBuildCommand
|
|
});
|
|
|
|
const child = invocation.shell
|
|
? spawn(invocation.command, { shell: true })
|
|
: spawn(invocation.command, invocation.args, { shell: false });
|
|
|
|
let stderr = '';
|
|
child.stderr.on('data', (chunk) => { stderr += String(chunk); });
|
|
child.on('error', (error) => reject(new Error(`Packager failed to start: ${error.message}`)));
|
|
child.on('close', (code) => {
|
|
const outputFile = expectedOutputFile(outputDir, setupFile);
|
|
if (code === 0 && fs.existsSync(outputFile)) return resolve({ outputFile });
|
|
reject(new Error(`Packager exited with code ${code}${stderr ? `: ${stderr.trim()}` : ''}`));
|
|
});
|
|
});
|
|
}
|