@nestjs/schematics
Version:
Nest - modern, fast, powerful node.js web framework (@schematics)
186 lines (185 loc) • 5.88 kB
JavaScript
import { parse } from 'jsonc-parser';
import { createSourceFile, forEachChild, isIdentifier, isImportDeclaration, isNoSubstitutionTemplateLiteral, isStringLiteral, ScriptTarget, } from 'typescript';
import { JSONFile } from '../../utils/json-file.util.js';
import { getPackageJsonDependency, } from '../../utils/dependencies.utils.js';
export const MIGRATION_GUIDE_URL = 'https://docs.nestjs.com/migration-guide';
export class UpgradeReport {
changes = [];
notes = [];
warnings = [];
actions = [];
change(message) {
this.changes.push(message);
}
note(message) {
this.notes.push(message);
}
warn(message) {
this.warnings.push(message);
}
action(message) {
this.actions.push(message);
}
print(context) {
const { logger } = context;
const section = (title, items, bullet, level) => {
if (items.length === 0) {
return;
}
logger[level]('');
logger[level](title);
items.forEach((item) => logger[level](` ${bullet} ${item}`));
};
section('Changes applied:', this.changes, '✔', 'info');
section('Notes:', this.notes, 'ℹ', 'info');
section('Please review:', this.warnings, '⚠', 'warn');
section('Action required:', this.actions, '➜', 'warn');
logger.info('');
logger.info(`Full migration guide: ${MIGRATION_GUIDE_URL}`);
}
}
export function parseMajor(range) {
if (!range) {
return null;
}
const match = /^\s*(?:[\^~]|>=?|=)?\s*v?(\d+)(?:\.|$|\s|-|x)/.exec(range.trim());
return match ? Number(match[1]) : null;
}
export function parseMajorMinor(range) {
if (!range) {
return null;
}
const match = /^\s*(?:[\^~]|>=?|=)?\s*v?(\d+)\.(\d+)/.exec(range.trim());
return match ? [Number(match[1]), Number(match[2])] : null;
}
export function parseVersion(version) {
const match = /(\d+)\.(\d+)\.(\d+)/.exec(version);
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : [];
}
export function readJsonFile(tree, path) {
const buffer = tree.read(path);
if (!buffer) {
return null;
}
try {
return parse(buffer.toString('utf-8'));
}
catch {
return null;
}
}
export function readPackageJson(tree) {
return readJsonFile(tree, 'package.json');
}
const NEST_CLI_CONFIG_FILES = [
'nest-cli.json',
'.nestcli.json',
'.nest-cli.json',
'nest.json',
];
export function findNestCliConfigPath(tree) {
return NEST_CLI_CONFIG_FILES.find((candidate) => tree.exists(candidate));
}
export function readNestCliConfig(tree) {
const path = findNestCliConfigPath(tree);
const config = path ? (readJsonFile(tree, path) ?? {}) : {};
return {
sourceRoot: config.sourceRoot ?? 'src',
entryFile: config.entryFile ?? 'main',
projects: config.projects ?? {},
};
}
export function removePackageJsonDependency(tree, name) {
const dependency = getPackageJsonDependency(tree, name);
if (!dependency) {
return null;
}
const json = new JSONFile(tree, '/package.json');
json.remove([dependency.type, name]);
return dependency.type;
}
export function getSourceRoots(tree) {
const config = readNestCliConfig(tree);
const roots = new Set([
config.sourceRoot,
'src',
'apps',
'libs',
'test',
]);
Object.values(config.projects).forEach((project) => {
if (project?.sourceRoot) {
roots.add(project.sourceRoot);
}
});
return [...roots].map((root) => root.replace(/^\.?\//, '').replace(/\/$/, ''));
}
export function isTsSourceFile(path) {
return path.endsWith('.ts') && !path.endsWith('.d.ts');
}
export function collectSourceFiles(tree, roots) {
const files = new Set();
for (const root of roots) {
tree.getDir(root).visit((path) => {
if (isTsSourceFile(path) &&
!path.includes('/node_modules/') &&
!path.includes('/dist/')) {
files.add(path);
}
});
}
return [...files].sort();
}
export function parseSource(path, content) {
return createSourceFile(path, content, ScriptTarget.Latest, true);
}
export function forEachDescendant(node, visitor) {
visitor(node);
forEachChild(node, (child) => forEachDescendant(child, visitor));
}
export function importsAnyOf(source, modules) {
return source.statements.some((statement) => isImportDeclaration(statement) &&
isStringLiteral(statement.moduleSpecifier) &&
modules.includes(statement.moduleSpecifier.text));
}
export function getPropertyName(property) {
const name = property.name;
if (!name) {
return undefined;
}
if (isIdentifier(name) ||
isStringLiteral(name) ||
isNoSubstitutionTemplateLiteral(name)) {
return name.text;
}
return undefined;
}
export function findProperty(literal, name) {
return literal.properties.find((property) => getPropertyName(property) === name);
}
export function removePropertyEdit(content, property) {
const start = property.getFullStart();
let end = property.getEnd();
while (end < content.length && /\s/.test(content[end])) {
end++;
}
if (content[end] === ',') {
end++;
}
else {
end = property.getEnd();
}
return { start, end, text: '' };
}
export function applyEdits(content, edits) {
const sorted = [...edits].sort((a, b) => b.start - a.start);
let result = content;
for (const edit of sorted) {
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
}
return result;
}
export function quoteOf(node, source) {
const text = node.getText(source);
return text.startsWith('"') ? '"' : "'";
}