UNPKG

node-msix-packager

Version:

A utility to create MSIX packages from Node.js applications with MCP server support, Node.js Single Executable Application (SEA) bundling using @vercel/ncc and postject, and enhanced build options

278 lines (250 loc) 8.19 kB
const path = require('path'); const fs = require('fs-extra'); const { execSync } = require('child_process'); const https = require('https'); const { CONSTANTS, ToolNotFoundError, MSIXError } = require('./constants'); /** * Executes a command safely with error handling * @param {string} command - Command to execute * @param {Object} options - Execution options * @returns {string} Command output * @throws {MSIXError} When command execution fails */ function executeCommand(command, options = {}) { try { const result = execSync(command, { encoding: 'utf8', stdio: options.silent ? 'pipe' : 'inherit', windowsHide: true, ...options }); return result; } catch (error) { const errorMessage = `Command failed: ${command}\n${error.message}`; throw new MSIXError(errorMessage); } } /** * Finds Windows SDK tools * @returns {Object} Object containing paths to makeappx and signtool * @throws {ToolNotFoundError} When tools are not found */ function findWindowsSDKTools() { const possiblePaths = CONSTANTS.WINDOWS_SDK_PATHS; for (const basePath of possiblePaths) { try { const makeappxPath = path.join(basePath, 'makeappx.exe'); const signtoolPath = path.join(basePath, 'signtool.exe'); if (fs.existsSync(makeappxPath) && fs.existsSync(signtoolPath)) { return { makeappx: makeappxPath, signtool: signtoolPath }; } } catch (error) { // Continue searching } } throw new ToolNotFoundError('Windows SDK tools (makeappx.exe, signtool.exe) not found. Please install Windows SDK.'); } /** * Generates a unique temporary directory name * @param {string} prefix - Prefix for the temporary directory * @returns {string} Temporary directory path */ function getTempDir(prefix = 'msix-temp') { const tempBase = process.env.TEMP || process.env.TMP || path.join(process.cwd(), 'temp'); const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 8); return path.join(tempBase, `${prefix}-${timestamp}-${random}`); } /** * Cleans up temporary directories and files * @param {string[]} paths - Array of paths to clean up */ async function cleanup(paths) { for (const cleanupPath of paths) { try { if (await fs.pathExists(cleanupPath)) { await fs.remove(cleanupPath); console.log(`Cleaned up: ${cleanupPath}`); } } catch (error) { console.warn(`Warning: Could not clean up ${cleanupPath}: ${error.message}`); } } } /** * Creates a directory structure ensuring all parent directories exist * @param {string} dirPath - Directory path to create */ async function ensureDir(dirPath) { try { await fs.ensureDir(dirPath); } catch (error) { throw new MSIXError(`Failed to create directory ${dirPath}: ${error.message}`); } } /** * Copies files with progress indication * @param {string} src - Source path * @param {string} dest - Destination path * @param {boolean} showProgress - Whether to show progress */ async function copyFiles(src, dest, showProgress = true) { try { if (showProgress) { console.log(`Copying files from ${src} to ${dest}...`); } await fs.copy(src, dest, { overwrite: true, errorOnExist: false, filter: (src) => { // Skip node_modules and common temporary directories const relativePath = path.relative(src, src); return !CONSTANTS.COPY_EXCLUDE_PATTERNS.some(pattern => relativePath.includes(pattern) ); } }); if (showProgress) { console.log('Files copied successfully'); } } catch (error) { throw new MSIXError(`Failed to copy files: ${error.message}`); } } /** * Reads and parses package.json from a directory * @param {string} directoryPath - Path to directory containing package.json * @returns {Object} Parsed package.json content * @throws {MSIXError} When package.json cannot be read or parsed */ async function readPackageJson(directoryPath) { const packageJsonPath = path.join(directoryPath, 'package.json'); try { const content = await fs.readFile(packageJsonPath, 'utf8'); return JSON.parse(content); } catch (error) { throw new MSIXError(`Failed to read package.json from ${directoryPath}: ${error.message}`); } } /** * Formats file size for human reading * @param {number} bytes - Size in bytes * @returns {string} Formatted size string */ function formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } /** * Validates that all required tools are available * @throws {ToolNotFoundError} When required tools are missing */ function validateRequiredTools() { try { // Check for Node.js execSync('node --version', { stdio: 'pipe' }); } catch { throw new ToolNotFoundError('Node.js is required but not found in PATH'); } try { // Check for npm execSync('npm --version', { stdio: 'pipe' }); } catch { throw new ToolNotFoundError('npm is required but not found in PATH'); } // Check for Windows SDK tools findWindowsSDKTools(); // This will throw if not found } /** * Gets system information for debugging * @returns {Object} System information */ function getSystemInfo() { try { const nodeVersion = execSync('node --version', { encoding: 'utf8' }).trim(); const npmVersion = execSync('npm --version', { encoding: 'utf8' }).trim(); const osVersion = require('os').release(); const platform = process.platform; const arch = process.arch; return { node: nodeVersion, npm: npmVersion, os: osVersion, platform, architecture: arch, timestamp: new Date().toISOString() }; } catch (error) { return { error: 'Could not gather system information', timestamp: new Date().toISOString() }; } } /** * Gets the latest Node.js LTS version dynamically * @returns {Promise<string>} Latest LTS version (e.g., 'v22.8.0') */ async function getLatestNodeVersion() { try { return new Promise((resolve, reject) => { const options = { hostname: 'nodejs.org', path: '/dist/index.json', method: 'GET', timeout: 10000 }; const req = https.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { const releases = JSON.parse(data); // Find latest LTS version const latestLTS = releases.find(release => release.lts); if (latestLTS) { resolve(latestLTS.version); } else { // Fallback to latest stable resolve(releases[0].version); } } catch (parseError) { console.warn('Warning: Could not parse Node.js releases, using fallback version'); resolve('v22.8.0'); // Fallback version } }); }); req.on('error', (error) => { console.warn('Warning: Could not fetch latest Node.js version, using fallback'); resolve('v22.8.0'); // Fallback version }); req.on('timeout', () => { console.warn('Warning: Timeout fetching Node.js version, using fallback'); resolve('v22.8.0'); // Fallback version }); req.end(); }); } catch (error) { console.warn('Warning: Error getting Node.js version, using fallback'); return 'v22.8.0'; // Fallback version } } module.exports = { executeCommand, findWindowsSDKTools, getTempDir, cleanup, ensureDir, copyFiles, readPackageJson, formatFileSize, validateRequiredTools, getSystemInfo, getLatestNodeVersion };