UNPKG

@adinsure-ops/ops-cli

Version:

Operations CLI for working with AdInsure

410 lines (408 loc) 18.2 kB
import { __awaiter } from "tslib"; import { Flags, ux } from '@oclif/core'; import chalk from 'chalk'; import fs from 'fs-extra'; import path from 'path'; import CommandBase from '../command.base.js'; class CopyTransformation { constructor(srcPath, dstPath) { this.name = this.constructor.name; this.srcPath = srcPath; this.dstPath = dstPath; } transform() { try { fs.accessSync(this.srcPath, fs.constants.R_OK); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error_) { const error = error_.code === 'ENOENT' ? new Error(`${this.name}: source file does not exist <${this.srcPath}>`) : new Error(`${this.name}: ${error_} <${this.srcPath}>`); throw error; } ux.log(`Copying from ${this.srcPath} to ${this.dstPath}`); try { fs.copySync(this.srcPath, this.dstPath, { overwrite: true }); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { throw new Error(`${this.name}: ${error} <${this.srcPath}>`); } } } class JavascriptTransformation { constructor(srcPath, dstPath, javascriptFile) { this.name = this.constructor.name; this.srcPath = srcPath; this.dstPath = dstPath; this.jsFile = javascriptFile; } importTransformationFunction() { return __awaiter(this, void 0, void 0, function* () { try { const { default: transformationFunction } = yield import(this.jsFile); return transformationFunction; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { if (error.code === 'ERR_MODULE_NOT_FOUND') { throw new Error(`${this.name}: module file ${this.jsFile} not found <${this.srcPath}>`); } else if (error instanceof SyntaxError) { throw new SyntaxError(`${this.name}: ${error.message} in '${this.jsFile}' <${this.srcPath}>`); } throw new Error(`${this.name}: ${error} <${this.srcPath}>`); } }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any executeTransformationFunction(transformationFunction) { return __awaiter(this, void 0, void 0, function* () { try { const result = yield transformationFunction(this.srcPath); if (typeof result !== 'string') { throw new TypeError(`${this.name}: transformation functions must return string, the script returned ${typeof (result)} <${this.srcPath}>`); } return result; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { if (error instanceof TypeError && error.message.search(`/.*must return.*/`) !== -1) { console.log(error.message.search(`/.*must return.*/`)); throw new Error(`${this.name}: invalid function in ${this.jsFile} <${this.srcPath}>`); } /* Rethrow anything errors that were thrown in the above try block */ if (error.message && error.message.search(`${this.name}`.length > 0)) { throw error; } throw new Error(`${this.name}: ${error} <${this.srcPath}>`); } }); } transform() { return __awaiter(this, void 0, void 0, function* () { // Check if source exists, if not, issue a warning try { fs.accessSync(this.srcPath); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { if (error.code === 'ENOENT') { const errMsg = `${this.name}: source does not exist, relying on transformation to find proper source file <${this.srcPath}>`; ux.log(`${chalk.yellow(errMsg)}`); } else { throw new Error(`${this.name}: ${error} <${this.srcPath}>`); } } const msg = `Transforming from ${this.srcPath} to ${this.dstPath} using ${this.jsFile}`; ux.log(`${chalk.yellow(msg)}`); // eslint-disable-next-line no-useless-catch try { // Import transformation function const transFunction = yield this.importTransformationFunction(); // Execute transformation function const transResult = yield this.executeTransformationFunction(transFunction); fs.outputFileSync(this.dstPath, transResult, { encoding: 'utf8', mode: 0o644, flag: 'w' }); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { throw error; } }); } } class RemovalTransformation { constructor(srcPath) { this.name = this.constructor.name; this.srcPath = srcPath; } transform() { try { fs.accessSync(this.srcPath, fs.constants.W_OK); ux.log(`Removing ${this.srcPath}.`); fs.removeSync(this.srcPath); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { if (error.code === 'ENOENT') { const errMsg = `${this.name}: target does not exist, skipping removal <${this.srcPath}>`; ux.log(`${chalk.yellow(errMsg)}`); } else { throw new Error(`${this.name}: ${error} <${this.srcPath}>`); } } } } class PrepareContent extends CommandBase { parseDotfileLine(line, sourceDirectory) { return __awaiter(this, void 0, void 0, function* () { const m = line.match('^([!#])?(.+?)({.*?})?$'); const dotfileLine = { prefix: m[1], source: path.join(sourceDirectory, path.normalize(m[2].trim())), // should it be absolute path? transformData: m[3], }; /* Sanity check on source file name. Should not contain "{", ":", or "}" characters */ const vm = dotfileLine.source.match('[{:}]+'); if (vm !== null) { throw new SyntaxError(`<${PrepareContent.dotfile}> source file name '${dotfileLine.source}' contains invalid character '${vm[0]}'`); } return dotfileLine; }); } readDotfile(sourceDirectory) { return __awaiter(this, void 0, void 0, function* () { let fileContent; try { fileContent = fs.readFileSync(path.join(sourceDirectory, PrepareContent.dotfile), 'utf8'); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { if (error.code === 'ENOENT') { const errMsg = `.opsprepare file not found in ${path.resolve(sourceDirectory)}`; throw new Error(errMsg); } throw new Error(error.message); } const lines = fileContent.split(/\r?\n/).filter(line => line.length > 0); const lineParse = lines.map(line => this.parseDotfileLine(line.trim(), sourceDirectory)); const parsedLines = Promise.all(lineParse).then(lines => lines.filter(line => line.prefix !== '#')); return parsedLines; }); } getTransformation(lineData, baseSourcedir, baseDestinationDir) { return __awaiter(this, void 0, void 0, function* () { const targetPath = path.join(baseDestinationDir, lineData.source); /* Anything prefixed with ! is a removal */ if (lineData.prefix === '!') { return new RemovalTransformation(targetPath); } /* In case transformation data is present, prepare transformations based on requested plugin. */ if (lineData.transformData) { const transformType = lineData.transformData.match('^{(.+?):.*}$')[1]; switch (transformType) { case 'cp': { const mPath = lineData.transformData.match('^{cp:([^:]+)}$'); if (!mPath) { throw new SyntaxError(`<${PrepareContent.dotfile}> syntax '${lineData.transformData}' is invalid for 'cp' transformation type`); } const tPath = path.join(baseDestinationDir, path.dirname(lineData.source), mPath[1]); return new CopyTransformation(lineData.source, tPath); } case 'js': { /* TODO: Some sanity checking will be required after regex matching to make sure everything was matched and is defined */ // Parse transformation data const mData = lineData.transformData.match('^{js:(.+?).js}$'); if (!mData) { throw new SyntaxError(`<${PrepareContent.dotfile}> syntax '${lineData.transformData}' is invalid for 'js' transformation type`); } const jsFile = 'file://' + path.resolve(path.join(baseSourcedir, mData[1])) + '.js'; // File path absolute const filePath = path.resolve(lineData.source); return new JavascriptTransformation(filePath, targetPath, jsFile); } default: { const p = lineData.prefix || ''; const s = lineData.source; const t = lineData.transformData || ''; this.error(`No valid transformation can be extrapolated from ${p}${s}${t}`); } } } /* Return default transformation */ return new CopyTransformation(lineData.source, targetPath); }); } prepareTransformations(linesData, baseSourceDir, baseDestinationDir) { return __awaiter(this, void 0, void 0, function* () { const transformations = linesData.map(line => this.getTransformation(line, baseSourceDir, baseDestinationDir)); return Promise.all(transformations); }); } /* This function divides transformations into execution groups based on their class and runs transformations for each group */ runTransformations(transformations) { return __awaiter(this, void 0, void 0, function* () { const copyTransformList = []; const modlueTransformList = []; const excludeList = []; // Sort transformation into two lists for (const t of transformations) { if (t instanceof CopyTransformation) { copyTransformList.push(t); } else if (t instanceof RemovalTransformation) { excludeList.push(t); } else { modlueTransformList.push(t); } } const t = (transformation) => __awaiter(this, void 0, void 0, function* () { try { transformation.transform(); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { throw Promise.reject(error.message).catch(error_ => { this.log(`${chalk.redBright('ERROR: ' + error_)}`); }); } }); const doCopyTransform = copyTransformList.map(t); const doModuleTransform = modlueTransformList.map(t); const doExclude = excludeList.map(t); /* Copy first, then transform and finally remove files. This should ensure that transformations can overwrite previous copies. Removals can still erase transformations though. If needed, we can remove before transforming. */ yield Promise.all(doCopyTransform); yield Promise.all(doModuleTransform); yield Promise.all(doExclude); }); } /* Validates file system on source repository by checking if files or directories exist */ /* async validate(arr: string[][], source: string): Promise<boolean> { //this.log(`${chalk.blue('Validating if files and directories exist.')}`); cli.action.start(`${chalk.blue('Validating if files and directories exist')}`, '', {stdout: true}); for (let a of arr[0]) { let element_path = path.join(source, a); if(!(fs.existsSync(path.join(element_path)))) { this.log(`${chalk.red('File or directory: ' + path.join(element_path) + ' could not be found')}`); return false; } } for (let a of arr[1]) { let element_path = path.join(source, a); if(!(fs.existsSync(element_path))) { this.log(`${chalk.red('File or directory: ' + element_path + ' could not be found')}`); return false; } } //this.log(`${chalk.green('Validation done.')}`); cli.action.stop(`${chalk.green('done')}`); return true; } */ /* This function copies elements and removes excluded ones. */ /* async copyWorkspace(arr: string[][], dest: string, source: string): Promise<any> { if (await this.validate(arr, source)) { cli.action.start(`${chalk.blue('Preparing content')}`, '', {stdout: true}); arr[0].forEach(a => { this.log('Copying: ', path.join(source, a)); fs.copySync(path.join(source, a), path.join(dest, a), {overwrite: true}); }); this.log(`${chalk.green('Copying done.')}`); this.log(`${chalk.blue('Removing excluded elements.')}`); arr[1].forEach(a => { let curPath = path.join(dest, a); if (fs.pathExistsSync(curPath)) { if (fs.lstatSync(curPath).isFile()) { try { fs.removeSync(curPath); this.log('Removing file: ', curPath); } catch (err: any) { cli.action.stop(`${chalk.red('failed')}`); this.error(error.message); } } else if (fs.lstatSync(curPath).isDirectory()) { try { fs.removeSync(curPath); this.log('Removing dir: ', curPath); } catch (err: any) { cli.action.stop(`${chalk.red('failed')}`); this.error(error.message); } } else { cli.action.stop(`${chalk.red('error')}`); this.error('Type not file or directory'); } } }); this.log(`${chalk.green('Removing done.')}`); cli.action.stop(`${chalk.green('done')}`); } else { cli.action.stop(`${chalk.red('error')}`); this.error('Content validation failed.'); } } */ run() { const _super = Object.create(null, { run: { get: () => super.run } }); return __awaiter(this, void 0, void 0, function* () { _super.run.call(this); const { flags } = yield this.parse(PrepareContent); // read the flags and set paths relative to execution directory let source = path.relative(process.cwd(), flags.source); source = source || '.'; console.log(source); const dest = flags.destination; try { const dotfileLines = yield this.readDotfile(source); this.log(`${chalk.blue('Content transformation started.')}`); const transformations = yield this.prepareTransformations(dotfileLines, source, dest); yield this.runTransformations(transformations); this.log(`${chalk.green('Content prepared at: ', dest)}`); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { this.log(`${chalk.red(error)}`); /* In test mode, exit with status 0 */ if (process.env.OPS_TEST_MODE === 'true') { this.exit(0); } console.log(error.message); this.exit(1); } }); } } PrepareContent.dotfile = '.opsprepare'; PrepareContent.description = `copies files as described in ${PrepareContent.dotfile} which is at root of source directory`; PrepareContent.usage = 'prepare-content -d <destination> [-s <source>]'; PrepareContent.examples = [ `$ ops prepare-content -d .\\my_workspace`, `$ ops prepare-content -d .\\my_workspace -s .\\my_repository`, `$ ops prepare-content -d C:\\git\\my_workspace`, `$ ops prepare-content -d C:\\git\\my_workspace -s C:\\git\\my_repository\\my_repository`, ]; PrepareContent.flags = { destination: Flags.string({ char: 'd', description: 'where to copy the workspace files', required: true, }), source: Flags.string({ char: 's', description: '[optional] source directory, default: current dir', required: false, default: '.', }), }; export default PrepareContent;