112 lines
3.9 KiB
JavaScript
112 lines
3.9 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const crypto = require("crypto");
|
|
const fs = require("fs");
|
|
const https = require("https");
|
|
const path = require("path");
|
|
const { spawnSync } = require("child_process");
|
|
|
|
const { findProjectRoot } = require("../lib/project-root");
|
|
|
|
function platformKey(platform = process.platform, arch = process.arch) {
|
|
const osName = { darwin: "darwin", linux: "linux" }[platform];
|
|
const archName = { x64: "amd64", arm64: "arm64" }[arch];
|
|
if (!osName || !archName) {
|
|
throw new Error(`Unsupported actionlint platform: ${platform}/${arch}`);
|
|
}
|
|
return `${osName}-${archName}`;
|
|
}
|
|
|
|
function sha256(filePath) {
|
|
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
|
|
}
|
|
|
|
function download(url, destination, redirects = 5) {
|
|
return new Promise((resolve, reject) => {
|
|
const request = https.get(url, { headers: { "user-agent": "agentic-awesome-skills-actionlint" } }, (response) => {
|
|
if ([301, 302, 303, 307, 308].includes(response.statusCode) && response.headers.location) {
|
|
response.resume();
|
|
if (redirects <= 0) {
|
|
reject(new Error(`Too many redirects downloading ${url}`));
|
|
return;
|
|
}
|
|
download(response.headers.location, destination, redirects - 1).then(resolve, reject);
|
|
return;
|
|
}
|
|
if (response.statusCode !== 200) {
|
|
response.resume();
|
|
reject(new Error(`Download failed (${response.statusCode}) for ${url}`));
|
|
return;
|
|
}
|
|
const stream = fs.createWriteStream(destination, { mode: 0o600 });
|
|
response.pipe(stream);
|
|
stream.on("finish", () => stream.close(resolve));
|
|
stream.on("error", reject);
|
|
});
|
|
request.on("error", reject);
|
|
});
|
|
}
|
|
|
|
function run(command, args, cwd) {
|
|
const result = spawnSync(command, args, { cwd, stdio: "inherit" });
|
|
if (result.error) {
|
|
throw result.error;
|
|
}
|
|
if (result.status !== 0) {
|
|
throw new Error(`${command} ${args.join(" ")} failed with status ${result.status}`);
|
|
}
|
|
}
|
|
|
|
async function ensureActionlint(projectRoot) {
|
|
const config = JSON.parse(
|
|
fs.readFileSync(path.join(projectRoot, "tools", "config", "actionlint.json"), "utf8"),
|
|
);
|
|
const key = platformKey();
|
|
const expectedHash = config.artifacts[key];
|
|
if (!/^[a-f0-9]{64}$/.test(String(expectedHash || ""))) {
|
|
throw new Error(`Missing SHA-256 for actionlint platform ${key}`);
|
|
}
|
|
|
|
const [osName, archName] = key.split("-");
|
|
const cacheDir = path.join(projectRoot, ".tmp", "actionlint", config.version, key);
|
|
const binaryPath = path.join(cacheDir, "actionlint");
|
|
const archiveName = `actionlint_${config.version}_${osName}_${archName}.tar.gz`;
|
|
const archivePath = path.join(cacheDir, archiveName);
|
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
|
|
if (!fs.existsSync(archivePath) || sha256(archivePath) !== expectedHash) {
|
|
fs.rmSync(archivePath, { force: true });
|
|
const url = `https://github.com/rhysd/actionlint/releases/download/v${config.version}/${archiveName}`;
|
|
console.log(`[actionlint] Downloading pinned v${config.version} for ${key}`);
|
|
await download(url, archivePath);
|
|
}
|
|
const actualHash = sha256(archivePath);
|
|
if (actualHash !== expectedHash) {
|
|
fs.rmSync(archivePath, { force: true });
|
|
throw new Error(`actionlint checksum mismatch: expected ${expectedHash}, got ${actualHash}`);
|
|
}
|
|
|
|
// Always recreate the executable from the verified archive so a stale or
|
|
// tampered cache entry cannot become the verifier.
|
|
fs.rmSync(binaryPath, { force: true });
|
|
run("tar", ["-xzf", archivePath, "-C", cacheDir, "actionlint"], projectRoot);
|
|
fs.chmodSync(binaryPath, 0o755);
|
|
|
|
return binaryPath;
|
|
}
|
|
|
|
async function main() {
|
|
const projectRoot = findProjectRoot(__dirname);
|
|
const binary = await ensureActionlint(projectRoot);
|
|
run(binary, ["-color"], projectRoot);
|
|
}
|
|
|
|
if (require.main === module) {
|
|
main().catch((error) => {
|
|
console.error(`[actionlint] ${error.message}`);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
module.exports = { download, ensureActionlint, platformKey, sha256 };
|