UNPKG

mega-linter-runner

Version:
317 lines (299 loc) • 11.1 kB
import { optionsDefinition, KNOWN_CONTAINER_ENGINES } from "./options.js" import { expandEnvEntries } from "./env-parser.js"; import { listVars } from "./list-vars.js"; import { spawnSync } from "child_process"; import { default as c } from 'chalk'; import * as path from 'path'; import { dirname } from 'path'; import { fileURLToPath } from 'url'; import os from "os"; import which from "which"; import { default as fs } from "fs-extra"; import { MegaLinterUpgrader } from "./upgrade.js"; import { CodeTotalRunner } from "./codetotal.js"; import { DEFAULT_RELEASE } from "./config.js"; import { createEnv } from "yeoman-environment"; import { default as FindPackageJson } from "find-package-json"; export class MegaLinterRunner { async run(options) { // Show help ( index or for an options) if (options.help) { let outputString; if (options._ && options._.length) { outputString = optionsDefinition.generateHelpForOption(options._[0]); } else { outputString = optionsDefinition.generateHelp(); } console.info(outputString); return { status: 0, stdout: outputString }; } // List MegaLinter env variables (optionally filtered) if (options.listVars) { const pattern = options._ && options._.length ? options._[0] : null; const { stdout } = listVars({ pattern, asJson: options.json === true }); console.log(stdout); return { status: 0, stdout }; } // Show version if (options.version) { let v = process.env.npm_package_version; if (!v) { try { const finder = FindPackageJson(__dirname); v = finder.next().value.version; } catch (e) { v = "error"; } } const outputString = `mega-linter-runner version ${v}`; console.log(outputString); return { status: 0, stdout: outputString }; } // Run configuration generator if (options.install) { const env = createEnv(); const __dirname = dirname(fileURLToPath(import.meta.url)); const generatorPath = path.resolve( path.join(__dirname, "..", "generators", "mega-linter") ); console.log("Yeoman generator used: " + generatorPath); env.run(generatorPath); return { status: 0 }; } // Run custom flavor generator if (options.customFlavorSetup) { if (options.customFlavorLinters) { globalThis.customFlavorLinters = options.customFlavorLinters.split(",").map((linter) => linter.trim()); } const env = createEnv(); const __dirname = dirname(fileURLToPath(import.meta.url)); const generatorPath = path.resolve( path.join(__dirname, "..", "generators", "mega-linter-custom-flavor") ); console.log("Yeoman generator used: " + generatorPath); env.run(generatorPath); return { status: 0 }; } // Run upgrader from v4 to v5 if (options.upgrade) { const megaLinterUpgrader = new MegaLinterUpgrader(); await megaLinterUpgrader.run(); return { status: 0 }; } if (options.codetotal) { console.warn( c.yellow( "[WARNING] CodeTotal is not actively maintained. The --codetotal integration is kept for legacy users and may be removed in a future major release.", ), ); const codeTotalRunner = new CodeTotalRunner(options); await codeTotalRunner.run(); return { status: 0 } } // Build MegaLinter docker image name with flavor and release version this.containerEngine = options.containerEngine || "docker"; if (!KNOWN_CONTAINER_ENGINES.includes(this.containerEngine)) { throw new Error( `Invalid container engine: ${this.containerEngine}. Supported engines are ${KNOWN_CONTAINER_ENGINES.join(", ")}.`, ); } const release = options.release in ["stable"] ? DEFAULT_RELEASE : options.release; const dockerImageName = // v4 retrocompatibility >> (options.flavor === "all" || options.flavor == null) && this.isv4(release) ? "nvuillam/mega-linter" : options.flavor !== "all" && this.isv4(release) ? `nvuillam/mega-linter-${options.flavor}` : // << v4 retrocompatibility // v5 retrocompatibility >> (options.flavor === "all" || options.flavor == null) && this.isv5(release) ? "megalinter/megalinter" : options.flavor !== "all" && this.isv5(release) ? `megalinter/megalinter-${options.flavor}` : // << v5 retrocompatibility options.flavor === "all" || options.flavor == null ? "ghcr.io/oxsecurity/megalinter" : `ghcr.io/oxsecurity/megalinter-${options.flavor}`; this.checkPreviousVersion(release); const dockerImage = options.image || `${dockerImageName}:${release}`; // Docker image can be directly sent in options // Check for docker installation const whichPromise = which(this.containerEngine); whichPromise.catch(() => { if (this.containerEngine === "podman") { console.error(` ERROR: Podman engine has not been found on your system. - To run MegaLinter locally, please install Podman: https://podman.io/docs/installation - To run Podman on CI, use a base image containing Podman engine`); } else { console.error(` ERROR: Docker engine has not been found on your system. - to run MegaLinter locally, please install docker desktop: https://www.docker.com/products/docker-desktop - to run docker on CI, use a base image containing docker engine`); } }); // Get platform to use with docker pull & run const imagePlatform = options.platform || "linux/amd64"; // Pull docker image if (options.nodockerpull !== true) { console.info(`Pulling docker image ${dockerImage} ... `); console.info( "INFO: this operation can be long during the first use of mega-linter-runner" ); console.info( "The next runs, it will be immediate (thanks to docker cache !)" ); const spawnResPull = spawnSync( this.containerEngine, ["pull", "--platform", imagePlatform, dockerImage], { detached: false, stdio: "inherit", windowsHide: true, windowsVerbatimArguments: true, } ); // Manage case when unable to pull docker image if (spawnResPull.status !== 0) { return { status: 2, errorMsg: `Unable to pull [${dockerImage}]: \n${JSON.stringify( spawnResPull, null, 2 )}`, }; } } else { console.log(`Skipped pull of ${dockerImage} (--nodockerpull used)`); } // Build docker run options const lintPath = path.resolve(options.path || "."); const dotenvPath = path.join(lintPath, ".env"); const envVarsFromDotenv = []; let emptyEnvFile = null; if (fs.existsSync(dotenvPath)) { const dotenvContent = await fs.readFile(dotenvPath, "utf8"); dotenvContent.split(/\r?\n/).forEach((line) => { const trimmedLine = line.trim(); if (!trimmedLine || trimmedLine.startsWith("#")) { return; } const equalIndex = trimmedLine.indexOf("="); if (equalIndex === -1) { return; } const key = trimmedLine.slice(0, equalIndex).trim(); const value = trimmedLine.slice(equalIndex + 1).trim(); if (!key) { return; } envVarsFromDotenv.push(`${key}=${value}`); }); const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "megalinter-")); emptyEnvFile = path.join(tmpDir, "empty.env"); await fs.writeFile(emptyEnvFile, ""); } const commandArgs = ["run", "--platform", imagePlatform]; const removeContainer = options["removeContainer"] ? true : options["noRemoveContainer"] ? false : true; if (removeContainer) { commandArgs.push("--rm"); } if (options["containerName"]) { commandArgs.push(...["--name", options["containerName"]]); } commandArgs.push(...["-v", "/var/run/docker.sock:/var/run/docker.sock:rw"]); commandArgs.push(...["-v", `${lintPath}:/tmp/lint:rw`]); if (emptyEnvFile) { commandArgs.push(...["-v", `${emptyEnvFile}:/tmp/lint/.env:ro`]); } if (options.fix === true) { commandArgs.push(...["-e", "APPLY_FIXES=all"]); } if (options.debug === true) { commandArgs.push(...["-e", "LOG_LEVEL=DEBUG"]); } if (options.json === true) { commandArgs.push(...["-e", "JSON_REPORTER=true"]); } if (envVarsFromDotenv.length > 0) { for (const envVarEqualsValue of envVarsFromDotenv) { commandArgs.push(...["-e", envVarEqualsValue]); } } if (options.env) { for (const envVarEqualsValue of expandEnvEntries(options.env)) { commandArgs.push(...["-e", envVarEqualsValue]); } } // Files only if (options.filesonly === true) { commandArgs.push(...["-e", "SKIP_CLI_LINT_MODES=project"]); } // list of files if ((options._ || []).length > 0) { commandArgs.push( ...["-e"], `MEGALINTER_FILES_TO_LINT=${options._.join(",")}` ); } commandArgs.push(dockerImage); // Call docker run console.log(`Command: ${this.containerEngine} ${commandArgs.join(" ")}`); const spawnOptions = { env: Object.assign({}, process.env), stdio: "inherit", windowsHide: true, }; const spawnRes = spawnSync(this.containerEngine, commandArgs, spawnOptions); // Output json if requested if (options.json === true) { const jsonOutputFile = path.join( lintPath, process.env.REPORT_OUTPUT_FOLDER || "report", "mega-linter-report.json" ); if (fs.existsSync(jsonOutputFile)) { const jsonRaw = await fs.readFile(jsonOutputFile, "utf8"); console.log(JSON.stringify(JSON.parse(jsonRaw))); } } return spawnRes; } isv4(release) { const isV4flag = release === "insiders" || release.includes("v4"); return isV4flag; } isv5(release) { const isV5flag = release.includes("v5"); return isV5flag; } checkPreviousVersion(release) { if (release.includes("v4") || release.includes("v5") || release.includes("v6")) { console.warn( c.bold( "#######################################################################" ) ); console.warn( c.bold(`MEGA-LINTER HAS A NEW ${DEFAULT_RELEASE} VERSION. Please upgrade to benefit of latest features :)`) ); console.warn( c.bold( "- Running the command at the root of your repo (requires node.js): npx mega-linter-runner@latest --upgrade" ) ); console.warn( c.bold( `- or replace ${release} by ${DEFAULT_RELEASE} in your scripts` ) ); console.warn( c.bold( "#######################################################################" ) ); } } }