@gobstones/gobstones-scripts
Version:
Scripts to abstract away build configuration of Gobstones Project's libraries and modules.
1,122 lines (1,110 loc) • 131 kB
JavaScript
import childProcess from 'child_process';
import path from 'path';
import fs$1 from 'fs-extra';
import tsconfigJs from 'tsconfig.js';
import fs from 'fs';
import colors from 'ansi-colors';
import commandExists from 'command-exists';
import { stripIndent } from 'common-tags';
/*
* *****************************************************************************
* 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.
* *****************************************************************************
*/
/**
* An error that occurs when there is a problem in the filesystem state.
*
* @group Errors
*/
class FileSystemError extends Error {
/**
* Create a FileSystemError.
*
* @param message - A string with the error message.
*/
constructor(message) {
super(message);
// It restores the prototype chain
Object.setPrototypeOf(this, new.target.prototype);
}
}
/*
* *****************************************************************************
* 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 Config
* @author Alan Rodas Bonjour <alanrodas@gmail.com>
*/
/**
* The tool's version number
*
* This needs to be here in order to avoid reading the package.json, which
* is not reliable between different implementations of package managers
* and that may lead to false readings.
*/
const version = '0.9.3';
/**
* The tool's test server address.
*
* This line is added to the .npmrc file of projects
* when they are created in test mode. It can also be used
* in any project that attempts to use the version from
* the verdaccio server instead of the regular one.
*/
const testServer = '@gobstones:registry=http://localhost:4567';
/*
* *****************************************************************************
* 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
* ----------------------------------------------------
*/
/**
* Answers if the platform is windows.
*
* @returns `true` if the current OS is Windows, `false` otherwise.
*/
const isWindows = () => { var _a; return process && (process.platform === 'win32' || /^(msys|cygwin)$/.test((_a = process.env.OSTYPE) !== null && _a !== void 0 ? _a : '')); };
/*
* *****************************************************************************
* 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
* ----------------------------------------------------
*/
/**
* The different available log levels.
*/
var LogLevel;
(function (LogLevel) {
LogLevel["Error"] = "error";
LogLevel["Warn"] = "warn";
LogLevel["Info"] = "info";
LogLevel["Debug"] = "debug";
})(LogLevel || (LogLevel = {}));
/**
* This class provides a centralize way to report messages in the terminal through
* the application, including messages that are always printed, debug information,
* error messages and others.
*/
class Logger {
/** Create a new logger */
constructor(level) {
this.level = level;
this._on = true;
}
/**
* Turn this logger on. If already on, nothing happens.
*
* @returns the receiver logger.
*/
on() {
this._on = true;
return this;
}
/**
* Turn this logger off. If already off, nothing happens.
*
* @returns the receiver logger.
*/
off() {
this._on = false;
return this;
}
/**
* Log a message as an error, if the level allows it and the logger is on.
*
* @param msg - The message to print.
* @param style - A style on which to print the message
*
* @returns the receiver logger.
*/
error(msg, style) {
this.print(msg, style, LogLevel.Error);
return this;
}
/**
* Log a message as an warning, if the level allows it and the logger is on.
*
* @param msg - The message to print.
* @param style - A style on which to print the message
*
* @returns the receiver logger.
*/
warn(msg, style) {
this.print(msg, style, LogLevel.Warn);
return this;
}
/**
* Log a message as information, if the level allows it and the logger is on.
*
* @param msg - The message to print.
* @param style - A style on which to print the message
*
* @returns the receiver logger.
*/
info(msg, style) {
this.print(msg, style, LogLevel.Info);
return this;
}
/**
* Log a message as debug information, if the level allows it and the logger is on.
*
* @param msg - The message to print.
* @param style - A style on which to print the message
*
* @returns the receiver logger.
*/
debug(msg, style) {
this.print(msg, style, LogLevel.Debug);
return this;
}
/**
* Log a message regardless of the active level, but only if the logger is on.
*
* @param msg - The message to print.
* @param style - A style on which to print the message
*
* @returns the receiver logger.
*/
log(msg, style) {
this.print(msg, style);
return this;
}
/**
* Print the given message in the terminal, if the log level allows it and the logger is on.
*
* @param msg - The message to print
* @param style - The style to use for printing.
* @param actualLevel - The actual level on which the message should be printed.
*/
print(msg, style, actualLevel) {
if (this._on && (!actualLevel || this.isLevelGeqThan(actualLevel, this.level))) {
if (style) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
msg = colors[style](msg);
}
// eslint-disable-next-line no-console
console.log(msg);
}
}
/**
* Answers if the first level is greater or equal than the second one.
*
* @param level1 - The first level to compare
* @param level2 - The second level to compare.
*
* @returns `true` if the first level is greater or equal than the second, `false` otherwise.
*/
isLevelGeqThan(level1, level2) {
return (level1 === LogLevel.Error ||
(level1 === LogLevel.Warn && level2 !== LogLevel.Error) ||
(level1 === LogLevel.Info && level2 !== LogLevel.Error && level2 !== LogLevel.Warn) ||
level2 === LogLevel.Debug);
}
}
/**
* The default {@link Logger}.
*/
const logger = new Logger(LogLevel.Error);
/*
* *****************************************************************************
* 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
* ----------------------------------------------------
*/
/**
* A package json reader creates a wrapper around the action of reading a
* JSON file and accessing the properties of it in an easy fashion.
* If reading the file fails, the returned reader will always return undefined
* for any property intended to be read.
*/
class PackageJsonReader {
/**
* Create a new PackageJsonReader.
*
* @param packageJsonLocation - The location of the file to read.
*/
constructor(packageJsonLocation) {
logger.debug(`[PackageJsonReader]: Attempting to read package.json configuration at: ${packageJsonLocation}`, 'yellow');
try {
const contents = fs.readFileSync(packageJsonLocation);
this._contents = JSON.parse(contents.toString());
}
catch (_a) {
logger.debug(`[PackageJsonReader]: File package.json not found at location`, 'yellow');
this._contents = undefined;
}
}
/**
* Return the associated value for a given key.
*/
getValueAt(key) {
let nextValue = this._contents;
const keys = key.split('.');
for (const k of keys) {
nextValue = nextValue ? nextValue[k] : undefined;
}
return nextValue;
}
}
/*
* *****************************************************************************
* 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
* ----------------------------------------------------
*/
/**
* 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.
*/
const getBin = (projectRootPath, packageManagerDefinition, packageName, binName) => {
if (!binName) {
binName = packageName;
}
// Hold the result of the file found for caching it at the end.
let result = {};
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 (_a) {
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
});
};
/**
* 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, packageRootDir, binName) => {
logger.debug(`[getBin] Attempting to find the binary file: ${binName} using root: ${packageJsonPath}`, 'magenta');
const pkgJsonReader = new PackageJsonReader(packageJsonPath);
let pkgBinDefinition = pkgJsonReader.getValueAt('bin');
const binRelativeToPackgeInJson = 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;
};
/*
* *****************************************************************************
* 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
* ----------------------------------------------------
*/
/**
* Returns the `@gobstones/gobstones-scripts` root path. That is, the path to
* the module in the users `node_modules` folder.
*
* @param os - The OS that is currently discovered as environment.
* @param projectRootPath - The currently identified project's root path.
*
* @returns The root path of the `gobstones-scripts` project.
*/
const getGobstonesScriptsRootPath = (os, projectRootPath) => {
logger.debug(`[getGobstonesScriptsRootPath]: Attempting to recover the gobstones-scripts folder location`, 'green');
// Return if previously calculated
let detectedScriptRootPath = '';
try {
logger.debug(`[getGobstonesScriptsRootPath]: Attempting through require.resolve method`, 'green');
// First attempt by using require.resolve
// This method is not reliable and may fail
// depending on setup
detectedScriptRootPath = path.join(path.dirname(require.resolve('@gobstones/gobstones-scripts/package.json')));
logger.debug(`[getGobstonesScriptsRootPath]: Found through require.resolve at: ${detectedScriptRootPath}`, 'green');
return detectedScriptRootPath;
}
catch (_a) {
logger.debug(`[getGobstonesScriptsRootPath]: require.resolve method failed`, 'green');
// First, check if we are running from the project itself, that may be
// the same folder, or a folder further up
let possibleRootPath = projectRootPath;
const pathStartBySystem = os === 'windows' ? ((possibleRootPath === null || possibleRootPath === void 0 ? void 0 : possibleRootPath.match(/^[A-Z]:\\/)) ? possibleRootPath.substring(0, 3) : 'C:\\') : '/';
logger.debug(`[getGobstonesScriptsRootPath]: Verifying if "${possibleRootPath}" is the gobstones-scripts root folder`, 'green');
while (possibleRootPath !== pathStartBySystem) {
const pkgReader = new PackageJsonReader(path.join(possibleRootPath, 'package.json'));
if (pkgReader.getValueAt('name') === '@gobstones/gobstones-scripts') {
// Found
detectedScriptRootPath = possibleRootPath;
logger.debug(`[getGobstonesScriptsRootPath]: Found gobstones-scripts at: ${possibleRootPath}`, 'green');
return detectedScriptRootPath;
}
possibleRootPath = path.dirname(possibleRootPath);
logger.debug(`[getGobstonesScriptsRootPath]: Not found. Attempting with next folder: ${possibleRootPath}`, 'green');
}
logger.debug(`[getGobstonesScriptsRootPath]: Reached root directory and not found`, 'green');
// Return the path to a particular location
// based on the root folder string, the
// file does not necessarily exists.
const getRootPath = (command) => {
try {
const processGettedPath = childProcess.execSync(command).toString().trim();
return path.join(processGettedPath || '', '@gobstones', 'gobstones-scripts');
}
catch (_a) {
return '';
}
};
// If running globally, try to find a configuration
// of the particular tool. That means, get the directory of
// tool installed (which may fail if not installed), and
// ensure the directory exists.
const attemptForCommandWithLocations = (command, locations) => {
if (commandExists.sync(command)) {
logger.debug(`[getGobstonesScriptsRootPath]: Attempting to see if gobstones-scripts ` +
`is installed globally through ${command}`, 'green');
for (const location of locations) {
logger.debug(`[getGobstonesScriptsRootPath]: Attempting with location from "${location}"`, 'green');
const rootPath = getRootPath(location);
if (rootPath && fs.existsSync(rootPath)) {
logger.debug(`[getGobstonesScriptsRootPath]: Found. Setting gobstones-scripts root to: ${rootPath}`, 'green');
return rootPath;
}
logger.debug(`[getGobstonesScriptsRootPath]: Not found, attempting next location.`, 'green');
}
}
logger.debug(`[getGobstonesScriptsRootPath]: Not found at any location for command ${command}`, 'green');
return undefined;
};
let indexToLocation = 0;
const actionsToFindLocation = [
() => attemptForCommandWithLocations('npm', [
'npm root --location=project',
'npm root --location=user',
'npm root --location=global'
]),
() => attemptForCommandWithLocations('pnpm', ['pnpm root', 'pnpm root --global'])
];
while (!detectedScriptRootPath && indexToLocation < actionsToFindLocation.length) {
detectedScriptRootPath = actionsToFindLocation[indexToLocation]() || '';
indexToLocation++;
}
if (detectedScriptRootPath) {
return detectedScriptRootPath;
}
else {
logger.debug(`[getGobstonesScriptsRootPath]: Could not find location through any of the commands.`, 'green');
}
// Also get a default path, which requires
// the project root path, and attempt to find
// the project under node_modules.
const defaultPath = path.join(projectRootPath, 'node_modules', '@gobstones', 'gobstones-scripts');
if (fs.existsSync(defaultPath)) {
logger.debug(`[getGobstonesScriptsRootPath]: Returning default at: ${defaultPath}`, 'green');
return defaultPath;
}
}
logger.debug(`[getGobstonesScriptsRootPath]: Could not find the gobstones-script project library`, 'green');
throw Error('cannot find script root');
};
/*
* *****************************************************************************
* 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
* ----------------------------------------------------
*/
/**
* Return the package manager in use based different features. First, by
* identifying the current runner through the `npm_config_user_agent` environment
* variable. It such variable is not set, which is common for global runs,
* attempts to identify the runner by locating the global `gobstones-scripts`
* command. If no match is found, defaults to `npm`.
*
* @param availablePackageManagers - The list of all available package managers.
* @param defaultPackageManager The default package manager to use.
*
* @returns The package manager in use
*/
const getInUsePackageManager = (availablePackageManagers, defaultPackageManager = 'npm') => {
logger.debug(`[getInUsePackageManager]: Attempting to locate package manager in use`, 'gray');
const userAgent = process.env.npm_config_user_agent;
let whichFile = '';
if (!userAgent) {
logger.debug(`[getInUsePackageManager]: No node user agent found, checking if gobstones-scripts was run globally`, 'gray');
try {
logger.debug(`[getInUsePackageManager]: Attempting to find global gobstones-script binary through "which" command`, 'gray');
whichFile = childProcess
.spawnSync('which gobstones-scripts', { shell: true })
.output.toString()
.replace(/,/g, '')
.trim();
}
catch (_a) {
logger.debug(`[getInUsePackageManager]: "which" may not be installed in this system.`, 'cyan');
}
}
const value = userAgent !== null && userAgent !== void 0 ? userAgent : whichFile;
logger.debug(`[getInUsePackageManager]: Found gobstones-scripts at: ${value}`, 'gray');
let result;
for (const pm of Object.keys(availablePackageManagers)) {
if (value === null || value === void 0 ? void 0 : value.includes(availablePackageManagers[pm].cmd)) {
if (commandExists.sync(availablePackageManagers[pm].cmd)) {
result = availablePackageManagers[pm].cmd;
break;
}
}
}
if (!result) {
logger.debug(`[getInUsePackageManager]: Could not determine package manager. Using default: ${defaultPackageManager}`, 'gray');
return defaultPackageManager;
}
else {
logger.debug(`[getInUsePackageManager]: Detected package manager in use as: ${result}`, 'cyan');
return result;
}
};
/*
* *****************************************************************************
* 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
* ----------------------------------------------------
*/
/**
* Returns the current project's root path by attempting to identify it.
* If it cannot be identified, it returns the current working directory
* as the project's root path.
*
* @param os - The OS that is currently discovered as environment.
*
* @returns The project's root path.
*/
const getProjectRootPath = (os) => {
logger.debug(`[getProjectRootPath]: Attempting to recover the project root path`, 'cyan');
// The obvious choice is the the current directory
let possibleRootPath = process.env.PWD
? process.env.PWD
: process.cwd()
? process.cwd()
: path.resolve('.');
const pathStartBySystem = os === 'windows'
? // eslint-disable-next-line no-null/no-null
possibleRootPath.match(/^[A-Z]:\\/) !== null
? possibleRootPath.substring(0, 3)
: 'C:\\'
: '/';
// Attempt to find the root in current directory and above
// until no more directories are found
logger.debug(`[getProjectRootPath]: Trying to check if "${possibleRootPath}" is the project's root path`, 'cyan');
while (possibleRootPath && possibleRootPath !== pathStartBySystem) {
// A valid root is one such that there is a package.json
// with or without gobstones-scripts config section.
if (fs.existsSync(path.join(possibleRootPath, 'package.json'))) {
// Found
logger.debug(`[getProjectRootPath]: Found a package.json file at: "${possibleRootPath}"`, 'cyan');
logger.debug(`[getProjectRootPath]: Project's root path is: "${possibleRootPath}"`, 'cyan');
return possibleRootPath;
}
// Go up
possibleRootPath = path.dirname(possibleRootPath);
logger.debug(`[getProjectRootPath]: No package.json found, attempting with new folder: ${possibleRootPath}"`, 'cyan');
}
possibleRootPath = process.env.PWD ? process.env.PWD : process.cwd() ? process.cwd() : path.resolve('.');
logger.debug(`[getProjectRootPath]: Reached root directory and no project root found.`, 'cyan');
logger.debug(`[getProjectRootPath]: Considering current working directory as project's root`, 'cyan');
logger.debug(`[getProjectRootPath]: Project's root path set to: ${possibleRootPath}`, 'cyan');
return possibleRootPath;
};
/*
* *****************************************************************************
* 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
* ----------------------------------------------------
*/
/**
* Locate the tooling file to use for the given type definition.
*
* @param projectRoot - The root directory for the project.
* @param gobstonesScriptsProjectsRoot - The root directory for the gobstones-scripts package.
* @param fileDef - The file definition to locate the tooling for.
*
* @returns The tooling file path for the given file definition, or undefined if none could be found.
*/
const getToolingFile = (projectRoot, gobstonesScriptsProjectsRoot, fileDef) => {
logger.debug(`[getToolingFile] Attempting to determine which configuration file to use for: ${fileDef.name}`, 'blue');
if (fs.existsSync(path.join(projectRoot, fileDef.projectLocation[0]))) {
logger.debug(`[getToolingFile] Found file at the root of the project: ${path.join(projectRoot, fileDef.projectLocation[0])}`, 'blue');
return path.join(projectRoot, fileDef.projectLocation[0]);
}
else if (fs.existsSync(path.join(gobstonesScriptsProjectsRoot, fileDef.gobstonesScriptsLocation[0]))) {
logger.debug(`[getToolingFile] Found file at the gobstones-scripts library: ${path.join(gobstonesScriptsProjectsRoot, fileDef.gobstonesScriptsLocation[0])}`, 'blue');
return path.join(gobstonesScriptsProjectsRoot, fileDef.gobstonesScriptsLocation[0]);
}
logger.debug(`[getToolingFile] No file found. ${fileDef.name === 'tsConfigJSON'
? 'This is Ok in this case, not to worry.'
: 'A file should have been found. Something wrong happened.'}`, 'blue');
return undefined;
};
/*
* *****************************************************************************
* 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
* ----------------------------------------------------
*/
/**
* Answers if the platform is macOS.
*
* @returns `true` if the current OS is MacOS, `false` otherwise.
*/
const isMacos = () => process && process.platform === 'darwin';
/*
* *****************************************************************************
* 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 Config
* @author Alan Rodas Bonjour <alanrodas@gmail.com>
*/
// ==========================================
// #endregion Script File Types
// ==========================================
// ==========================================
// #region Config
// ==========================================
/**
* This class represents the main configuration object generated by the application.
* The configuration is automatically loaded once the {@link init} method is called.
* This object is also the main entry point to obtain configuration options of the tool
* as to obtain the located directories, tooling files and obtain location for the
* executable scripts for different tools.
*/
class Config {
// ------------------------------------------
// #endregion Private Properties
// ------------------------------------------
/**
* Create a new instance of the configuration.
*/
constructor() {
/*
* We need to set the logger on if debug was sent from the CLI,
* as this i the first thing that happens, even after running any code.
* This is clearly a violation of the concern of 'config', but
* there doesn't seem to be a better way without recurring to
* dynamic imports, which implies changing the build system.
*/
if (process.argv.includes('-D') || process.argv.includes('--debug')) {
logger.level = LogLevel.Debug;
logger.on();
}
logger.debug('[config] Creating configuration object');
// has not yet been initialized
this._lastInitializationValues = undefined;
this._binaryFilesCache = {};
}
// ------------------------------------------
// #region Accessing
// ------------------------------------------
/** Returns the subpart of the configuration corresponding to package managers. */
get packageManagers() {
return this._packageManagers;
}
/** Returns subpart of the configuration corresponding to the environment. */
get environment() {
return this._environment;
}
/** Returns the subpart of the configuration corresponding to the different path locations. */
get locations() {
return this._locations;
}
/** Returns the subpart of the configuration corresponding to the current execution environment. */
get executionEnvironment() {
return this._executionEnvironment;
}
/** Returns the subpart of the configuration corresponding to the different project types. */
get projectTypes() {
return this._projectTypes;
}
/** The subpart of the configuration corresponding to the different project type filtered files. */
get filteredProjectTypes() {
return this._filteredProjectTypes;
}
get packageManager() {
return this._packageManagers[this._executionEnvironment.packageManager];
}
get projectType() {
return this._projectTypes[this._executionEnvironment.projectType];
}
get projectTypeFilteredFiles() {
return this._filteredProjectTypes[this._executionEnvironment.projectType];
}
// ------------------------------------------
// #endregion Accessing
// ------------------------------------------
// ------------------------------------------
// #region Initialization
// ------------------------------------------
/**
* Orchestrate the initialization of the Config object.
* This initialization is needed in order to access any of the
* sub-configuration sections, except for retrieving
* executable scripts.
*/
init(apiGivenProjectType, apiGivenPackageManager, debug, test, useLocalTsconfigJson) {
if (!this._lastInitializationValues) {
logger.debug(`[config] Initializing configuration from scratch`);
this._initAvailablePackageManagers();
this._detectEnvironment();
this._initializeLocations();
this._loadProjectTypeDefinitions();
this._loadProcessedProjectTypeDefinitions();
}
if (!this._lastInitializationValues ||
(apiGivenProjectType && this._lastInitializationValues.apiGivenProjectType !== apiGivenProjectType) ||
(apiGivenPackageManager &&
this._lastInitializationValues.apiGivenPackageManager !== apiGivenPackageManager) ||
(debug && this._lastInitializationValues.debug !== debug) ||
(test && this._lastInitializationValues.test !== test)) {
logger.debug(`[config] Already initialized, updating CLI/API parameters`);
this._initializeExecutionEnvironment(apiGivenProjectType, apiGivenPackageManager, debug, test, useLocalTsconfigJson);
this._lastInitializationValues = this._executionEnvironment;
}
return this;
}
// ------------------------------------------
// #endregion Initialization
// ------------------------------------------
// ------------------------------------------
// #region Public API
// ------------------------------------------
/**
* Change the current directory of the process to another one.
* Additionally, update the global configuration to match.
*
* @param dir - The directory to change to
*/
changeDir(dir) {
process.chdir(dir);
this._environment.workingDirectory = dir;
this._locations.projectRoot = dir;
return dir;
}
/**
* Return the information for executing a binary file, if it can be found
* by the configuration system. Additionally, and differently from the
* simple {@link getBin} helper, this method provides caching, as to not
* attempt to find the element twice.
*
* @param packageName - The package name that contains the binary file.
* @param binName - The binary file to execute.
*
* @returns The executable to run, or undefined if not found.
*/
getBinary(packageName, binName) {
if (!this._binaryFilesCache[`${packageName}-${binName}`]) {
this._binaryFilesCache[`${packageName}-${binName}`] = getBin(this._locations.projectRoot, this.packageManager, packageName, binName);
}
return this._binaryFilesCache[`${packageName}-${binName}`];
}
// ------------------------------------------
// #endregion Public API
// ------------------------------------------
/* ******************************************************** */
// ------------------------------------------
// #region Private Initialization
// ------------------------------------------
/** Initialize the different available package managers. */
_initAvailablePackageManagers() {
this._packageManagers = {
npm: {
name: 'npm',
cmd: 'npm',
install: 'npm install',
run: 'npx',
modulesFolders: ['node_modules'],
binFolders: ['node_modules/bin']
},
yarn: {
name: 'yarn',
cmd: 'yarn',
install: 'yarn install',
run: 'npx',
modulesFolders: ['node_modules'],
binFolders: ['node_modules/bin']
},
pnpm: {
name: 'pnpm',
cmd: 'pnpm',
install: 'pnpm install',
run: 'pnpm exec',
modulesFolders: ['node_modules', 'node_modules/@gobstones/gobstones-scripts/node_modules'],
binFolders: ['node_modules/bin', 'node_modules/@gobstones/gobstones-scripts/node_modules/bin']
}
};
}
/** Initialize the current environment from detected information. */
_detectEnvironment() {
var _a, _b;
this._environment = {
toolVersion: version,
toolTestServer: testServer,
operatingSystem: isWindows() ? 'windows' : isMacos() ? 'macos' : 'posix',
workingDirectory: (_b = (_a = process.env.PWD) !== null && _a !== void 0 ? _a : process.cwd()) !== null && _b !== void 0 ? _b : path.resolve('.'),
detectedPackageManager: getInUsePackageManager(this._packageManagers, 'npm')
};
}
/**
* Initialize the different locations by attempting to detect the current
* folder containing a project and the folder containing the gobstones-scripts Library.
*/
_initializeLocations() {
const projectRoot = getProjectRootPath(this._environment.operatingSystem);
const gobstonesScriptsRoot = getGobstonesScriptsRootPath(this._environment.operatingSystem, projectRoot);
this._locations = {
projectRoot,
gobstonesScriptsRoot,
gobstonesScriptsProjectsRoot: path.join(gobstonesScriptsRoot, 'project-types')
};
}
/**
* Initialize the current execution environment. This is obtained by a
* mix between possible CLI/API given parameters (such as using -t or -m),
* given as input, and the information read in the current's project package.json,
* as well as defaults in case no configuration is provided.
*
* Priority is given to CLI/API given, then the package.json configuration
* and lastly defaults.
*
* @param apiGivenProjectType - The CLI/API given value for project type to use, if any.
* @param apiGivenPackageManager - The CLI/API given value for package manager to use, if any.
* @param debug - The CLI/API given value to know if we are running in debug mode.
* @param test - The CLI/API given value to know if we are running in test mode.
* @param useLocalTsconfigJson - The CLI/API given value to know if we should use the default tsconfig.json file.
*/
_initializeExecutionEnvironment(apiGivenProjectType, apiGivenPackageManager, debug, test, useLocalTsconfigJson) {
var _a, _b, _c, _d, _e, _f;
if (apiGivenProjectType && !Object.keys(this._projectTypes).includes(apiGivenProjectType)) {
throw new Error('Invalid project type');
}
if (apiGivenPackageManager && !Object.keys(this._packageManagers).includes(apiGivenPackageManager)) {
throw new Error('Invalid package manager');
}
const pkgReader = new PackageJsonReader(path.join(this._locations.projectRoot, 'package.json'));
this._executionEnvironment = {
projectType: ((_a = apiGivenProjectType !== null && apiGivenProjectType !== void 0 ? apiGivenProjectType : pkgReader.getValueAt('config.gobstones-scripts.type')) !== null && _a !== void 0 ? _a : 'Library'),
packageManager: ((_b = apiGivenPackageManager !== null && apiGivenPackageManager !== void 0 ? apiGivenPackageManager : pkgReader.getValueAt('config.gobstones-scripts.manager')) !== null && _b !== void 0 ? _b : 'npm'),
useFullPaths: ((_c = pkgReader.getValueAt('config.gobstones-scripts.use-full-paths')) !== null && _c !== void 0 ? _c : false),
useLocalTsconfigJson: ((_d = useLocalTsconfigJson !== null && useLocalTsconfigJson !== void 0 ? useLocalTsconfigJson : pkgReader.getValueAt('config.gobstones-scripts.use-local-tsconfig-json')) !== null && _d !== void 0 ? _d : fs.existsSync(path.join(this._locations.projectRoot, 'package.json'))),
debug: ((_e = debug !== null && debug !== void 0 ? debug : pkgReader.getValueAt('config.gobstones-scripts.debug')) !== null && _e !== void 0 ? _e : false),
test: ((_f = test !== null && test !== void 0 ? test : pkgReader.getValueAt('config.gobstones-scripts.test')) !== null && _f !== void 0 ? _f : false)
};
}
/**
* Initialize the different project type definitions with all their
* file information.
*/
_loadProjectTypeDefinitions() {
this._projectTypes = {
Library: this._joinProjectTypeDefinitions(this._getCommonProjectTypeDefinition('Library', ['src', 'test', 'packageJson', 'typescript', 'rollup', 'nps'], ['jestproxies', 'vite', 'stories', 'storybook', 'demos'])),
CLILibrary: this._joinProjectTypeDefinitions(this._getCommonProjectTypeDefinition('CLILibrary', ['src', 'test', 'packageJson', 'typescript', 'rollup', 'nps'], ['jestproxies', 'vite', 'stories', 'storybook', 'demos'])),
ReactLibrary: this._joinProjectTypeDefinitions(this._getCommonProjectTypeDefinition('ReactLibrary', [
'src',
'test',
'packageJson',
'typescript',
'rollup',
'nps',
'typedoc',
'jestproxies',
'vite',
'stories',
'storybook'
], ['demos'])),
NonCode: this._joinProjectTypeDefinitions(this._getCommonProjectTypeDefinition('NonCode', ['src', 'test', 'packageJson'], [
'eslint',
'tsConfigJSON',
'typescript',
'rollup',
'typedoc',
'jest',
'jestproxies',
'vite',
'stories',
'storybook',
'demos'
]), {
// On node code, nps should always be copied to the project's root
nps: this._fileDefinitionWithTooling('nps', 'NonCode', ['nps'], [], {
gobstonesScriptsLocation: ['<projectTypePath>/package-scripts.js'],
projectLocation: ['package-scripts.js'],
copyOnInit: true,
isOverridable: true
})
})
};
}
/**
* Returns the file information for all files that are common to any
* project. Expects the route of the project's subfolder.
*
* @param projectTypePath - The route of the project's subfolder (e.g. 'CLILibrary' or 'NonCode')
* @param noCommonFiles - The filenames to search in the project specific folder, instead of the common.
* @param excludedFiles - Files from the common folder to exclude from this project definition.
*
* @returns A partial ProjectTypeDefinition.
*/
_getCommonProjectTypeDefinition(projectTypePath, noCommonFiles, excludedFiles) {
return {
// only on init
src: this._fileDefinition('src', projectTypePath, noCommonFiles, excludedFiles, {
gobstonesScriptsLocation: ['<projectTypePath>/src'],
projectLocation: ['src'],
copyOnInit: true
}),
test: this._fileDefinition('test', projectTypePath, noCommonFiles, excludedFiles, {
gobstonesScriptsLocation: ['<projectTypePath>/test'],
projectLocation: ['test'],
copyOnInit: true
}),
changelog: this._fileDefinition('changelog', projectTypePath, noCommonFiles, excludedFiles, {
gobston