326 lines
9.4 KiB
TypeScript
326 lines
9.4 KiB
TypeScript
import {
|
|
generateFiles,
|
|
joinPathFragments,
|
|
NxJsonConfiguration,
|
|
readJson,
|
|
readWorkspaceConfiguration,
|
|
Tree,
|
|
updateJson,
|
|
updateWorkspaceConfiguration,
|
|
writeJson,
|
|
} from '@nrwl/devkit';
|
|
import { DEFAULT_NRWL_PRETTIER_CONFIG } from '@nrwl/workspace/src/generators/workspace/workspace';
|
|
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 { dirname, join } from 'path';
|
|
import { angularDevkitVersion, nxVersion } from '../../../utils/versions';
|
|
import { GeneratorOptions } from '../schema';
|
|
import {
|
|
getCypressConfigFile,
|
|
getE2eKey,
|
|
getE2eProject,
|
|
isCypressE2eProject,
|
|
isProtractorE2eProject,
|
|
} from './e2e-utils';
|
|
import { WorkspaceProjects } from './types';
|
|
|
|
// TODO: most of the validation here will be moved to the app migrator when
|
|
// support for multiple apps is added. This will only contain workspace-wide
|
|
// validation.
|
|
export function validateWorkspace(
|
|
tree: Tree,
|
|
projects: WorkspaceProjects
|
|
): void {
|
|
try {
|
|
if (!tree.exists('package.json')) {
|
|
throw new Error('Cannot find package.json');
|
|
}
|
|
|
|
if (!tree.exists('angular.json')) {
|
|
throw new Error('Cannot find angular.json');
|
|
}
|
|
|
|
const e2eKey = getE2eKey(projects);
|
|
const e2eApp = getE2eProject(projects);
|
|
|
|
if (!e2eApp) {
|
|
return;
|
|
}
|
|
|
|
if (isProtractorE2eProject(e2eApp.config)) {
|
|
if (tree.exists(e2eApp.config.targets.e2e.options.protractorConfig)) {
|
|
return;
|
|
}
|
|
|
|
console.info(
|
|
`Make sure the "${e2eKey}.architect.e2e.options.protractorConfig" is valid or the "${e2eKey}" project is removed from "angular.json".`
|
|
);
|
|
throw new Error(
|
|
`An e2e project with Protractor was found but "${e2eApp.config.targets.e2e.options.protractorConfig}" could not be found.`
|
|
);
|
|
}
|
|
|
|
if (isCypressE2eProject(e2eApp.config)) {
|
|
const configFile = getCypressConfigFile(e2eApp.config);
|
|
if (configFile && !tree.exists(configFile)) {
|
|
throw new Error(
|
|
`An e2e project with Cypress was found but "${configFile}" could not be found.`
|
|
);
|
|
}
|
|
|
|
if (!tree.exists('cypress')) {
|
|
throw new Error(
|
|
`An e2e project with Cypress was found but the "cypress" directory could not be found.`
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
throw new Error(
|
|
`An e2e project was found but it's using an unsupported executor "${e2eApp.config.targets.e2e.executor}".`
|
|
);
|
|
} catch (e) {
|
|
console.error(e.message);
|
|
console.error(
|
|
'Your workspace could not be converted into an Nx Workspace because of the above error.'
|
|
);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
export function createNxJson(
|
|
tree: Tree,
|
|
options: GeneratorOptions,
|
|
setWorkspaceLayoutAsNewProjectRoot: boolean = false
|
|
): void {
|
|
const { projects = {}, newProjectRoot = '' } = readJson(tree, 'angular.json');
|
|
// TODO: temporarily leaving this here because it's the old behavior for a
|
|
// minimal migration, will be removed in a later PR
|
|
const hasLibraries = Object.keys(projects).find(
|
|
(project) =>
|
|
projects[project].projectType &&
|
|
projects[project].projectType !== 'application'
|
|
);
|
|
if (Object.keys(projects).length !== 1 || hasLibraries) {
|
|
throw new Error(
|
|
`The schematic can only be used with Angular CLI workspaces with a single application.`
|
|
);
|
|
}
|
|
|
|
writeJson<NxJsonConfiguration>(tree, 'nx.json', {
|
|
npmScope: options.npmScope,
|
|
affected: {
|
|
defaultBase: options.defaultBase ?? deduceDefaultBase(),
|
|
},
|
|
implicitDependencies: {
|
|
'package.json': {
|
|
dependencies: '*',
|
|
devDependencies: '*',
|
|
},
|
|
'.eslintrc.json': '*',
|
|
},
|
|
tasksRunnerOptions: {
|
|
default: {
|
|
runner: 'nx/tasks-runners/default',
|
|
options: {
|
|
cacheableOperations: ['build', 'lint', 'test', 'e2e'],
|
|
},
|
|
},
|
|
},
|
|
targetDependencies: {
|
|
build: [
|
|
{
|
|
target: 'build',
|
|
projects: 'dependencies',
|
|
},
|
|
],
|
|
},
|
|
workspaceLayout: setWorkspaceLayoutAsNewProjectRoot
|
|
? { appsDir: newProjectRoot, libsDir: newProjectRoot }
|
|
: undefined,
|
|
});
|
|
}
|
|
|
|
export function decorateAngularCli(tree: Tree): void {
|
|
const nrwlWorkspacePath = require.resolve('@nrwl/workspace/package.json');
|
|
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 {
|
|
if (!json.scripts) 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;
|
|
workspaceConfig.cli = workspaceConfig.cli ?? {};
|
|
if (!workspaceConfig.cli.defaultCollection) {
|
|
workspaceConfig.cli.defaultCollection = '@nrwl/angular';
|
|
}
|
|
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 updateTsLint(tree: Tree): void {
|
|
if (!tree.exists(`tslint.json`)) {
|
|
return;
|
|
}
|
|
|
|
updateJson(tree, 'tslint.json', (tslintJson) => {
|
|
[
|
|
'no-trailing-whitespace',
|
|
'one-line',
|
|
'quotemark',
|
|
'typedef-whitespace',
|
|
'whitespace',
|
|
].forEach((key) => {
|
|
tslintJson[key] = undefined;
|
|
});
|
|
tslintJson.rulesDirectory = tslintJson.rulesDirectory ?? [];
|
|
tslintJson.rulesDirectory.push('node_modules/@nrwl/workspace/src/tslint');
|
|
tslintJson.rules['nx-enforce-module-boundaries'] = [
|
|
true,
|
|
{
|
|
allow: [],
|
|
depConstraints: [{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] }],
|
|
},
|
|
];
|
|
return tslintJson;
|
|
});
|
|
}
|
|
|
|
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: '',
|
|
}
|
|
);
|
|
}
|
|
|
|
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,
|
|
});
|
|
}
|
|
}
|
|
|
|
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: '.' }
|
|
);
|
|
}
|
|
}
|