UNPKG

@bscotch/stitch-launcher

Version:

Manage GameMaker IDE and runtime installations for fast switching between versions.

185 lines 8.05 kB
import { Pathy } from '@bscotch/pathy'; import { arrayWrapped, formatTimestamp } from '@bscotch/utility/browser'; import { spawn } from 'child_process'; import { artifactExtensionForPlatform, currentOs, projectLogDirectory, } from './utility.js'; export async function executeGameMakerRuntimeInstallCommand(runtime, newRuntime) { return await executeGameMakerCommand(runtime, 'runtime', ['Install', newRuntime.version], { user: (await runtime.activeUserDirectory()).absolute, runtimePath: runtime.directory.up().absolute, runtimeUrl: newRuntime.feedUrl, verbose: true, }); } export async function computeOptions(runtime, options) { const target = options?.targetPlatform || 'windows'; const projectPath = new Pathy(options.project); const projectDir = projectPath.up(); const outputDir = new Pathy(options?.outDir || projectDir); const tempDir = new Pathy(projectDir).join('tmp'); const empath = (p) => `"${p}"`; return { project: empath(projectPath), user: empath(await runtime.activeUserDirectory()), runtimePath: empath(runtime.directory), runtime: options?.yyc ? 'YYC' : 'VM', config: options?.config, verbose: !options?.quiet, ignorecache: !!options?.noCache, cache: empath(tempDir.join('igor/cache')), temp: empath(tempDir.join('igor/temp')), // For some reason the filename has to be there // but only the directory is used... of: empath(tempDir.join(`igor/out/${projectPath.name}.win`)), tf: empath(outputDir.join(`${projectPath.name}.${artifactExtensionForPlatform(target)}`)), }; } export async function computeGameMakerCleanOptions(runtime, options) { const target = options?.targetPlatform || 'windows'; const buildOptions = await computeOptions(runtime, options); return { target, command: 'Clean', options: buildOptions, }; } export async function computeGameMakerBuildOptions(runtime, options) { const target = options?.targetPlatform || 'windows'; const command = options?.compile ? target === 'windows' ? 'PackageZip' : 'Package' : 'Run'; const buildOptions = await computeOptions(runtime, options); return { target, command, options: buildOptions, }; } // If I don't specify modules, I'll just get all of those I'm entitled to. // The /rp points to the folder CONTAINING the runtime FOLDERS // The /ru feed is required // The other arguments are positional export async function executeGameMakerBuildCommand(runtime, options) { const { target, command, options: buildOptions, } = await computeGameMakerBuildOptions(runtime, options); const results = await executeGameMakerCommand(runtime, target, command, buildOptions, options); return results; } export async function executeGameMakerCleanCommand(runtime, options) { const { target, command, options: buildOptions, } = await computeGameMakerCleanOptions(runtime, options); const results = await executeGameMakerCommand(runtime, target, command, buildOptions, options); return results; } export async function stringifyGameMakerBuildCommand(runtime, options) { const { cmd, args } = await computeGameMakerBuildCommand(runtime, options); const escapedCmd = cmd.replace(/[/\\]/g, '/').replace(/ /g, '\\ '); return `${escapedCmd} ${args.join(' ')}`; } export async function stringifyGameMakerCleanCommand(runtime, options) { const { cmd, args } = await computeGameMakerCleanCommand(runtime, options); const escapedCmd = cmd.replace(/[/\\]/g, '/').replace(/ /g, '\\ '); return `${escapedCmd} ${args.join(' ')}`; } export async function computeGameMakerCleanCommand(runtime, options) { const { target, command, options: buildOptions, } = await computeGameMakerCleanOptions(runtime, options); return computeGameMakerCommand(runtime, target, command, buildOptions); } export async function computeGameMakerBuildCommand(runtime, options) { const { target, command, options: buildOptions, } = await computeGameMakerBuildOptions(runtime, options); return computeGameMakerCommand(runtime, target, command, buildOptions); } export function computeGameMakerCommand(runtime, worker, command, executionOptions) { let args = Object.entries(executionOptions) .map((option) => { const [key, value] = option; if (typeof value === 'undefined') { return; } const arg = `--${key}`; if (value === false) { return; } if (value === true) { return arg; } return arg + `=${value}`; }) .filter((x) => x); const cmd = runtime.executablePath.absolute; args = [...args, '--', worker, ...arrayWrapped(command)]; return { cmd, args, }; } export async function executeGameMakerCommand(runtime, worker, command, executionOptions, otherOptions) { const childEnv = { ...process.env }; if (childEnv.PATH && currentOs === 'windows') { //This is because node's the ENV contain the PATH variable that conflicts with MSBuild //See https://github.com/dotnet/msbuild/issues/5726 delete childEnv.PATH; } const { cmd, args } = computeGameMakerCommand(runtime, worker, command, executionOptions); console.log('🚀 Running GameMaker CLI command:'); console.log(cmd, ...args); const child = spawn(cmd, args, { env: childEnv, stdio: 'pipe', }); // Set up writeable file streams const timestamp = formatTimestamp(new Date(), { secondsPrecision: 0, timeSeparator: '', }); const logDir = await projectLogDirectory(executionOptions.project, otherOptions); const logFilePathy = (fileName) => { const logFileName = `${otherOptions?.excludeLogFileTimestamps ? '' : `${timestamp}.`}${fileName}.txt`; const logFilePath = new Pathy(logDir.join(logFileName)); return logFilePath; }; const results = {}; for (const pipe of ['stdout', 'stderr']) { // Create a writeable filestream child[pipe].on('data', (data) => { const dataString = data.toString(); results[pipe] += dataString; console[pipe === 'stderr' ? 'error' : 'log'](dataString); }); } return new Promise((resolve) => { child.on('exit', async () => { // The text "Igor complete." appears // after the compile logs (if compile // was successful) AND after the run // (if the run exited normally). const successMessage = 'Igor complete.'; const logParts = results.stdout.split(successMessage); results.compileSucceeded = logParts.length > 1; const wasRunnable = command === 'Run' && results.compileSucceeded; const containedTwoIgorCompletes = logParts.length === 3; results.runnerSucceeded = wasRunnable ? containedTwoIgorCompletes : undefined; // Add compiler & runner logs for (const [index, source] of ['compiler', 'runner'].entries()) { let content = logParts[index]; if (!content) { continue; } const needsSuccessMessage = (index === 0 && results.compileSucceeded) || (index === 1 && results.runnerSucceeded); if (needsSuccessMessage) { content += successMessage; } const logName = `${source}Logs`; results[logName] = content; const logFileKey = `${source}LogsPath`; const logFilePath = logFilePathy(source); await logFilePath.write(content); results[logFileKey] = logFilePath.absolute; } resolve(results); }); }); } //# sourceMappingURL=GameMakerRuntime.command.js.map