@nestjs/schematics
Version:
Nest - modern, fast, powerful node.js web framework (@schematics)
222 lines (221 loc) • 8.78 kB
JavaScript
import { join, normalize, strings } from '@angular-devkit/core';
import { apply, branchAndMerge, chain, mergeWith, move, noop, SchematicsException, template, url, } from '@angular-devkit/schematics';
import { parse } from 'jsonc-parser';
import { formatFiles } from '../../utils/format-files.rule.js';
import { createModuleNameMapper, inPlaceSortByKeys, normalizeToKebabOrSnakeCase, } from '../../utils/index.js';
import { DEFAULT_LANGUAGE, DEFAULT_LIB_PATH, DEFAULT_PATH_NAME, PROJECT_TYPE, } from '../defaults.js';
import { FileSystemReader } from '../readers/index.js';
import { isEsmProject } from '../../utils/source-root.helpers.js';
export function main(options) {
options = transform(options);
return chain([
addLibraryToCliOptions(options.path, options.name),
updatePackageJson(options),
updateJestEndToEnd(options),
updateTsConfig(options.name, options.prefix, options.path),
(tree) => {
options.isEsm = isEsmProject(tree);
return tree;
},
branchAndMerge(mergeWith(generate(options))),
options.format === true ? formatFiles() : noop(),
]);
}
function getDefaultLibraryPrefix(defaultLibraryPrefix = '@app') {
const fileSystemReader = new FileSystemReader(process.cwd());
const content = fileSystemReader.readSyncAnyOf([
'nest-cli.json',
'.nestcli.json',
'.nest-cli.json',
'nest.json',
]);
try {
const nestJson = JSON.parse(content || '{}');
if (Object.prototype.hasOwnProperty.call(nestJson, 'defaultLibraryPrefix')) {
return nestJson['defaultLibraryPrefix'];
}
}
catch {
return defaultLibraryPrefix;
}
return defaultLibraryPrefix;
}
function transform(options) {
const target = Object.assign({}, options);
const defaultSourceRoot = options.rootDir !== undefined ? options.rootDir : DEFAULT_LIB_PATH;
if (!target.name) {
throw new SchematicsException('Option (name) is required.');
}
target.language = target.language ? target.language : DEFAULT_LANGUAGE;
target.name = normalizeToKebabOrSnakeCase(target.name);
target.path =
target.path !== undefined
? join(normalize(defaultSourceRoot), target.path)
: normalize(defaultSourceRoot);
target.prefix = target.prefix || getDefaultLibraryPrefix();
target.specFileSuffix = normalizeToKebabOrSnakeCase(target.specFileSuffix || 'spec');
return target;
}
function updatePackageJson(options) {
return (host) => {
if (!host.exists('package.json')) {
return host;
}
const distRoot = join(options.path, options.name, 'src');
const packageKey = options.prefix
? options.prefix + '/' + options.name
: options.name;
return updateJsonFile(host, 'package.json', (packageJson) => {
updateNpmScripts(packageJson.scripts, options);
updateJestConfig(packageJson.jest, options, packageKey, distRoot);
});
};
}
function updateJestConfig(jestOptions, options, packageKey, distRoot) {
if (!jestOptions) {
return;
}
if (jestOptions.rootDir === DEFAULT_PATH_NAME) {
jestOptions.rootDir = '.';
jestOptions.coverageDirectory = './coverage';
}
const defaultSourceRoot = options.rootDir !== undefined ? options.rootDir : DEFAULT_LIB_PATH;
const jestSourceRoot = `<rootDir>/${defaultSourceRoot}/`;
if (!jestOptions.roots) {
jestOptions.roots = ['<rootDir>/src/', jestSourceRoot];
}
else if (jestOptions.roots.indexOf(jestSourceRoot) < 0) {
jestOptions.roots.push(jestSourceRoot);
}
if (!jestOptions.moduleNameMapper) {
jestOptions.moduleNameMapper = {};
}
const packageRoot = join('<rootDir>', distRoot);
const newMapper = createModuleNameMapper(packageKey, packageRoot);
Object.assign(jestOptions.moduleNameMapper, newMapper);
inPlaceSortByKeys(jestOptions.moduleNameMapper);
}
function updateNpmScripts(scripts, options) {
if (!scripts) {
return;
}
const defaultFormatScriptName = 'format';
if (!scripts[defaultFormatScriptName]) {
return;
}
if (scripts[defaultFormatScriptName] &&
scripts[defaultFormatScriptName].indexOf(DEFAULT_PATH_NAME) >= 0) {
const defaultSourceRoot = options.rootDir !== undefined ? options.rootDir : DEFAULT_LIB_PATH;
scripts[defaultFormatScriptName] =
`prettier --write "src/**/*.ts" "test/**/*.ts" "${defaultSourceRoot}/**/*.ts"`;
}
}
function updateJestEndToEnd(options) {
return (host) => {
const pathToFile = join('test', 'jest-e2e.json');
if (!host.exists(pathToFile)) {
return host;
}
const distRoot = join(options.path, options.name, 'src');
const packageKey = options.prefix
? options.prefix + '/' + options.name
: options.name;
return updateJsonFile(host, pathToFile, (jestOptions) => {
if (!jestOptions.moduleNameMapper) {
jestOptions.moduleNameMapper = {};
}
const packageRoot = '<rootDir>/../' + distRoot;
const newMapper = createModuleNameMapper(packageKey, packageRoot);
Object.assign(jestOptions.moduleNameMapper, newMapper);
inPlaceSortByKeys(jestOptions.moduleNameMapper);
});
};
}
function updateJsonFile(host, path, callback) {
const source = host.read(path);
if (source) {
const sourceText = source.toString('utf-8');
const json = parse(sourceText);
callback(json);
host.overwrite(path, JSON.stringify(json, null, 2));
}
return host;
}
function updateTsConfig(packageName, packagePrefix, root) {
return (host) => {
if (!host.exists('tsconfig.json')) {
return host;
}
const distRoot = join(root, packageName, 'src');
const packageKey = packagePrefix
? packagePrefix + '/' + packageName
: packageName;
return updateJsonFile(host, 'tsconfig.json', (tsconfig) => {
if (!tsconfig.compilerOptions) {
tsconfig.compilerOptions = {};
}
delete tsconfig.compilerOptions.baseUrl;
if (!tsconfig.compilerOptions.paths) {
tsconfig.compilerOptions.paths = {};
}
if (!tsconfig.compilerOptions.paths[packageKey]) {
tsconfig.compilerOptions.paths[packageKey] = [];
}
tsconfig.compilerOptions.paths[packageKey].push('./' + distRoot);
const deepPackagePath = packageKey + '/*';
if (!tsconfig.compilerOptions.paths[deepPackagePath]) {
tsconfig.compilerOptions.paths[deepPackagePath] = [];
}
tsconfig.compilerOptions.paths[deepPackagePath].push('./' + distRoot + '/*');
inPlaceSortByKeys(tsconfig.compilerOptions.paths);
});
};
}
function addLibraryToCliOptions(projectRoot, projectName) {
const rootPath = join(projectRoot, projectName);
const project = {
type: PROJECT_TYPE.LIBRARY,
root: rootPath,
entryFile: 'index',
sourceRoot: join(rootPath, 'src'),
compilerOptions: {
tsConfigPath: join(rootPath, 'tsconfig.lib.json'),
},
};
return (host) => {
const nestFileExists = host.exists('nest.json');
let nestCliFileExists = host.exists('nest-cli.json');
if (!nestCliFileExists && !nestFileExists) {
host.create('nest-cli.json', '{}');
nestCliFileExists = true;
}
return updateJsonFile(host, nestCliFileExists ? 'nest-cli.json' : 'nest.json', (optionsFile) => {
if (!optionsFile.projects) {
optionsFile.projects = {};
}
if (!optionsFile.compilerOptions) {
optionsFile.compilerOptions = {};
}
if (optionsFile.compilerOptions.builder === undefined) {
optionsFile.compilerOptions.builder = 'rspack';
}
if (optionsFile.projects[projectName]) {
throw new SchematicsException(`Project "${projectName}" exists in this workspace already.`);
}
optionsFile.projects[projectName] = project;
inPlaceSortByKeys(optionsFile.projects);
});
};
}
function generate(options) {
return (context) => {
const path = join(options.path, options.name);
return apply(url(join('./files', options.language)), [
template({
...strings,
...options,
}),
move(path),
])(context);
};
}