UNPKG

@plurid/joiner

Version:
1,331 lines (1,287 loc) 64.1 kB
import { Command } from 'commander'; import os from 'os'; import path from 'path'; import { spawn, execSync } from 'child_process'; import fetch from 'cross-fetch'; import fs, { promises } from 'fs'; import Deon, { typer, DEON_FILENAME_EXTENSION } from '@plurid/deon'; import 'crypto'; import yaml from 'js-yaml'; import { Worker } from 'worker_threads'; import checkUpdates from 'npm-check-updates'; import { isAlphaVersion, updateAlphaVersion } from 'alpha-versioning'; import http from 'http'; import { ncp } from 'ncp'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } // #region imports // #endregion libraries // #endregion imports // #region module const homeDirectory = os.homedir(); const JOINER_CONFIGURATION_FILE = '.joiner.config.deon'; const joinerConfigurationPath = path.join(homeDirectory, JOINER_CONFIGURATION_FILE); const JOINER_CLI_VERSION = "0.0.0-6"; const MANUAL_JOINER = 'https://manual.plurid.com/joiner'; // #endregion module // #endregion libraries // #endregion imports // #region module const fileExists = (path) => __awaiter(void 0, void 0, void 0, function* () { return !!(yield promises.stat(path).catch(e => false)); }); /** * Source: https://stackoverflow.com/a/57335271 * * @param callback Function to be called. * @param wait Debounce time. */ function debouncedCallback(callback, wait) { // track args & timeout handle between calls let argsRef; let timeout; function cleanup() { if (timeout) { clearTimeout(timeout); } } return function debouncedCallback(...args) { // capture latest args argsRef = args; // clear debounce timer cleanup(); // start waiting again timeout = setTimeout(() => { if (argsRef) { callback(...argsRef); } }, wait); }; } const resolveAbsolutePath = (value) => { const resolvedPath = path.isAbsolute(value) ? value : path.join(process.cwd(), value); return resolvedPath; }; // #endregion module // #endregion external // #endregion imports // #region module const updateConfigurationFile = (data) => __awaiter(void 0, void 0, void 0, function* () { try { const deon = new Deon(); const exists = yield fileExists(joinerConfigurationPath); if (!exists) { yield promises.writeFile(joinerConfigurationPath, ''); } const dataInFile = yield promises.readFile(joinerConfigurationPath, 'utf-8'); const deonDataInFile = yield deon.parse(dataInFile); const newData = Object.assign(Object.assign({}, deonDataInFile), data); const newDataString = deon.stringify(newData); yield promises.writeFile(joinerConfigurationPath, newDataString); return true; } catch (error) { console.log('error', error); return false; } }); const readConfigurationFile = () => __awaiter(void 0, void 0, void 0, function* () { const exists = yield fileExists(joinerConfigurationPath); if (!exists) { yield promises.writeFile(joinerConfigurationPath, ''); return {}; } const data = yield promises.readFile(joinerConfigurationPath, 'utf-8'); const deon = new Deon(); const ownerData = yield deon.parse(data); return typer(ownerData); }); // #endregion exports // #endregion libraries // #endregion imports // #region module const serverStart = () => __awaiter(void 0, void 0, void 0, function* () { const out = fs.openSync('./out.log', 'a'); const error = fs.openSync('./out.log', 'a'); /** * Start the server from the package root. */ const spawnedChild = spawn('node', [ './distribution/dashboard/index.js', ], { cwd: path.dirname(__dirname), stdio: [ 'ignore', out, error, ], detached: true, }); const { pid, } = spawnedChild; const port = yield new Promise((resolve, _) => { setTimeout(() => __awaiter(void 0, void 0, void 0, function* () { const outFile = yield promises.readFile('./out.log', 'utf-8'); promises.unlink('./out.log'); const re = /http:\/\/localhost:(\d+)/; const match = outFile.match(re); if (match) { resolve(match[1]); return; } resolve(''); }), 2000); }); spawnedChild.unref(); const parsedPort = parseInt(port); if (!parsedPort) { return; } return { pid, port: parsedPort, }; }); // #endregion exports // #endregion internal // #endregion imports // #region module const verifyDashboard = (address) => __awaiter(void 0, void 0, void 0, function* () { try { const response = yield fetch(address); if (response.status !== 200) { return false; } return true; } catch (error) { return false; } }); const dashboardStatus = () => __awaiter(void 0, void 0, void 0, function* () { const configurationFile = yield readConfigurationFile(); const { dashboard, } = configurationFile; if (!(dashboard === null || dashboard === void 0 ? void 0 : dashboard.port) || !(dashboard === null || dashboard === void 0 ? void 0 : dashboard.pid)) { console.log(`\n\tJoiner dashboard not started.\n`); return; } const dashboardAdress = `http://localhost:${dashboard.port}`; const dashboardActive = yield verifyDashboard(dashboardAdress); if (!dashboardActive) { // Clean dashboard metadata since process is no longer active. const updatedConfigurationFile = { dashboard: undefined, }; yield updateConfigurationFile(updatedConfigurationFile); console.log(`\n\tJoiner dashboard not started.\n`); return; } console.log(`\n\tJoiner dashboard started on ${dashboardAdress}\n`); }); const dashboardStart = () => __awaiter(void 0, void 0, void 0, function* () { const configurationFile = yield readConfigurationFile(); if (configurationFile === null || configurationFile === void 0 ? void 0 : configurationFile.dashboard) { const dashboardAdress = `http://localhost:${configurationFile.dashboard.port}`; const dashboardActive = yield verifyDashboard(dashboardAdress); if (dashboardActive) { console.log(`\n\tJoiner dashboard already started on ${dashboardAdress}\n`); return; } else { // Clean dashboard metadata since process is no longer active. const updatedConfigurationFile = { dashboard: undefined, }; yield updateConfigurationFile(updatedConfigurationFile); } } const data = yield serverStart(); if (!data) { return; } const { pid, port, } = data; const updatedConfigurationFile = { dashboard: { pid, port, }, }; yield updateConfigurationFile(updatedConfigurationFile); console.log(`\n\tJoiner dashboard started on http://localhost:${port}\n`); }); const dashboardStop = () => __awaiter(void 0, void 0, void 0, function* () { try { const configurationFile = yield readConfigurationFile(); const { dashboard, } = configurationFile; if (!dashboard) { console.log(`\n\tJoiner dashboard not started.\n`); return; } try { const killCommand = `kill -9 ${dashboard.pid}`; execSync(killCommand); } catch (error) { // continue } const updatedConfigurationFile = { dashboard: undefined, }; yield updateConfigurationFile(updatedConfigurationFile); console.log(`\n\tJoiner dashboard stopped on port ${dashboard.port}.\n`); } catch (error) { console.log(`\n\tSomething went wrong.\n`); } }); const dashboardRegister = (options) => __awaiter(void 0, void 0, void 0, function* () { try { const configurationFile = yield readConfigurationFile(); const pathValue = resolveAbsolutePath(options.path); const paths = [ ...(configurationFile.paths || []), pathValue, ]; yield updateConfigurationFile({ paths, }); console.log(`\n\t${options.path} registered.\n`); } catch (error) { console.log(`\n\tSomething went wrong.\n`); } }); const dashboardDeregister = (options) => __awaiter(void 0, void 0, void 0, function* () { try { const configurationFile = yield readConfigurationFile(); const paths = [ ...(configurationFile.paths || []), ]; const pathValue = resolveAbsolutePath(options.path); const updatedPaths = paths.filter(path => path !== pathValue); yield updateConfigurationFile({ paths: updatedPaths, }); console.log(`\n\t${options.path} deregistered.\n`); } catch (error) { console.log(`\n\tSomething went wrong.\n`); } }); // #endregion exports // #endregion external // #endregion imports // #region module const resolveCommand = (value) => { if (!value) { return 'start'; } const cleanValue = value.toLowerCase().trim(); if (cleanValue === 'status' || cleanValue === 'start' || cleanValue === 'stop' || cleanValue === 'register' || cleanValue === 'deregister') { return cleanValue; } return; }; const dashboardCommand = (value, options) => __awaiter(void 0, void 0, void 0, function* () { const command = resolveCommand(value); if (!command) { console.log(`Dashboard command '${value}' is not adequate.`); return; } switch (command) { case 'status': yield dashboardStatus(); break; case 'start': yield dashboardStart(); break; case 'stop': yield dashboardStop(); break; case 'register': yield dashboardRegister(options); break; case 'deregister': yield dashboardDeregister(options); break; } }); // #endregion exports // #endregion external // #endregion imports // #region module const deonDefaultConfiguration = `// uncomment and add paths to packages { packages [ // /path/to/package // /path/to/multiple-packages/* ] package { manager yarn publisher npm ignore [] } yarnWorkspace false commit { engine git combine false root /path/to/root fullFolder false divider ' > ' message setup: package } runFrom '' development { watchPackages all serverPort 55000 watchDirectories [ build distribution dist ] externalPackages [] } } `; const yamlDefaultConfiguration = `--- # uncomment and add paths to packages packages: # - /path/to/package # - /path/to/multiple-packages/* package: manager: yarn publisher: npm ignore: [] yarnWorkspace: false commit: engine: git combine: false root: '/path/to/root' fullFolder: false divider: ' > ' message: 'setup: package' runFrom: '' development: watchPackages: all serverPort: 55000 watchDirectories: ['build', 'distribution', 'dist'] externalPackages: [] `; const initializeCommand = (type) => __awaiter(void 0, void 0, void 0, function* () { const filename = type === 'deon' ? 'joiner.deon' : 'joiner.yaml'; const joinerPath = path.join(process.cwd(), filename); const joinerContent = type === 'deon' ? deonDefaultConfiguration : yamlDefaultConfiguration; if (!(yield fileExists(joinerPath))) { yield promises.writeFile(joinerPath, joinerContent); console.log(`\n\tJoiner initialized in root path:\n\n\t${process.cwd()}\n`); } else { console.log(`\n\tJoiner already initialized. File '${filename}' exists in path:\n\n\t${process.cwd()}\n`); } }); // #endregion exports // #endregion external // #endregion imports // #region module const resolvePackage = (packageName, configurationData) => { const { packages, } = configurationData; const safePackageName = packageName.toLowerCase(); if (safePackageName === 'all' || safePackageName === '*') { return packages; } if (safePackageName === 'self') { // The joiner file resolves to only one package. const codePackage = packages[0]; if (!codePackage) { console.log(`\n\tPackage ${packageName} could not be resolved.\n`); return; } return [ packages[0], ]; } if (safePackageName.startsWith('%')) { // The package is a numeric value, e.g. '%0', or '%12'. const value = parseInt(safePackageName.replace('%', '')) || 0; const codePackage = packages[value]; if (!codePackage) { console.log(`\n\tPackage ${packageName} could not be resolved.\n`); return; } return [ codePackage, ]; } for (const codePackage of packages) { if (codePackage.name.toLowerCase() === safePackageName || codePackage.alias.toLowerCase() === safePackageName) { return [ codePackage, ]; } } console.log(`\n\tPackage ${packageName} could not be resolved.\n`); return; }; const locatePackages = (packages, yarnWorkspace, runFrom, packageIgnore) => __awaiter(void 0, void 0, void 0, function* () { if (!packages && !yarnWorkspace) { return []; } const packagesPaths = yield resolvePackagesPaths(packages, yarnWorkspace, runFrom); const locatedPackages = []; for (const packagePath of packagesPaths) { if (!packagePath.includes('/*')) { const packageAbsolutePath = path.join(runFrom, packagePath); const locatedPackage = yield readPackageFile(packageAbsolutePath, packageIgnore); if (locatedPackage) { locatedPackages.push(locatedPackage); } } else { const packagesRoot = packagePath.replace('/*', ''); const packagesRootPath = path.join(runFrom, packagesRoot); try { const rootFiles = yield promises.readdir(packagesRootPath); for (const rootFile of rootFiles) { const packagePath = path.join(packagesRootPath, rootFile); const statistics = yield promises.stat(packagePath); if (statistics.isDirectory()) { const locatedPackage = yield readPackageFile(packagePath, packageIgnore); if (locatedPackage) { locatedPackages.push(locatedPackage); } } } } catch (error) { console.log(`\n\tPackages root ${packagesRootPath} could not be resolved.\n`); } } } const filteredPackagesByName = locatedPackages.filter(locatedPackage => !packageIgnore.includes(locatedPackage.name)); const filteredPackagesByPath = filteredPackagesByName.filter((filterablePackage) => { for (const packageIgnored of packageIgnore) { if (filterablePackage.path.includes(packageIgnored)) { return false; } } return true; }); return filteredPackagesByPath; }); const resolvePackagesPaths = (packages, yarnWorkspace, runFrom) => __awaiter(void 0, void 0, void 0, function* () { if (!yarnWorkspace) { return packages; } try { const packageJSONPath = path.join(runFrom, 'package.json'); const packageRawData = yield promises.readFile(packageJSONPath, 'utf-8'); const packageData = JSON.parse(packageRawData); const workspaces = packageData.workspaces || []; return workspaces; } catch (error) { console.log(`\n\tCould not read the workspaces in the package.json from:\n\t${process.cwd()}\n`); return []; } }); const readPackageFile = (packagePath, packageIgnore) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b, _c, _d; try { const packageJSONPath = path.join(packagePath, 'package.json'); const packageRawData = yield promises.readFile(packageJSONPath, 'utf-8'); const packageData = JSON.parse(packageRawData); const packageName = (_a = packageData.name) !== null && _a !== void 0 ? _a : ''; const packageAlias = computePackageAlias(packageName); const packageVersion = (_b = packageData.version) !== null && _b !== void 0 ? _b : '0.0.0'; const packagePrivate = (_c = packageData.private) !== null && _c !== void 0 ? _c : false; const locatedPackage = { path: packagePath, name: packageName, alias: packageAlias, version: packageVersion, private: packagePrivate, joinerpackage: false, }; return locatedPackage; } catch (error) { try { const packageJoinerPath = path.join(packagePath, 'joiner.package.yaml'); const packageRawData = yield promises.readFile(packageJoinerPath, 'utf-8'); const packageData = yaml.load(packageRawData); const packageName = (_d = packageData === null || packageData === void 0 ? void 0 : packageData.name) !== null && _d !== void 0 ? _d : ''; const packageAlias = computePackageAlias(packageName); const locatedPackage = { path: packagePath, name: packageName, alias: packageAlias, version: '0.0.0', private: true, joinerpackage: true, }; return locatedPackage; } catch (error) { let ignored = false; for (const packageIgnored of packageIgnore) { if (packagePath.includes(packageIgnored)) { ignored = true; } } if (!ignored) { console.log(`\n\tCould not read the package from:\n\t${packagePath}\n`); } return; } } }); /** * Extract from the package `name` the package `alias`. * * e.g. * * `@scope/one.two.three` -> `three` * * `one-two` -> `one-two` * * @param name */ const computePackageAlias = (name) => { const noScope = name.replace(/@\w+\//, ''); const split = noScope.split('.'); const lastWord = split[split.length - 1]; return lastWord; }; const resolveWatchedPackages = (packages, watchPackages) => { const allPackages = packages.map(workPackage => workPackage.name); if (!watchPackages) { return [ ...allPackages, ]; } if (typeof watchPackages === 'string') { if (watchPackages.toLowerCase() === 'all') { return [ ...allPackages, ]; } const watchPackage = packages.find(workPackage => workPackage.name === watchPackages); if (!watchPackage) { return []; } return [watchPackage.name]; } const resolvedWatchPackages = []; for (const watchPackage of watchPackages) { for (const workPackage of packages) { if (watchPackage === workPackage.name) { resolvedWatchPackages.push(watchPackage); break; } } } return resolvedWatchPackages; }; // #endregion module // #endregion external // #endregion imports // #region module const parseConfigurationFile = (configurationFile) => __awaiter(void 0, void 0, void 0, function* () { try { const configurationFilepath = configurationFile.startsWith('/') ? configurationFile : path.join(process.cwd(), configurationFile); const configurationFileData = yield promises.readFile(configurationFilepath, 'utf-8'); const extension = path.extname(configurationFile); if (extension === DEON_FILENAME_EXTENSION) { const deon = new Deon(); const data = yield deon.parse(configurationFileData, { filebase: path.dirname(configurationFilepath), }); return yield handleParsedConfigurationFile(data, configurationFilepath); } const parsedData = yaml.load(configurationFileData); return yield handleParsedConfigurationFile(parsedData, configurationFilepath); } catch (error) { console.log(`\n\tConfiguration file required.\n\n\tCheck the file '${configurationFile}' exists on the rootpath:\n\t${process.cwd()}\n`); return; } }); const handleParsedConfigurationFile = (parsedData, configurationFilepath) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3; const packageName = parsedData.name || ''; const packageManager = (_b = (_a = parsedData.package) === null || _a === void 0 ? void 0 : _a.manager) !== null && _b !== void 0 ? _b : 'yarn'; const packagePublisher = (_d = (_c = parsedData.package) === null || _c === void 0 ? void 0 : _c.publisher) !== null && _d !== void 0 ? _d : 'npm'; const packageIgnore = (_f = (_e = parsedData.package) === null || _e === void 0 ? void 0 : _e.ignore) !== null && _f !== void 0 ? _f : []; const yarnWorkspace = typeof parsedData.yarnWorkspace === 'boolean' ? parsedData.yarnWorkspace : parsedData.yarnWorkspace === 'true'; const commitEngine = (_h = (_g = parsedData.commit) === null || _g === void 0 ? void 0 : _g.engine) !== null && _h !== void 0 ? _h : 'git'; const commitCombine = typeof ((_j = parsedData.commit) === null || _j === void 0 ? void 0 : _j.combine) === 'boolean' ? (_k = parsedData.commit) === null || _k === void 0 ? void 0 : _k.combine : ((_l = parsedData.commit) === null || _l === void 0 ? void 0 : _l.combine) === 'true'; const commitRoot = (_o = (_m = parsedData.commit) === null || _m === void 0 ? void 0 : _m.root) !== null && _o !== void 0 ? _o : ''; const commitFullFolder = typeof ((_p = parsedData.commit) === null || _p === void 0 ? void 0 : _p.fullFolder) === 'boolean' ? (_q = parsedData.commit) === null || _q === void 0 ? void 0 : _q.fullFolder : ((_r = parsedData.commit) === null || _r === void 0 ? void 0 : _r.fullFolder) === 'true'; const commitDivider = (_t = (_s = parsedData.commit) === null || _s === void 0 ? void 0 : _s.divider) !== null && _t !== void 0 ? _t : ' > '; const commitMessage = (_v = (_u = parsedData.commit) === null || _u === void 0 ? void 0 : _u.message) !== null && _v !== void 0 ? _v : 'setup: package'; const runFromData = (_w = parsedData.runFrom) !== null && _w !== void 0 ? _w : ''; const configurationFileDirectory = path.dirname(configurationFilepath); const runFrom = runFromData ? path.resolve(configurationFileDirectory, runFromData) : process.cwd(); const commands = parsedData.commands || {}; const packages = yield locatePackages(parsedData.packages, yarnWorkspace, runFrom, packageIgnore); const developmentExternalPackages = (_y = (_x = parsedData.development) === null || _x === void 0 ? void 0 : _x.externalPackages) !== null && _y !== void 0 ? _y : []; const developmentWatchPackages = resolveWatchedPackages(packages, (_z = parsedData.development) === null || _z === void 0 ? void 0 : _z.watchPackages); const developmentWatchDirectories = (_1 = (_0 = parsedData.development) === null || _0 === void 0 ? void 0 : _0.watchDirectories) !== null && _1 !== void 0 ? _1 : ['build', 'distribution', 'dist']; const developmentServerPort = (_3 = (_2 = parsedData.development) === null || _2 === void 0 ? void 0 : _2.serverPort) !== null && _3 !== void 0 ? _3 : 55000; if (packages.length === 0 && !yarnWorkspace) { console.log(`\n\tPackages required to be specified in the 'joiner' configuration file.\n`); return; } const configurationFile = { name: packageName, packages, package: { manager: packageManager, publisher: packagePublisher, ignore: packageIgnore, }, yarnWorkspace, commit: { engine: commitEngine, combine: commitCombine, root: commitRoot, fullFolder: commitFullFolder, divider: commitDivider, message: commitMessage, }, development: { externalPackages: developmentExternalPackages, watchPackages: developmentWatchPackages, watchDirectories: developmentWatchDirectories, serverPort: developmentServerPort, }, runFrom, commands, }; return configurationFile; }); const getDefaultConfigurationFilepath = () => __awaiter(void 0, void 0, void 0, function* () { const filepaths = [ 'joiner.deon', 'joiner.yaml', 'scripts/joiner.deon', 'scripts/joiner.yaml', 'scripts/joiner.packages.deon', 'scripts/joiner.packages.yaml', ]; let pathFound = ''; for (const filepath of filepaths) { try { const possiblePath = path.join(process.cwd(), filepath); const exists = yield promises.stat(possiblePath); if (exists) { pathFound = filepath; break; } } catch (error) { continue; } } return pathFound; }); // #endregion module // #endregion external // #endregion imports // #region module const listCommand = (configurationFile) => __awaiter(void 0, void 0, void 0, function* () { const configurationData = yield parseConfigurationFile(configurationFile); if (!configurationData) { return; } const { packages, } = configurationData; if (packages.length > 0) { console.log(`\n\tJoiner commandable packages:\n`); for (const [index, configPackage] of packages.entries()) { console.log(`\t\t${index} · ${configPackage.name}`); console.log(`\t\talias: ${configPackage.alias}`); console.log(`\t\tpath: ${configPackage.path}\n`); } } else { console.log(`\n\tNo joiner commandable packages.\n`); } }); // #endregion exports // #endregion external // #endregion imports // #region module const runExecution = (configPackage, command) => __awaiter(void 0, void 0, void 0, function* () { const executableCommand = command.join(' '); console.log(`\n\tRunning command '${executableCommand}' in:\n\t${configPackage.path}\n`); const startTime = Date.now(); execSync(executableCommand, { cwd: configPackage.path, stdio: 'inherit', }); const endTime = Date.now(); const commandTime = (endTime - startTime) / 1000; console.log(`\n\tCommand\n\n\t\t${executableCommand}\n\n\tran in ${commandTime} seconds.\n`); }); // #endregion exports // #endregion external // #endregion imports // #region module const generateBatches = (packages, batch) => { const batchesCount = Math.ceil(packages.length / batch); const batches = []; for (let i = 0; i < batchesCount; i++) { const batchValues = packages.slice(i * batch, (i + 1) * batch); batches.push(batchValues); } return batches; }; // #endregion exports // #region imports // #endregion libraries // #endregion imports // #region module const runWorker = (name, data) => { return new Promise((resolve, reject) => { const worker = new Worker(path.join(__dirname, `../distribution/worker-${name}.js`), { workerData: data, }); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) { reject(new Error(`Worker stopped with exit code ${code}`)); } resolve(true); }); }); }; // #endregion exports // #endregion external // #endregion imports // #region module class Batcher { constructor(packages, batch, workerName, workerData) { this.packages = packages; this.batch = batch; this.workerName = workerName; this.workerData = workerData; } run() { return __awaiter(this, void 0, void 0, function* () { const batches = generateBatches(this.packages, this.batch); for (const batchPackages of batches) { const promises = []; for (const configPackage of batchPackages) { const resolving = runWorker(this.workerName, Object.assign({ configPackage }, this.workerData)); promises.push(resolving); } yield Promise.all(promises); } }); } } // #endregion exports // #endregion external // #endregion imports // #region module const runCommand = (packageName, command, options) => __awaiter(void 0, void 0, void 0, function* () { const { batch, parallel, configuration, } = options; const configurationData = yield parseConfigurationFile(configuration); if (!configurationData) { return; } const resolvedPackage = resolvePackage(packageName, configurationData); if (!resolvedPackage) { return; } if (parallel) { const batcher = new Batcher(resolvedPackage, batch, 'run', { command, }); yield batcher.run(); return; } for (const configPackage of resolvedPackage) { yield runExecution(configPackage, command); } }); // #endregion exports // #endregion external // #endregion imports // #region module const commandExecution = (configPackage, commandNames, configurationData) => __awaiter(void 0, void 0, void 0, function* () { for (const commandName of commandNames) { const executableCommandData = configurationData.commands[commandName]; const executableCommand = executableCommandData.join(' '); console.log(`\n\tRunning command '${executableCommand}' in:\n\t${configPackage.path}\n`); const startTime = Date.now(); execSync(executableCommand, { cwd: configPackage.path, stdio: 'inherit', }); const endTime = Date.now(); const commandTime = (endTime - startTime) / 1000; console.log(`\n\tCommand\n\n\t\t${executableCommand}\n\n\tran in ${commandTime} seconds.\n`); } }); // #endregion exports // #endregion external // #endregion imports // #region module const commandCommand = (packageName, commandNames, options) => __awaiter(void 0, void 0, void 0, function* () { const { batch, parallel, configuration, } = options; const configurationData = yield parseConfigurationFile(configuration); if (!configurationData) { console.log(`\n\tNo configuration data in the configuration file ${configuration}.\n`); return; } const resolvedPackage = resolvePackage(packageName, configurationData); if (!resolvedPackage) { console.log(`\n\tNo package ${packageName}.\n`); return; } const registeredCommands = Object.keys(configurationData.commands); for (const commandName of commandNames) { if (!registeredCommands.includes(commandName)) { console.log(`\n\tNo command ${commandName}.\n`); return; } } if (parallel) { const batcher = new Batcher(resolvedPackage, batch, 'command', { commandNames, configurationData, }); yield batcher.run(); return; } for (const configPackage of resolvedPackage) { yield commandExecution(configPackage, commandNames, configurationData); } }); // #endregion exports // #endregion external // #endregion imports // #region module const updateExecution = (configPackage, configurationFile) => __awaiter(void 0, void 0, void 0, function* () { const inThePackage = `in the package '${configPackage.name}'`; console.log(`\n\tChecking for updates ${inThePackage}...`); try { const updatedPackages = yield checkUpdates({ packageFile: path.join(configPackage.path, 'package.json'), silent: true, jsonUpgraded: true, upgrade: true, dep: 'prod,dev', cache: true, }); if (!updatedPackages) { console.log(`\n\tCould not find updates ${inThePackage}...`); return; } if (Object.keys(updatedPackages).length > 0) { console.log(`\n\t'${configPackage.name}' dependencies updated:\n`); for (const [key, value] of Object.entries(updatedPackages)) { console.log(`\t\t${key}: ${value}`); } console.log(`\n\tInstalling the updates ${inThePackage}...`); installCommand(configPackage, configurationFile); console.log(`\tUpdates installed ${inThePackage}.\n`); } else { console.log(`\n\tNo dependencies updates ${inThePackage}.\n`); console.log(`\n\tInstalling the dependencies ${inThePackage}...`); installCommand(configPackage, configurationFile); console.log(`\tDependencies installed ${inThePackage}.\n`); } } catch (error) { console.log(`\n\tSomething went wrong.\n`); } }); const installCommand = (configPackage, configurationFile) => { const install = `${configurationFile.package.manager} install`; execSync(install, { cwd: configPackage.path, stdio: 'ignore', }); }; // #endregion exports // #endregion external // #endregion imports // #region module const updateCommand = (packageName, options) => __awaiter(void 0, void 0, void 0, function* () { const { batch, parallel, configuration, } = options; const configurationData = yield parseConfigurationFile(configuration); if (!configurationData) { return; } const resolvedPackage = resolvePackage(packageName, configurationData); if (!resolvedPackage) { return; } if (parallel) { const batcher = new Batcher(resolvedPackage, batch, 'update', { configurationFile: configurationData, }); yield batcher.run(); return; } for (const configPackage of resolvedPackage) { yield updateExecution(configPackage, configurationData); } }); // #endregion exports // #endregion external // #endregion imports // #region module const patchCommand = (packageName, configurationFile, versionType) => __awaiter(void 0, void 0, void 0, function* () { if (packageName === '.') { const packageData = { alias: '', joinerpackage: false, name: 'in the current directory', path: process.cwd(), private: false, version: '0.0.0-0', }; yield patchLogic(packageData, versionType); return; } const configurationData = yield parseConfigurationFile(configurationFile); if (!configurationData) { return; } const resolvedPackage = resolvePackage(packageName, configurationData); if (!resolvedPackage) { return; } for (const configPackage of resolvedPackage) { yield patchLogic(configPackage, versionType); } }); const patchLogic = (configPackage, versionType) => __awaiter(void 0, void 0, void 0, function* () { try { console.log(`\n\tPatching version for package ${configPackage.name}...`); const packageJSONPath = path.join(configPackage.path, 'package.json'); const packageJSONRawData = yield promises.readFile(packageJSONPath, 'utf-8'); const packageJSONData = JSON.parse(packageJSONRawData); const { version, } = packageJSONData; const isAlpha = isAlphaVersion(version); if (!isAlpha) { const versionTypeCommand = resolveVersionType(versionType); const patchCommand = `npm version ${versionTypeCommand} --no-git-tag-version`; execSync(patchCommand, { cwd: configPackage.path, stdio: 'ignore', }); const packageJSONPath = path.join(configPackage.path, 'package.json'); const packageJSONRawData = yield promises.readFile(packageJSONPath, 'utf-8'); const packageJSONData = JSON.parse(packageJSONRawData); const { name, version, } = packageJSONData; console.log(`\t${name} version patched to version ${version}.\n`); return; } const alphaUpdatedVersion = updateAlphaVersion(version); const updatedPackageData = Object.assign({}, packageJSONData); updatedPackageData.version = alphaUpdatedVersion; yield promises.writeFile(packageJSONPath, JSON.stringify(updatedPackageData, null, 4)); } catch (error) { console.log(`\n\tSomething went wrong. Check the version field for '${configPackage.name}'.\n`); } }); const resolveVersionType = (versionType) => { const versionTypeCommand = (versionType === 'major' || versionType === 'minor' || versionType === 'patch') ? versionType : 'patch'; return versionTypeCommand; }; // #endregion exports // #endregion external // #endregion imports // #region module const commitCommand = (packageName, configurationFile, message) => __awaiter(void 0, void 0, void 0, function* () { const configurationData = yield parseConfigurationFile(configurationFile); if (!configurationData) { return; } const resolvedPackage = resolvePackage(packageName, configurationData); if (!resolvedPackage) { return; } for (const configPackage of resolvedPackage) { yield commitLogic(configPackage, configurationData, message); } }); const commitLogic = (configPackage, configurationData, message) => __awaiter(void 0, void 0, void 0, function* () { try { console.log(`\n\tCommiting the package '${configPackage.name}'...`); const addCommand = 'git add .'; execSync(addCommand, { cwd: configPackage.path, stdio: 'ignore', }); const { combine, root, fullFolder, divider, message: configMessage, } = configurationData.commit; const packageFolder = path.relative(process.cwd(), configPackage.path); const packageFolderSplit = packageFolder.split('/'); const packageFolderName = packageFolderSplit[packageFolderSplit.length - 1]; const packageName = fullFolder ? packageFolder : packageFolderName; const commitMessage = message || configMessage; const commitCommandMessage = combine ? root + packageName + divider + commitMessage : commitMessage; const commitCommand = `git commit -m '${commitCommandMessage}'`; execSync(commitCommand, { cwd: configPackage.path, stdio: 'ignore', }); console.log(`\t'${configPackage.name}' changes commited.\n`); } catch (error) { console.log(`\n\tSomething went wrong.\n`); } }); // #endregion exports // #endregion external // #endregion imports // #region module const publishExecution = (configPackage) => __awaiter(void 0, void 0, void 0, function* () { try { if (configPackage.private) { console.log(`\n\tPackage '${configPackage.name} is private. Did not publish.'`); return; } console.log(`\n\tPublishing the package '${configPackage.name}'...`); const publishCommand = 'npm publish'; execSync(publishCommand, { cwd: configPackage.path, stdio: 'ignore', }); console.log(`\t'${configPackage.name}' published.\n`); } catch (error) { console.log(`\n\tSomething went wrong.\n`); } }); // #endregion exports // #endregion external // #endregion imports // #region module const publishCommand = (packageName, options) => __awaiter(void 0, void 0, void 0, function* () { const { batch, parallel, configuration, } = options; const configurationData = yield parseConfigurationFile(configuration); if (!configurationData) { return; } const resolvedPackage = resolvePackage(packageName, configurationData); if (!resolvedPackage) { return; } if (parallel) { const batcher = new Batcher(resolvedPackage, batch, 'publish', {}); yield batcher.run(); return; } for (const configPackage of resolvedPackage) { yield publishExecution(configPackage); } }); // #endregion exports // #endregion external // #endregion imports // #region module const ucomExecution = (packageName, options) => __awaiter(void 0, void 0, void 0, function* () { yield updateCommand(packageName, options); yield commitCommand(packageName, options.configuration); }); // #endregion exports // #endregion external // #endregion imports // #region module const ucomCommand = (packageName, options) => __awaiter(void 0, void 0, void 0, function* () { const { batch, parallel, configuration, } = options; if (parallel) { const configurationData = yield parseConfigurationFile(configuration); if (!configurationData) { return; } const resolvedPackage = resolvePackage(packageName, configurationData); if (!resolvedPackage) { return; } const batcher = new Batcher(resolvedPackage, batch, 'ucom', {}); yield batcher.run(); return; } console.log(`\n\t---------------`); console.log(`\tUcomishing ${packageName}...`); yield ucomExecution(packageName, options); console.log(`\n\tUcomished ${packageName}.`); console.log(`\t---------------\n`); }); // #endregion exports // #endregion external // #endregion imports // #region module const upcomExecution = (packageName, options) => __awaiter(void 0, void 0, void 0, function* () { yield updateCommand(packageName, options); yield patchCommand(packageName, options.configuration, 'patch'); yield commitCommand(packageName, options.configuration); }); // #endregion exports // #endregion external // #endregion imports // #region module const upcomCommand = (packageName, options) => __awaiter(void 0, void 0, void 0, function* () { const { batch, parallel, configuration, } = options; if (parallel) { const configurationData = yield parseConfigurationFile(configuration); if (!configurationData) { return; } const resolvedPackage = resolvePackage(packageName, configurationData); if (!resolvedPackage) { return; } const batcher = new Batcher(resolvedPackage, batch, 'upcom', {}); yield batcher.run(); return; } console.log(`\n\t---------------`); console.log(`\tUpcomishing ${packageName}...`); yield upcomExecution(packageName, options); console.log(`\n\tUpcomished ${packageName}.`); console.log(`\t---------------\n`); }); // #endregion exports // #endregion external // #endregion imports // #region module const upcomlishExecution = (packageName, options) => __awaiter(void 0, void 0, void 0, function* () { yield updateCommand(packageName, options); yield patchCommand(packageName, options.configuration, 'patch'); yield commitCommand(packageName, options.configuration); yield publishCommand(packageName, options); }); // #endregion exports // #endregion external // #endregion imports // #region module const upcomlishCommand = (packageName, options) => __awaiter(void 0, void 0, void 0, function* () { const { batch, parallel, configuration, } = options; if (parallel) { const configurationData = yield parseConfigurationFile(configuration); if (!configurationData) { return; } const resolvedPackage = resolvePackage(packageName, configurationData); if (!resolvedPackage) { return; } const batcher = new Batcher(resolvedPackage, batch, 'upcomlish', {}); yield batcher.run(); return; } console.log(`\n\t---------------`); console.log(`\tUpcomlishishing ${packageName}...`); yield upcomlishExecution(packageName, options); console.log(`\n\tUpcomlishished ${packageName}.`); console.log(`\t---------------\n`); }); // #endregion exports // #endregion external // #endregion imports // #region module const developmentPackageUpdateDirectoryLogic = (workPackage, updatePackage, watchDirectory) => __awaiter(void 0, void 0, void 0, function* () { const watchDirectoryPath = path.join(workPackage.path, watchDirectory); const updatePackageDependencyPath = path.join(updatePackage.path, 'node_modules', workPackage.name); // console.log('updatePackageDependencyPath', updatePackageDependencyPath); const updatePackageDependencyBuildPath = path.join(updatePackageDependencyPath, watchDirectory); // console.log('updatePackageDependencyBuildPath', updatePackageDependencyBuildPath); if (fs.existsSync(watchDirectoryPath)) { // check that the updatePackage is actually dependent on the workPackage if (!fs.existsSync(updatePackageDependencyPath)) { return; } // check that if there is already a build folder if (fs.existsSync(updatePackageDependencyBuildPath)) { fs.rmSync(updatePackageDependencyBuildPath, { recursive: true, }); } fs.mkdirSync(updatePackageDependencyBuildPath, { recursive: true }); /** * copy * workPackage.path/watchDirectory * to * updatePackage/node_modules/workPackage.name/watchDirectory */ ncp(watchDirectoryPath, updatePackageDependencyBuildPath, (error) => { if (error) { return console.error(error); } const date = new Date(); console.log(`\t[${date.toLocaleTimeString()} - ${date.toLocaleDateString()}]: Copied '${workPackage.name}' build output '${watchDirectory}' to '${updatePackage.name}' dependency folder.`); }); } }); const developmentPackageUpdateLogic = (registeredPackage, updatePackage, configuration) => { const workPackage = configuration.packages.find(configPackage => configPackage.name === registeredPackage); if (!workPackage) { return; } for (const watchDirectory of configuration.development.watchDirectories) { developmentPackageUpdateDirectoryLogic(workPackage, updatePackage, watchDirectory); } }; class DevelopmentWatcher { constructor(configuration) { this.watchers = []; this.packageRegistry = new Set(); this.developmentPackagesUpdate = (configuration, packageRegistry) => { for (const registeredPackage of packageRegistry) { // Loop over modules