🏡 index : cold-air-networks/mirror-jetstream.git

#! /usr/bin/env node

import commandLineArgs from "command-line-args";
import fs from "fs";
import { fileURLToPath } from "url";
import path from "path";

import { logError, logCommand, printHelp, runTest, sh, logInfo } from "./helper.mjs";

const FILE_PATH = fileURLToPath(import.meta.url);
const SRC_DIR = path.dirname(path.dirname(FILE_PATH));

const optionDefinitions = [
  { name: "help", alias: "h", description: "Print this help text." },
  { name: "diff", type: String, description: "A git commit range to determine which builds to run (e.g. main...HEAD)." },
];

const options = commandLineArgs(optionDefinitions);

if ("help" in options)
  printHelp(optionDefinitions);


if (options.diff) {
    const { stdoutString } = await sh("git", "diff", "--name-only", options.diff);
    const changedDirs = new Set();
    const changedFiles = stdoutString.trim().split("\n");
    for (const file of changedFiles) {
        let currentDir = path.dirname(file)
        while (currentDir !== ".") {
            changedDirs.add(path.join(SRC_DIR, currentDir));
            currentDir = path.dirname(currentDir);
        }
    }
    options.changedDirs = changedDirs;
} else {
    options.changedDirs = undefined;
}

async function findPackageJsonFiles(dir, accumulator=[]) {
    const dirEntries = fs.readdirSync(dir, { withFileTypes: true });
    for (const dirent of dirEntries) {
        if (dirent.name === "node_modules" || dirent.name === ".git")
            continue;
        const fullPath = path.join(dir, dirent.name);
        if (dirent.isDirectory()) {
            // Ignore third-party git dirs.
            if (fs.existsSync(path.join(fullPath, ".git"))) {
                continue;
            }
            findPackageJsonFiles(fullPath, accumulator);
        } else if (dirent.name === "package.json") {
            accumulator.push(fullPath)
        }
    }
    return accumulator;
}

async function runBuilds() {
    const packageJsonFiles = await findPackageJsonFiles(SRC_DIR);
    let success = true;

    logInfo(`Found ${packageJsonFiles.length} package.json files`);
    console.log(packageJsonFiles)
    let filteredPackageJsonFiles = packageJsonFiles;
    if (options.changedDirs?.size === 0) {
        logInfo("No file changes detected, skipping all");
    }
    if (options.changedDirs?.size > 0) {
        filteredPackageJsonFiles = packageJsonFiles.filter(file => {
            const dir = path.dirname(file);
            return options.changedDirs.has(dir);
        });
        logInfo(`Found ${filteredPackageJsonFiles.length} modified package.json files to build`);
    }

    for (const file of filteredPackageJsonFiles) {
        const content = fs.readFileSync(file, "utf-8");
        const packageJson = JSON.parse(content);
        if (!packageJson.scripts?.build) {
            continue;
        }

        const dir = path.dirname(file);
        const relativeDir = path.relative(SRC_DIR, dir);
        const testName = `Building ./${relativeDir}:`;
        
        const buildTask = async () => {
            const oldCWD = process.cwd();
            try {
                logCommand("cd", dir);
                process.chdir(dir);
                await sh("npm", "ci");
                await sh("npm", "run", "build");
            } finally {
                process.chdir(oldCWD);
                await sh("git", "reset", "--hard");
            }
        };
        
        success &&= await runTest(testName, buildTask);
    }

    if (!success) {
        logError("One or more builds failed.");
        process.exit(1);
    }
}

setImmediate(runBuilds);