From 7981c3837571f9445dbd44795d1dcc8229b35663 Mon Sep 17 00:00:00 2001 From: DannyFeng Date: Sat, 22 Nov 2025 17:44:09 +0800 Subject: [PATCH] feat: Add option to install latest Inno Setup via URL Introduces 'install_latest' and 'installer_url' inputs to allow downloading and installing the latest Inno Setup from a specified URL before running. Updates logic to support silent installation via direct download or fallback to Chocolatey, and improves detection of iscc.exe location. --- action.yml | 8 ++++ src/index.js | 115 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 105 insertions(+), 18 deletions(-) diff --git a/action.yml b/action.yml index e4bed07..a28c3a7 100755 --- a/action.yml +++ b/action.yml @@ -12,6 +12,14 @@ inputs: options: description: 'Extra arguments/options to include. Include the slashes for them.' required: false + install_latest: + description: 'Set to true to download and install the latest Inno Setup from the installer URL before running.' + required: false + default: 'false' + installer_url: + description: 'URL to download the Inno Setup installer (can be overridden).' + required: false + default: 'https://jrsoftware.org/download.php/is.exe?site=1' runs: using: 'node20' diff --git a/src/index.js b/src/index.js index 3c5cc22..393cadb 100644 --- a/src/index.js +++ b/src/index.js @@ -1,14 +1,20 @@ import * as core from "@actions/core"; import { promises as fs } from "fs"; +import { createWriteStream } from "fs"; import { exec, execFile } from "child_process"; import { promisify } from "util"; +import https from "https"; +import os from "os"; +import pathModule from "path"; const execPromise = promisify(exec); const execFilePromise = promisify(execFile); const workspacePath = process.env.GITHUB_WORKSPACE; const options = core.getMultilineInput("options"); -const path = core.getInput("path"); +const scriptInput = core.getInput("path"); +const installLatest = (core.getInput("install_latest") || "false").toLowerCase() === "true"; +const installerUrl = core.getInput("installer_url") || "https://jrsoftware.org/download.php/is.exe?site=1"; async function run() { try { @@ -35,27 +41,100 @@ async function run() { const escapedOptions = options.map((str) => str.replace(/(["'])/g, "$1")); - // Install Inno Setup silently - try { - const { stdout, stderr } = await execPromise(`choco install innosetup`); - console.error(stderr); - } catch (err) { - throw new Error( - `Failed to install Inno Setup: ${err.stderr || err.message}`, - ); + async function downloadFile(url, dest) { + return new Promise((resolve, reject) => { + const maxRedirects = 5; + function getUrl(u, redirects) { + if (redirects > maxRedirects) return reject(new Error("Too many redirects while downloading installer")); + https.get(u, (res) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + return getUrl(res.headers.location, redirects + 1); + } + if (res.statusCode !== 200) { + return reject(new Error(`Download failed with status ${res.statusCode}`)); + } + const file = createWriteStream(dest); + res.pipe(file); + file.on("finish", () => file.close(resolve)); + file.on("error", reject); + }).on("error", reject); + } + getUrl(url, 0); + }); } - // Run Inno Setup Compiler - const isccPath = `${process.env["ProgramFiles(x86)"]}\\Inno Setup 6\\iscc.exe`; - const scriptPath = `${workspacePath}\\${path}`; + async function findIscc() { + const candidates = []; + const pf86 = process.env["ProgramFiles(x86)"]; + const pf = process.env["ProgramFiles"]; + if (pf86) { + candidates.push(pathModule.join(pf86, "Inno Setup 6", "iscc.exe")); + candidates.push(pathModule.join(pf86, "Inno Setup 7", "iscc.exe")); + candidates.push(pathModule.join(pf86, "Inno Setup", "iscc.exe")); + } + if (pf) { + candidates.push(pathModule.join(pf, "Inno Setup 6", "iscc.exe")); + candidates.push(pathModule.join(pf, "Inno Setup 7", "iscc.exe")); + candidates.push(pathModule.join(pf, "Inno Setup", "iscc.exe")); + } + + for (const p of candidates) { + try { + await fs.access(p); + return p; + } catch (e) { + // ignore + } + } + + // Fallback to 'where' to see if it's on PATH + try { + const { stdout } = await execPromise("where iscc.exe"); + const line = stdout.split(/\r?\n/).find(Boolean); + if (line) return line.trim(); + } catch (e) { + // ignore + } + + return null; + } + + // Install Inno Setup: either download+install latest or fallback to choco + if (installLatest) { + const tmpExe = pathModule.join(os.tmpdir(), `inno-setup-installer-${Date.now()}.exe`); + try { + core.info(`Downloading Inno Setup from ${installerUrl} ...`); + await downloadFile(installerUrl, tmpExe); + core.info(`Running installer silently: ${tmpExe}`); + await execFilePromise(tmpExe, ["/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/SP-"]); + } catch (err) { + core.warning(`Download/install failed: ${err.message}. Falling back to Chocolatey.`); + try { + await execPromise(`choco install innosetup -y`); + } catch (err2) { + throw new Error(`Failed to install Inno Setup: ${err2.stderr || err2.message}`); + } + } + } else { + try { + await execPromise(`choco install innosetup -y`); + } catch (err) { + throw new Error(`Failed to install Inno Setup: ${err.stderr || err.message}`); + } + } + + // Locate iscc.exe + const isccPath = await findIscc(); + if (!isccPath) { + throw new Error("Could not locate iscc.exe after installation."); + } + + const scriptPath = pathModule.join(workspacePath, scriptInput); try { - const { stdout, stderr } = await execFilePromise(isccPath, [ - scriptPath, - ...escapedOptions, - ]); - console.log(stdout); - console.error(stderr); + const { stdout, stderr } = await execFilePromise(isccPath, [scriptPath, ...escapedOptions]); + if (stdout) console.log(stdout); + if (stderr) console.error(stderr); } catch (err) { throw new Error(`Execution failed: ${err.stderr || err.message}`); }