UNPKG

@gobstones/gobstones-scripts

Version:

Scripts to abstract away build configuration of Gobstones Project's libraries and modules.

361 lines (317 loc) 12.9 kB
/* * ***************************************************************************** * 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 CLI * @author Alan Rodas Bonjour <alanrodas@gmail.com> * * @internal * ---------------------------------------------------- */ import path from 'path'; import { program as commanderProgram } from 'commander'; import * as cli from './cli-helpers'; import { t } from '../@i18n'; import * as api from '../API'; import { config } from '../Config'; import { LogLevel, logger } from '../Helpers/Logger'; /** * The command line program definition */ export const program = commanderProgram; /** * The general program options. * * @internal */ interface GeneralOptions { type?: string; silent?: boolean; debug?: boolean; } /** * The options that expect a package manager * * @internal */ interface PackageManagerBasedOption { packageManager?: string; } /** * The general program options with the "--test" option added. * * @internal */ type GeneralOptionsWithTest = GeneralOptions & { test?: boolean }; /** * The options for command that expect a list of items. * * @internal */ interface ItemBasedOptions { items?: string; force?: boolean; } // Initialize the configuration config.init(); program .description(`${cli.banner()}\n\n${cli.welcome()}`) .addHelpText('before', `${cli.banner()}\n\n${cli.welcome()}`) .version(config.environment.toolVersion, '-v --version') .option('-c, --config', t('cli:descriptions.args.config')) .action((options: { config?: boolean }) => { if (!options.config) { program.outputHelp(); process.exit(0); } cli.printConfiguration(); process.exit(0); }); program .command('create <project-name>') .description(t('descriptions.commands.create')) .option( '-t, --type <project-type>', t('cli:descriptions.args.type', { options: `"${Object.keys(config.projectTypes).join('", "')}"` }), 'Library' ) .option( '-p, --package-manager <package-manager>', t('cli:descriptions.args.packageManager', { options: `"${Object.keys(config.packageManagers).join('", "')}"` }), 'npm' ) .option('-s, --silent', t('cli:descriptions.args.silent'), undefined) .option('-D, --debug', t('cli:descriptions.args.debug'), undefined) .option('-T, --test', t('cli:descriptions.args.test'), undefined) .action((projectName: string, options: PackageManagerBasedOption & GeneralOptionsWithTest) => { if (options.debug) { logger.level = LogLevel.Debug; } if (options.silent) { logger.off(); } failIfOptionInvalid(options, 'type', Object.keys(config.projectTypes)); failIfOptionInvalid(options, 'package-manager', Object.keys(config.packageManagers)); config.init(options.type, options.packageManager, options.debug, options.test, undefined); cli.displayWelcomeForAction( t('cli:messages.creatingProject', { projectName, projectType: config.executionEnvironment.projectType, packageManager: config.executionEnvironment.packageManager }) ); cli.runOrEnd(() => { api.create(projectName, options.type, options.packageManager, options.test); }); }); program .command('init') .description(t('descriptions.commands.init')) .option( '-t, --type <project-type>', t('cli:descriptions.args.type', { options: `"${Object.keys(config.projectTypes).join('", "')}"` }), 'Library' ) .option( '-p, --package-manager <package-manager>', t('cli:descriptions.args.packageManager', { options: `"${Object.keys(config.packageManagers).join('", "')}"` }), 'npm' ) .option('-s, --silent', t('cli:descriptions.args.silent'), undefined) .option('-D, --debug', t('cli:descriptions.args.debug'), undefined) .option('-T, --test', t('cli:descriptions.args.test'), undefined) .action((options: PackageManagerBasedOption & GeneralOptionsWithTest) => { if (options.debug) { logger.level = LogLevel.Debug; } if (options.silent) { logger.off(); } failIfOptionInvalid(options, 'type', Object.keys(config.projectTypes)); failIfOptionInvalid(options, 'package-manager', Object.keys(config.packageManagers)); config.init(options.type, options.packageManager, options.debug, options.test, undefined); cli.displayWelcomeForAction( t('cli:messages.initializingProject', { projectType: config.executionEnvironment.projectType, packageManager: config.executionEnvironment.packageManager }) ); cli.runOrEnd(() => { api.init(config.executionEnvironment.projectType, config.executionEnvironment.packageManager, options.test); }); }); program .command('update') .description(t('descriptions.commands.update')) .option('-f, --force', 'whether to override previous values', undefined) .option( '-i, --items <item>', `the items to update. One of "all", "${config.projectTypeFilteredFiles.copiedOnUpdate.join('", "')}"`, 'all' ) .option( '-t, --type <project-type> the project type to create, one of "' + Object.keys(config.projectTypes).join('", "') + '"' ) .option('-s, --silent', t('cli:descriptions.args.silent'), undefined) .option('-D, --debug', t('cli:descriptions.args.debug'), undefined) .option('-T, --test', t('cli:descriptions.args.test'), undefined) .action((options: GeneralOptionsWithTest & ItemBasedOptions) => { if (options.debug) { logger.level = LogLevel.Debug; } if (options.silent) { logger.off(); } failIfOptionInvalid(options, 'type', Object.keys(config.projectTypes)); logger.log(JSON.stringify(config.projectTypeFilteredFiles)); if (options.items !== 'all') { failIfOptionInvalid(options, 'items', config.projectTypeFilteredFiles.copiedOnUpdate); } config.init(options.type, undefined, options.debug, options.test, undefined); cli.displayWelcomeForAction( t('cli:messages.updatingFiles', { projectType: config.executionEnvironment.projectType, packageManager: config.executionEnvironment.packageManager, files: options.items ?? '' }) ); cli.runOrEnd(() => { const files = api.update(options.items, options.force, options.type, options.test); const useAbsolute = config.executionEnvironment.useFullPaths; logger.on(); logger.log('Files updated:'); files.forEach((file) => { const fileName = useAbsolute ? file : path.relative(config.locations.projectRoot, file); logger.log(`\t${fileName}`, 'blue'); }); }); }); program .command('eject') .description(t('descriptions.commands.eject')) .option('-f, --force', 'whether to override previous values', undefined) .option( '-i, --items <item>', `the items to update. One of "all", "${config.projectTypeFilteredFiles.copiedOnEject.join('", "')}"`, 'all' ) .option( '-t, --type <project-type>', t('cli:descriptions.args.type', { options: `"${Object.keys(config.projectTypes).join('", "')}"` }) ) .option('-s, --silent', t('cli:descriptions.args.silent'), undefined) .option('-D, --debug', t('cli:descriptions.args.debug'), undefined) .action((options: GeneralOptions & ItemBasedOptions) => { if (options.debug) { logger.level = LogLevel.Debug; } if (options.silent) { logger.off(); } failIfOptionInvalid(options, 'type', Object.keys(config.projectTypes)); if (options.items !== 'all') { failIfOptionInvalid(options, 'items', config.projectTypeFilteredFiles.copiedOnEject); } config.init(options.type, undefined, options.debug, undefined, undefined); cli.displayWelcomeForAction( t('cli:messages.ejectingFiles', { projectType: config.executionEnvironment.projectType, packageManager: config.executionEnvironment.packageManager, files: options.items ?? '' }) ); cli.runOrEnd(() => { const files = api.eject(options.items, options.force, options.type); const useAbsolute = config.executionEnvironment.useFullPaths; logger.on(); logger.log('Files ejected:'); files.forEach((file) => { const fileName = useAbsolute ? file : path.relative(config.locations.projectRoot, file); logger.log(`\t${fileName}`, 'blue'); }); }); }); program .command('run [command] [...args]') .description(t('descriptions.commands.run')) .option( '-t, --type <project-type>', t('cli:descriptions.args.type', { options: `"${Object.keys(config.projectTypes).join('", "')}"` }) ) .option( '-p, --package-manager <package-manager>', t('cli:descriptions.args.packageManager', { options: `"${Object.keys(config.packageManagers).join('", "')}"` }), 'npm' ) .option('-s, --silent', t('cli:descriptions.args.silent'), undefined) .option('-D, --debug', t('cli:descriptions.args.debug'), undefined) .option('-j, --use-local-tsconfig-json', t('cli:descriptions.args.useLocalTsconfigJson'), undefined) .action( ( command: string, args: string[], options: PackageManagerBasedOption & GeneralOptions & { useLocalTsconfigJson: boolean } ) => { if (options.debug) { logger.level = LogLevel.Debug; } if (options.silent) { logger.off(); } failIfOptionInvalid(options, 'package-manager', Object.keys(config.packageManagers)); failIfOptionInvalid(options, 'type', Object.keys(config.projectTypes)); config.init(options.type, options.packageManager, options.debug, undefined, options.useLocalTsconfigJson); cli.displayWelcomeForAction( !command ? t('cli:messages.presentingCommands', { projectType: config.executionEnvironment.projectType, packageManager: config.executionEnvironment.packageManager }) : t('cli:messages.executingCommands', { command, projectType: config.executionEnvironment.projectType, packageManager: config.executionEnvironment.packageManager }) ); cli.runOrEnd(() => { api.run(command, args, undefined, options.packageManager, options.useLocalTsconfigJson); }); } ); /** * Throw an error if the given value at the options object (if any) is not a valid * option for the given option name, if the possible values is one of the given * possible values. * * @param options - The object containing all options. * @param optionName - The option name to verify. * @param possibleValues - The possible values the option can take. * * @throws if there is an option given and the value is not one of the possible values. */ export const failIfOptionInvalid = (options: unknown, optionName: string, possibleValues: string[]): void => { const optionNameCamelCased = optionName.replace(/-(.)/g, (_, group1) => (group1 as string).toUpperCase()); const optionValue = (options as Record<string, string>)[optionNameCamelCased]; if (optionValue && !possibleValues.includes(optionValue)) { const message = t('cli:errors.invalidOption', { optionValue, optionName, options: `"${possibleValues.join('", "')}"` }); logger.on(); logger.error(message, 'bgRed'); process.exit(1); } };