import chalk from "chalk"; import { ui } from "../environment/userInteraction.js"; import { getPackageManagerList, knownAikidoTools } from "./helpers.js"; import { getShimsDir, getBinDir, getPathWrapperTemplatePath, } from "../config/safeChainDir.js"; import fs from "fs"; import os from "os"; import path from "path"; /** * Loops over the detected shells and calls the setup function for each. */ export async function setupCi() { ui.writeInformation( chalk.bold("Setting up shell aliases.") + ` This will wrap safe-chain around ${getPackageManagerList()}.` ); ui.emptyLine(); const shimsDir = getShimsDir(); const binDir = getBinDir(); // Create the shims directory if it doesn't exist if (!fs.existsSync(shimsDir)) { fs.mkdirSync(shimsDir, { recursive: true }); } createShims(shimsDir); ui.writeInformation(`Created shims in ${shimsDir}`); modifyPathForCi(shimsDir, binDir); ui.writeInformation(`Added shims directory to PATH for CI environments.`); } /** * @param {string} shimsDir * * @returns {void} */ function createUnixShims(shimsDir) { // Read the template file const templatePath = getPathWrapperTemplatePath(import.meta.url, "unix-wrapper.template.sh"); if (!fs.existsSync(templatePath)) { ui.writeError(`Template file not found: ${templatePath}`); return; } const template = fs.readFileSync(templatePath, "utf-8"); // Create a shim for each tool let created = 0; for (const toolInfo of getToolsToSetup()) { const shimContent = template .replaceAll("{{PACKAGE_MANAGER}}", toolInfo.tool) .replaceAll("{{AIKIDO_COMMAND}}", toolInfo.aikidoCommand); const shimPath = path.join(shimsDir, toolInfo.tool); fs.writeFileSync(shimPath, shimContent, "utf-8"); // Make the shim executable on Unix systems fs.chmodSync(shimPath, 0o755); created++; } ui.writeInformation(`Created ${created} Unix shim(s) in ${shimsDir}`); } /** * @param {string} shimsDir * * @returns {void} */ function createWindowsShims(shimsDir) { // Read the template file const templatePath = getPathWrapperTemplatePath(import.meta.url, "windows-wrapper.template.cmd"); if (!fs.existsSync(templatePath)) { ui.writeError(`Windows template file not found: ${templatePath}`); return; } const template = fs.readFileSync(templatePath, "utf-8"); // Create a shim for each tool let created = 0; for (const toolInfo of getToolsToSetup()) { const shimContent = template .replaceAll("{{PACKAGE_MANAGER}}", toolInfo.tool) .replaceAll("{{AIKIDO_COMMAND}}", toolInfo.aikidoCommand); const shimPath = `${shimsDir}/${toolInfo.tool}.cmd`; fs.writeFileSync(shimPath, shimContent, "utf-8"); created++; } ui.writeInformation(`Created ${created} Windows shim(s) in ${shimsDir}`); } /** * @param {string} shimsDir * * @returns {void} */ function createShims(shimsDir) { if (os.platform() === "win32") { createWindowsShims(shimsDir); } else { createUnixShims(shimsDir); } } /** * @param {string} shimsDir * @param {string} binDir * * @returns {void} */ function modifyPathForCi(shimsDir, binDir) { if (process.env.GITHUB_PATH) { // In GitHub Actions, append the shims directory to GITHUB_PATH fs.appendFileSync( process.env.GITHUB_PATH, shimsDir + os.EOL + binDir + os.EOL, "utf-8" ); ui.writeInformation( `Added shims directory to GITHUB_PATH for GitHub Actions.` ); } if (process.env.TF_BUILD) { // In Azure Pipelines, prepending the path is done via a logging command: // ##vso[task.prependpath]/path/to/add // Logging this to stdout will cause the Azure Pipelines agent to pick it up ui.writeInformation("##vso[task.prependpath]" + shimsDir); ui.writeInformation("##vso[task.prependpath]" + binDir); } if (process.env.BASH_ENV) { // In CircleCI, persisting PATH across steps is done by appending shell exports // to the file referenced by BASH_ENV. CircleCI sources this file for 'run' each step. const exportLine = `export PATH="${shimsDir}:${binDir}:$PATH"` + os.EOL; fs.appendFileSync(process.env.BASH_ENV, exportLine, "utf-8"); ui.writeInformation(`Added shims directory to BASH_ENV for CircleCI.`); } } function getToolsToSetup() { return knownAikidoTools; }