ntts
Version:
A CLI tool for refactoring an existing NodeJs application to a fully functional TypeScript application.
127 lines (111 loc) • 5.11 kB
text/typescript
import { readFileSync, writeFileSync } from 'fs';
import { resolve } from 'path';
import { PackageJsonModel, Scripts } from '../models/package-json.model';
import Logger from '../logger/logger';
import FileRename from '../file-rename/file-rename';
import TsconfigHandler from '../tsconfig-handler/tsconfig-handler';
interface NodeCliModel {
preNodeArguments: string[];
node: boolean;
options: string[];
scriptFile?: string;
arguments: string[];
}
class PackageJsonHandler {
private static isNodeScript = (script: string): boolean => /^(.*? +)?node(?! +inspect)(\s+-.*?)* [^\-\s].*$/.test(script);
private static parseNodeScript = (script: string): NodeCliModel => script
.replace(/\s+/g, ' ')
.split(' ')
.reduce((acc: NodeCliModel, parameter: string) => {
if (!acc.node && parameter !== 'node') {
return { ...acc, preNodeArguments: [...acc.preNodeArguments, parameter] };
}
if (!acc.node && parameter === 'node') {
return { ...acc, node: true };
}
if (!acc.scriptFile && parameter.startsWith('-')) {
return { ...acc, options: [...acc.options, parameter] };
}
if (!acc.scriptFile) {
return { ...acc, scriptFile: FileRename.renameFileEnding(parameter, '.ts') };
}
return { ...acc, arguments: [...acc.arguments, parameter] };
}, {
preNodeArguments: [], node: false, options: [], arguments: [],
});
private static fileInTargetPath = (path: string, file?: string): boolean => !!file && resolve(file).startsWith(resolve(path));
private static transformNodeScript = (script: string, path: string, tsconfig: string): string => {
const trimmed = script.trim();
if (!this.isNodeScript(trimmed)) {
return trimmed;
}
const parsedScript = this.parseNodeScript(trimmed);
if (this.fileInTargetPath(path, parsedScript.scriptFile)) {
return (`${parsedScript.preNodeArguments.join(' ')} ts-node -P `
+ `${tsconfig} ${parsedScript.scriptFile} ${parsedScript.arguments.join(' ')}`).trim();
}
return trimmed;
};
private static splitScript = (script: string): string[] => script
.split(' && ')
.reduce((acc: string[], s) => acc.concat(s.split(' & ')), []);
private static joinScripts = (scripts: string[], connectors: RegExpMatchArray | null) => scripts
.reduce((acc, s, index) => {
if (index === 0) {
return s;
}
return acc + (connectors ? connectors[index - 1] : ' && ') + s;
}, '');
private static uniqueName = (scripts: Scripts, name: string, counter: number): string => {
const newName = `${name}-${counter}`;
if (Object.prototype.hasOwnProperty.call(scripts, newName)) {
const newCounter = counter + 1;
return this.uniqueName(scripts, name, newCounter);
}
return newName;
};
private static addTscScriptName = (scripts: Scripts, name: string, script: string): Scripts => {
if (Object.prototype.hasOwnProperty.call(scripts, name)) {
const newName = this.uniqueName(scripts, name, 1);
return { ...scripts, [newName]: script };
}
return { ...scripts, [name]: script };
};
static changeMainFile = (packageJson: PackageJsonModel, target: string): PackageJsonModel => {
if (packageJson.main && this.fileInTargetPath(target, packageJson.main)) {
return { ...packageJson, main: FileRename.renameFileEnding(packageJson.main, '.ts') };
}
return packageJson;
};
static addTsScripts = (scripts: Scripts, path: string): Scripts => {
const tsconfig = TsconfigHandler.tsconfigFileName();
const watchScripts = this.addTscScriptName(scripts, 'tsc-watch', `tsc -w -p ${tsconfig}`);
const fullScripts = this.addTscScriptName(watchScripts, 'tsc-build', `tsc -p ${tsconfig}`);
return Object
.entries(fullScripts)
.reduce((acc: Scripts, [name, script]: [string, string]) => {
const connectors = script.match(/ +[&]{1,2} +/g);
const transformed = this.splitScript(script).map((s) => this.transformNodeScript(s, path, tsconfig));
const result = this.joinScripts(transformed, connectors);
return { ...acc, [name]: result };
}, {});
};
static readPackageJson = (): PackageJsonModel => {
const packageJson = JSON.parse(readFileSync('package.json', { encoding: 'utf-8' }));
if (!Object.prototype.hasOwnProperty.call(packageJson, 'scripts')) {
return { ...packageJson, scripts: {} } as PackageJsonModel;
}
return packageJson as PackageJsonModel;
};
static writePackageJson = (packageJson: PackageJsonModel) => {
writeFileSync('package.json', JSON.stringify(packageJson, null, 2));
};
static refactorScripts = (target: string) => {
Logger.info('Adding new scripts to package.json');
const packageJson = this.changeMainFile(PackageJsonHandler.readPackageJson(), target);
const scripts = PackageJsonHandler.addTsScripts(packageJson.scripts, target);
PackageJsonHandler.writePackageJson({ ...packageJson, scripts });
Logger.success('Scripts added to package.json!');
};
}
export default PackageJsonHandler;