Initial
This commit is contained in:
80
server/services/intuneWinBuilder.js
Normal file
80
server/services/intuneWinBuilder.js
Normal file
@@ -0,0 +1,80 @@
|
||||
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()}` : ''}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user