@gobstones/gobstones-scripts
Version:
Scripts to abstract away build configuration of Gobstones Project's libraries and modules.
204 lines (168 loc) • 8.25 kB
text/typescript
/*
* *****************************************************************************
* Copyright (C) National University of Quilmes 2018-2024
* Gobstones (TM) is a trademark of the National University of Quilmes.
*
* This program is free software distributed under the terms of the
* GNU Affero General Public License version 3.
* Additional terms added in compliance to section 7 of such license apply.
*
* You may read the full license at https://gobstones.github.io/gobstones-guidelines/LICENSE.
* *****************************************************************************
*/
/**
* ----------------------------------------------------
* @module Helpers
* @author Alan Rodas Bonjour <alanrodas@gmail.com>
*
* @internal
* ----------------------------------------------------
*/
import fs from 'fs';
import path from 'path';
import { isWindows } from './isWindows';
import { logger } from './Logger';
import { PackageJsonReader } from './PackageJsonReader';
import { ExecutableScriptDefinition, PackageManagerDefinition } from '../Config/config';
/**
* Get the path to one of the bin scripts exported by a package.
* If the bin name is different from the package name, it should
* be given, if not given, the name of the binary is assumed to be the same
* of that of the package.
*
* @param projectRootPath - The currently identified project's root path.
* @param packageManagerDefinition - The definition to all available package managers.
* @param packageName - The name of the package holding the binary.
* @param binName - The name of the binary that it's intended to be retrieved.
*
* @returns The information for the binary execution, or undefined if no binary was found.
*/
export const getBin = (
projectRootPath: string,
packageManagerDefinition: PackageManagerDefinition,
packageName: string,
binName?: string
): ExecutableScriptDefinition | undefined => {
if (!binName) {
binName = packageName;
}
// Hold the result of the file found for caching it at the end.
let result: Partial<ExecutableScriptDefinition> = {};
logger.debug(`[getBin]: Attempting to find binary file "${binName}" for the package "${packageName}"`, 'magenta');
try {
logger.debug(`[getBin]: Trying to retrieve the package.json information through require.resolve`, 'magenta');
// Attempt to get the bin by resolving the package.json
const packageJsonPath = require.resolve(`${packageName}/package.json`);
const packageRootDir = path.dirname(packageJsonPath);
const binFile = getBinFile(packageJsonPath, packageRootDir, binName);
result = {
scriptFile: binFile,
command: `node --experimental-vm-modules ${binFile}`,
mode: 'node'
};
} catch {
logger.debug(`[getBin]: Could not detect through require.resolve`, 'magenta');
logger.debug(`[getBin]: Attempting to find binary in binary folders of current package manager`, 'magenta');
for (const modulesFolder of packageManagerDefinition.modulesFolders) {
logger.debug(`[getBin]: Verifying at: ${modulesFolder}`);
const packageNamePath = path.join(projectRootPath, modulesFolder, packageName, 'package.json');
if (fs.existsSync(packageNamePath)) {
logger.debug(`[getBin]: Found a package.json file at: ${packageNamePath}`, 'magenta');
const packageRootDir = path.dirname(packageNamePath);
const binFile = getBinFile(packageNamePath, packageRootDir, binName);
result = {
scriptFile: binFile,
command: `node --experimental-vm-modules ${binFile}`,
mode: 'node'
};
break;
} else {
logger.debug(`[getBin]: No package with name found. Attempting to find native binary.`, 'magenta');
const binFile = path.join(projectRootPath, modulesFolder, '.bin', binName || packageName);
if (fs.existsSync(binFile)) {
// We found a possible binary, but in some cases
// this are actually node files that were not reported.
// We need to check the heading to see that.
const contents = fs.readFileSync(binFile).toString();
const heading = contents.length > 0 ? contents.split('\n')[0] : '';
if (heading.includes('node')) {
// If the heading mentions node, it's a javascript file.
logger.debug(`[getBin]: Found a javascript executable at: ${binFile}`, 'magenta');
result = {
scriptFile: binFile,
command: `node --experimental-vm-modules ${binFile}`,
mode: 'node'
};
break;
}
// If not, it's a binary file, but there may be
// many depending on the OS. Verify to see if we have
// a powershell, command, or sh.
if (isWindows()) {
if (fs.existsSync(`${binFile}.ps1`)) {
logger.debug(`[getBin]: Found a powershell executable at: ${binFile}.ps1`, 'magenta');
result = {
scriptFile: `${binFile}.ps1`,
command: `powershell ${binFile}.ps1`,
mode: 'pwsh'
};
break;
}
if (fs.existsSync(`${binFile}.cmd`)) {
logger.debug(
`[getBin]: Found a windows command line executable at: ${binFile}.cmd`,
'magenta'
);
result = {
scriptFile: `${binFile}.cmd`,
command: `${binFile}.cmd`,
mode: 'cmd'
};
break;
}
}
// Default to shell script
logger.debug(`[getBin]: Found a shell script at: ${binFile}`, 'magenta');
result = {
scriptFile: binFile,
command: `sh ${binFile}`,
mode: 'sh'
};
break;
}
}
}
}
if (Object.keys(result).length === 0) {
logger.debug(`[getBin]: Could not find a binary file`, 'magenta');
return undefined;
}
return Object.assign(result, {
packageName,
binName
}) as ExecutableScriptDefinition;
};
/**
* Returns the bin file for a given package and binary file.
*
* @param packageJsonPath - The path of the requested package's package.json
* @param packageRootDir - The path to root of the package that contains the binary directory
* @param binName - The name of the binary that we want the path of.
*
* @returns The path to the binary file.
*
* @internal
*/
const getBinFile = (packageJsonPath: string, packageRootDir: string, binName: string): string => {
logger.debug(`[getBin] Attempting to find the binary file: ${binName} using root: ${packageJsonPath}`, 'magenta');
const pkgJsonReader = new PackageJsonReader(packageJsonPath);
let pkgBinDefinition = pkgJsonReader.getValueAt('bin') as string | Record<string, string>;
const binRelativeToPackgeInJson: string =
pkgBinDefinition && typeof pkgBinDefinition === 'object'
? (pkgBinDefinition = pkgBinDefinition[binName])
: pkgBinDefinition;
logger.debug(`[getBin] Found a package.json with bin file declared as: ${binRelativeToPackgeInJson}`, 'magenta');
const fullBinPath = path.join(packageRootDir, binRelativeToPackgeInJson);
logger.debug(`[getBin] Path for binary found as: ${fullBinPath}`, 'magenta');
return fullBinPath;
};