Source: index.js

const core = require('@actions/core');
const exec = require('@actions/exec');
const fs = require('fs');
const io = require('@actions/io');
const os = require('os');
const path = require('path');
const process = require('process');
const tc = require('@actions/tool-cache');


/**
 * Get full path to a <pre>fs.Dirent</pre> directly under the given folder.
 * @see {@link https://nodejs.org/dist/latest-v12.x/docs/api/fs.html#fs_class_fs_dirent|fs.Dirent}
 * @param {string} dirPath Path to folder to look into.
 * @param {Function} func Callback that returns a boolean on when the
 * right <pre>fs.Dirent</pre> is found.
 * @returns {(string|null)} Path to dirent that satisfies func, else null.
 */
async function getDirentPath(dirPath, func) {
    const extractDir = await fs.promises.opendir(dirPath);
    for await (const dirent of extractDir) {
        if (func(dirent)) {
            return path.join(dirPath, dirent.name);
        }
    }
    return null;
}

/**
 * Get the file path to a file in the extracted repository root folder.
 * @param {string} fileName Filename to get from extracted repository root.
 * @param {string} extractedPath Path to extracted tar/zip repository folder.
 * @throws {MissingFileError} If filename not found directly under the 
 * repository root directory.
 */
async function getRepoRootFile(fileName, extractedPath) {
    // Tarball extracts to repo name + git ref sub-folder
    const srcFolder = await getDirentPath(
        extractedPath,
        (dirent) => {return dirent.isDirectory()},
    );
    if (srcFolder) {
        const filePath = await getDirentPath(
            srcFolder,
            (dirent) => {return dirent.isFile() && dirent.name == fileName},
        );
        if (filePath) {
            return filePath;
        }
    }

    const globPath = path.join(extractedPath, '*', fileName)
    throw {
        'name': 'MissingFileError',
        'message': `Could not find ${globPath}`,
    };
}

/**
 * 
 * @typedef envPaths
 * @type object
 * @property {string[]} ENV_VAR_NAMES... Array of paths to add per
 * environment variable.
 */

/**
 *
 * Installs rez.
 *
 * Fetches from GitHub tools cache if previously installed, else extract
 * and install from the given GitHub repository link and Git ref.
 *
 * @param {string} rezGitRepo "user or org"/"repository name"
 * @param {string} gitRef master or commit hash or tag name or branch name.
 * @returns {envPaths} Environment variable names and paths to add for
 * them to setup the installed/cached rez install.
 */
async function installRez(rezGitRepo, gitRef) {
    var rezInstallPath = tc.find(rezGitRepo, gitRef);
    if (rezInstallPath.length) {
        var manifestData = null;
        const manifestPath = path.join(rezInstallPath, 'setup.json')
        try {
            manifestData = fs.readFileSync(manifestPath);
        } catch (error) {
            if (error.code != 'ENOENT') throw error
        }
        if (manifestData) {
            return JSON.parse(manifestData);
        };
    }

    let exeArgs = [];
    let filePath = '';
    let rezInstall = {};
    const binFolder = ((process.platform == 'win32') ? 'Scripts': 'bin');

    const downloadURL = `https://github.com/${rezGitRepo}/archive/${gitRef}.tar.gz`;
    core.info(`Downloading "${downloadURL}"...`);

    const rezTarPath = await tc.downloadTool(downloadURL);
    rezInstallPath = await tc.extractTar(rezTarPath);

    try {
        filePath = await getRepoRootFile('install.py', rezInstallPath);
        exeArgs = ['python', filePath, rezInstallPath];
        rezInstall['PATH'] = [path.join(rezInstallPath, binFolder, 'rez')];
    } catch (error) {
        if (error.name != 'MissingFileError') {
            throw error
        }
        exeArgs = ['pip', 'install', '--target', rezInstallPath];
        filePath = await getRepoRootFile('setup.py', rezInstallPath);
        exeArgs.push(path.dirname(filePath));
        rezInstall['PATH'] = [path.join(rezInstallPath, binFolder)];
        rezInstall['PYTHONPATH'] = [rezInstallPath];
    }
    // const installCommand = exeArgs.join(" ")
    const installExe = exeArgs.shift()

    core.info("Installing...")
    // core.debug(`${installCommand}`);
    await exec.exec(installExe, exeArgs);

    fs.writeFileSync(
        path.join(rezInstallPath, 'setup.json'),
        JSON.stringify(rezInstall),
    )
    await tc.cacheDir(rezInstallPath, rezGitRepo, gitRef);
    core.debug(`...(cached) "${rezInstallPath}" with setup.json`);

    return rezInstall;
}

/**
 * Create rez packages paths.
 *
 * These typically are:
 * <ul font-family=monospace>
 *  <li>$HOME/packages</li>
 *  <li>$HOME/.rez/packages/int</li>
 *  <li>$HOME/.rez/packages/ext</li>
 * </ul>
 */
async function makePackagesPaths() {
    let output = '';

    await exec.exec('rez', ['config', 'packages_path'], {
        listeners: {stdout: (data) => (output += data.toString())},
    })
    for (line of output.trim().split(os.EOL)) {
        await io.mkdirP(line.replace(/^- /, ''));
    }
}

/**
 * Add given environment variable paths to current process.env
 * @param {object} envPaths Environment names mapped to a list of paths to add.
 */
function addPathsToEnvs(envPaths) {
    let paths = [];
    let newPath = '';
    let currentPath = '';

    for (varName in envPaths) {
        if (varName == 'PATH') {
            // Make rez bin available for binding later, if any
            envPaths[varName].forEach(element => {core.addPath(element)});
        } else {
            // Add to current process.env so exec.exec will also pick it up
            currentPath = process.env[varName];
            if (currentPath) {
                paths = currentPath.split(path.delimiter);
                paths.concat(envPaths[varName]);
            } else {
                paths = envPaths[varName];
            }
            newPath = paths.join(path.delimiter);
            core.exportVariable(varName, newPath);
            currentPath = newPath;
        }
    }
}

/**
 * Installs or fetch cached rez install. Setup packages path and binds if any.
 */
async function run() {
    try {
        // Export relevant env vars for a rez install
        addPathsToEnvs(
            await installRez(core.getInput('source'), core.getInput('ref'))
        );

        // Create all packages_path folders
        if (core.getInput('makePackagesPaths')) {
            makePackagesPaths();
        }

        const binds = core.getInput('binds');
        if (binds.length) {
            // Create local packages path
            let output = '';
            await exec.exec('rez', ['config', 'local_packages_path'], {
                listeners: {stdout: (data) => (output += data.toString())},
            })
            await io.mkdirP(output.trimRight());

            // Create bind per package name (comma separated, remove whitespace)
            for (pkg of binds.trim().split(",")) {
                await exec.exec('rez', ['bind', pkg.trim()]);
            }
        }
    } catch (error) {
        core.setFailed(error.message);
    }
}

run();