UNPKG

@nestjs/schematics

Version:

Nest - modern, fast, powerful node.js web framework (@schematics)

222 lines (221 loc) 8.78 kB
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); }; }