412 lines
12 KiB
TypeScript
412 lines
12 KiB
TypeScript
import type { NxJsonConfiguration, Tree } from '@nrwl/devkit';
|
|
import {
|
|
generateFiles,
|
|
getProjects,
|
|
joinPathFragments,
|
|
readJson,
|
|
readWorkspaceConfiguration,
|
|
updateJson,
|
|
updateProjectConfiguration,
|
|
updateWorkspaceConfiguration,
|
|
writeJson,
|
|
} from '@nrwl/devkit';
|
|
import { Linter, lintInitGenerator } from '@nrwl/linter';
|
|
import { DEFAULT_NRWL_PRETTIER_CONFIG } from '@nrwl/workspace/src/generators/new/generate-workspace-files';
|
|
import { deduceDefaultBase } from '@nrwl/workspace/src/utilities/default-base';
|
|
import { resolveUserExistingPrettierConfig } from '@nrwl/workspace/src/utilities/prettier';
|
|
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript';
|
|
import { prettierVersion } from '@nrwl/workspace/src/utils/versions';
|
|
import { readFileSync } from 'fs';
|
|
import { readModulePackageJson } from 'nx/src/utils/package-json';
|
|
import { dirname, join } from 'path';
|
|
import { angularDevkitVersion, nxVersion } from '../../../utils/versions';
|
|
import type { ProjectMigrator } from '../migrators';
|
|
import type { GeneratorOptions } from '../schema';
|
|
import type { WorkspaceRootFileTypesInfo } from './types';
|
|
import { workspaceMigrationErrorHeading } from './validation-logging';
|
|
|
|
export function validateWorkspace(tree: Tree): void {
|
|
const errors: string[] = [];
|
|
if (!tree.exists('package.json')) {
|
|
errors.push('The "package.json" file could not be found.');
|
|
}
|
|
if (!tree.exists('angular.json')) {
|
|
errors.push('The "angular.json" file could not be found.');
|
|
}
|
|
|
|
if (!errors.length) {
|
|
return;
|
|
}
|
|
|
|
throw new Error(`${workspaceMigrationErrorHeading}
|
|
|
|
- ${errors.join('\n ')}`);
|
|
}
|
|
|
|
export function createNxJson(tree: Tree, options: GeneratorOptions): void {
|
|
const { npmScope } = options;
|
|
|
|
const targets = getWorkspaceCommonTargets(tree);
|
|
|
|
writeJson<NxJsonConfiguration>(tree, 'nx.json', {
|
|
...(npmScope ? { npmScope } : {}),
|
|
affected: {
|
|
defaultBase: options.defaultBase ?? deduceDefaultBase(),
|
|
},
|
|
tasksRunnerOptions: {
|
|
default: {
|
|
runner: 'nx/tasks-runners/default',
|
|
options: {
|
|
cacheableOperations: [
|
|
'build',
|
|
targets.test ? 'test' : undefined,
|
|
targets.lint ? 'lint' : undefined,
|
|
targets.e2e ? 'e2e' : undefined,
|
|
].filter(Boolean),
|
|
},
|
|
},
|
|
},
|
|
namedInputs: {
|
|
sharedGlobals: [],
|
|
default: ['{projectRoot}/**/*', 'sharedGlobals'],
|
|
production: [
|
|
'default',
|
|
...(targets.test
|
|
? [
|
|
'!{projectRoot}/tsconfig.spec.json',
|
|
'!{projectRoot}/**/*.spec.[jt]s',
|
|
'!{projectRoot}/karma.conf.js',
|
|
]
|
|
: []),
|
|
targets.lint ? '!{projectRoot}/.eslintrc.json' : undefined,
|
|
].filter(Boolean),
|
|
},
|
|
targetDefaults: {
|
|
build: {
|
|
dependsOn: ['^build'],
|
|
inputs: ['production', '^production'],
|
|
},
|
|
test: targets.test
|
|
? {
|
|
inputs: ['default', '^production', '{workspaceRoot}/karma.conf.js'],
|
|
}
|
|
: undefined,
|
|
lint: targets.lint
|
|
? {
|
|
inputs: ['default', '{workspaceRoot}/.eslintrc.json'],
|
|
}
|
|
: undefined,
|
|
e2e: targets.e2e
|
|
? {
|
|
inputs: ['default', '^production'],
|
|
}
|
|
: undefined,
|
|
},
|
|
});
|
|
}
|
|
|
|
function getWorkspaceCommonTargets(tree: Tree): {
|
|
e2e: boolean;
|
|
lint: boolean;
|
|
test: boolean;
|
|
} {
|
|
const targets = { e2e: false, lint: false, test: false };
|
|
const projects = getProjects(tree);
|
|
|
|
for (const [, project] of projects) {
|
|
if (!targets.e2e && project.targets?.e2e) {
|
|
targets.e2e = true;
|
|
}
|
|
if (!targets.lint && project.targets?.lint) {
|
|
targets.lint = true;
|
|
}
|
|
if (!targets.test && project.targets?.test) {
|
|
targets.test = true;
|
|
}
|
|
|
|
if (targets.e2e && targets.lint && targets.test) {
|
|
return targets;
|
|
}
|
|
}
|
|
|
|
return targets;
|
|
}
|
|
|
|
export function decorateAngularCli(tree: Tree): void {
|
|
const nrwlWorkspacePath = readModulePackageJson('@nrwl/workspace').path;
|
|
const decorateCli = readFileSync(
|
|
join(
|
|
dirname(nrwlWorkspacePath),
|
|
'src/generators/utils/decorate-angular-cli.js__tmpl__'
|
|
),
|
|
'utf-8'
|
|
);
|
|
tree.write('decorate-angular-cli.js', decorateCli);
|
|
|
|
updateJson(tree, 'package.json', (json) => {
|
|
if (
|
|
json.scripts &&
|
|
json.scripts.postinstall &&
|
|
!json.scripts.postinstall.includes('decorate-angular-cli.js')
|
|
) {
|
|
// if exists, add execution of this script
|
|
json.scripts.postinstall += ' && node ./decorate-angular-cli.js';
|
|
} else {
|
|
json.scripts ??= {};
|
|
// if doesn't exist, set to execute this script
|
|
json.scripts.postinstall = 'node ./decorate-angular-cli.js';
|
|
}
|
|
if (json.scripts.ng) {
|
|
json.scripts.ng = 'nx';
|
|
}
|
|
return json;
|
|
});
|
|
}
|
|
|
|
export function updateWorkspaceConfigDefaults(tree: Tree): void {
|
|
const workspaceConfig = readWorkspaceConfiguration(tree);
|
|
delete (workspaceConfig as any).newProjectRoot;
|
|
if (workspaceConfig.cli) {
|
|
delete (workspaceConfig as any).defaultCollection;
|
|
}
|
|
updateWorkspaceConfiguration(tree, workspaceConfig);
|
|
}
|
|
|
|
export function updateRootTsConfig(tree: Tree): void {
|
|
const tsconfig = readJson(tree, getRootTsConfigPathInTree(tree));
|
|
tsconfig.compilerOptions.paths ??= {};
|
|
tsconfig.compilerOptions.baseUrl = '.';
|
|
tsconfig.compilerOptions.rootDir = '.';
|
|
tsconfig.exclude = Array.from(
|
|
new Set([...(tsconfig.exclude ?? []), 'node_modules', 'tmp'])
|
|
);
|
|
writeJson(tree, 'tsconfig.base.json', tsconfig);
|
|
|
|
if (tree.exists('tsconfig.json')) {
|
|
tree.delete('tsconfig.json');
|
|
}
|
|
}
|
|
|
|
export function updatePackageJson(tree: Tree): void {
|
|
updateJson(tree, 'package.json', (packageJson) => {
|
|
packageJson.scripts = packageJson.scripts ?? {};
|
|
Object.keys(packageJson.scripts).forEach((script) => {
|
|
packageJson.scripts[script] = packageJson.scripts[script]
|
|
.replace(/^ng /, 'nx ')
|
|
.replace(/ ng /, ' nx ');
|
|
});
|
|
|
|
packageJson.devDependencies = packageJson.devDependencies ?? {};
|
|
packageJson.dependencies = packageJson.dependencies ?? {};
|
|
if (!packageJson.devDependencies['@angular/cli']) {
|
|
packageJson.devDependencies['@angular/cli'] = angularDevkitVersion;
|
|
}
|
|
if (!packageJson.devDependencies['@nrwl/workspace']) {
|
|
packageJson.devDependencies['@nrwl/workspace'] = nxVersion;
|
|
}
|
|
if (!packageJson.devDependencies['nx']) {
|
|
packageJson.devDependencies['nx'] = nxVersion;
|
|
}
|
|
if (!packageJson.devDependencies['prettier']) {
|
|
packageJson.devDependencies['prettier'] = prettierVersion;
|
|
}
|
|
|
|
return packageJson;
|
|
});
|
|
}
|
|
|
|
export function updateRootEsLintConfig(
|
|
tree: Tree,
|
|
existingEsLintConfig: any | undefined,
|
|
unitTestRunner?: string
|
|
): void {
|
|
if (tree.exists('.eslintrc.json')) {
|
|
/**
|
|
* If it still exists it means that there was no project at the root of the
|
|
* workspace, so it was not moved. In that case, we remove the file so the
|
|
* init generator do its work. We still receive the content of the file,
|
|
* so we update it after the init generator has run.
|
|
*/
|
|
tree.delete('.eslintrc.json');
|
|
}
|
|
|
|
lintInitGenerator(tree, { linter: Linter.EsLint, unitTestRunner });
|
|
|
|
if (!existingEsLintConfig) {
|
|
// There was no eslint config in the root, so we keep the generated one as-is.
|
|
return;
|
|
}
|
|
|
|
existingEsLintConfig.ignorePatterns = ['**/*'];
|
|
existingEsLintConfig.plugins = Array.from(
|
|
new Set([...(existingEsLintConfig.plugins ?? []), '@nrwl/nx'])
|
|
);
|
|
existingEsLintConfig.overrides?.forEach((override) => {
|
|
if (!override.parserOptions?.project) {
|
|
return;
|
|
}
|
|
|
|
delete override.parserOptions.project;
|
|
});
|
|
// add the @nrwl/nx/enforce-module-boundaries rule
|
|
existingEsLintConfig.overrides = [
|
|
...(existingEsLintConfig.overrides ?? []),
|
|
{
|
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
|
rules: {
|
|
'@nrwl/nx/enforce-module-boundaries': [
|
|
'error',
|
|
{
|
|
enforceBuildableLibDependency: true,
|
|
allow: [],
|
|
depConstraints: [
|
|
{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
];
|
|
|
|
writeJson(tree, '.eslintrc.json', existingEsLintConfig);
|
|
}
|
|
|
|
export function cleanupEsLintPackages(tree: Tree): void {
|
|
updateJson(tree, 'package.json', (json) => {
|
|
if (json.devDependencies?.['@angular-eslint/builder']) {
|
|
delete json.devDependencies['@angular-eslint/builder'];
|
|
}
|
|
if (json.dependencies?.['@angular-eslint/builder']) {
|
|
delete json.dependencies['@angular-eslint/builder'];
|
|
}
|
|
if (json.devDependencies?.['@angular-eslint/schematics']) {
|
|
delete json.devDependencies['@angular-eslint/schematics'];
|
|
}
|
|
if (json.dependencies?.['@angular-eslint/schematics']) {
|
|
delete json.dependencies['@angular-eslint/schematics'];
|
|
}
|
|
|
|
return json;
|
|
});
|
|
}
|
|
|
|
export async function createWorkspaceFiles(tree: Tree): Promise<void> {
|
|
updateVsCodeRecommendedExtensions(tree);
|
|
await updatePrettierConfig(tree);
|
|
|
|
generateFiles(tree, joinPathFragments(__dirname, '../files/root'), '.', {
|
|
tmpl: '',
|
|
dot: '.',
|
|
rootTsConfigPath: getRootTsConfigPathInTree(tree),
|
|
});
|
|
}
|
|
|
|
export function createRootKarmaConfig(tree: Tree): void {
|
|
generateFiles(
|
|
tree,
|
|
joinPathFragments(__dirname, '../files/root-karma'),
|
|
'.',
|
|
{
|
|
tmpl: '',
|
|
}
|
|
);
|
|
}
|
|
|
|
export function getWorkspaceRootFileTypesInfo(
|
|
tree: Tree,
|
|
migrators: ProjectMigrator[]
|
|
): WorkspaceRootFileTypesInfo {
|
|
const workspaceRootFileTypesInfo: WorkspaceRootFileTypesInfo = {
|
|
eslint: false,
|
|
karma: false,
|
|
};
|
|
|
|
if (tree.exists('.eslintrc.json')) {
|
|
workspaceRootFileTypesInfo.eslint = true;
|
|
}
|
|
if (tree.exists('karma.conf.js')) {
|
|
workspaceRootFileTypesInfo.karma = true;
|
|
}
|
|
|
|
if (workspaceRootFileTypesInfo.eslint && workspaceRootFileTypesInfo.karma) {
|
|
return workspaceRootFileTypesInfo;
|
|
}
|
|
|
|
for (const migrator of migrators) {
|
|
const projectInfo = migrator.getWorkspaceRootFileTypesInfo();
|
|
workspaceRootFileTypesInfo.eslint =
|
|
workspaceRootFileTypesInfo.eslint || projectInfo.eslint;
|
|
workspaceRootFileTypesInfo.karma =
|
|
workspaceRootFileTypesInfo.karma || projectInfo.karma;
|
|
|
|
if (workspaceRootFileTypesInfo.eslint && workspaceRootFileTypesInfo.karma) {
|
|
return workspaceRootFileTypesInfo;
|
|
}
|
|
}
|
|
|
|
return workspaceRootFileTypesInfo;
|
|
}
|
|
|
|
export function updateVsCodeRecommendedExtensions(tree: Tree): void {
|
|
const recommendations = [
|
|
'nrwl.angular-console',
|
|
'angular.ng-template',
|
|
'dbaeumer.vscode-eslint',
|
|
'esbenp.prettier-vscode',
|
|
];
|
|
if (tree.exists('.vscode/extensions.json')) {
|
|
updateJson(
|
|
tree,
|
|
'.vscode/extensions.json',
|
|
(json: { recommendations?: string[] }) => {
|
|
json.recommendations = json.recommendations || [];
|
|
recommendations.forEach((extension) => {
|
|
if (!json.recommendations.includes(extension)) {
|
|
json.recommendations.push(extension);
|
|
}
|
|
});
|
|
|
|
return json;
|
|
}
|
|
);
|
|
} else {
|
|
writeJson(tree, '.vscode/extensions.json', {
|
|
recommendations,
|
|
});
|
|
}
|
|
}
|
|
|
|
export async function updatePrettierConfig(tree: Tree): Promise<void> {
|
|
const existingPrettierConfig = await resolveUserExistingPrettierConfig();
|
|
if (!existingPrettierConfig) {
|
|
writeJson(tree, '.prettierrc', DEFAULT_NRWL_PRETTIER_CONFIG);
|
|
}
|
|
|
|
if (!tree.exists('.prettierignore')) {
|
|
generateFiles(
|
|
tree,
|
|
joinPathFragments(__dirname, '../files/prettier'),
|
|
'.',
|
|
{ tmpl: '', dot: '.' }
|
|
);
|
|
}
|
|
}
|
|
|
|
export function deleteAngularJson(tree: Tree): void {
|
|
const projects = getProjects(tree);
|
|
for (const [project, config] of projects) {
|
|
config.name = project;
|
|
updateProjectConfiguration(tree, project, config);
|
|
}
|
|
tree.delete('angular.json');
|
|
}
|
|
|
|
export function deleteGitKeepFilesIfNotNeeded(tree: Tree): void {
|
|
if (tree.children('apps').length > 1 && tree.exists('apps/.gitkeep')) {
|
|
tree.delete('apps/.gitkeep');
|
|
}
|
|
if (tree.children('libs').length > 1 && tree.exists('libs/.gitkeep')) {
|
|
tree.delete('libs/.gitkeep');
|
|
}
|
|
}
|