cleanup(angular): add @angular-devkit/build-angular:ng-packagr builder migrator (#12682)

This commit is contained in:
Leosvel Pérez Espinosa 2022-10-24 12:13:33 +02:00 committed by GitHub
parent 2f46797535
commit f4288e6bc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 484 additions and 284 deletions

View File

@ -1,7 +1,7 @@
import type { Tree } from '@nrwl/devkit';
import {
readJson,
readProjectConfiguration,
Tree,
updateJson,
writeJson,
} from '@nrwl/devkit';

View File

@ -1,34 +1,33 @@
import type { Tree } from '@nrwl/devkit';
import {
addDependenciesToPackageJson,
formatFiles,
installPackagesTask,
readJson,
readWorkspaceConfiguration,
Tree,
updateJson,
updateWorkspaceConfiguration,
} from '@nrwl/devkit';
import { nxVersion } from '../../utils/versions';
import type { ProjectMigrator } from './migrators';
import { AppMigrator, LibMigrator } from './migrators';
import type { GeneratorOptions } from './schema';
import { AppMigrator } from './utilities/app.migrator';
import { getAllProjects } from './utilities/get-all-projects';
import { LibMigrator } from './utilities/lib.migrator';
import { normalizeOptions } from './utilities/normalize-options';
import { ProjectMigrator } from './utilities/project.migrator';
import { validateProjects } from './utilities/validate-projects';
import {
cleanupEsLintPackages,
createNxJson,
createRootKarmaConfig,
createWorkspaceFiles,
decorateAngularCli,
getAllProjects,
getWorkspaceCapabilities,
normalizeOptions,
updatePackageJson,
updateRootEsLintConfig,
updateRootTsConfig,
updateWorkspaceConfigDefaults,
validateProjects,
validateWorkspace,
} from './utilities/workspace';
} from './utilities';
export async function migrateFromAngularCli(
tree: Tree,

View File

@ -0,0 +1,179 @@
import type {
ProjectConfiguration,
TargetConfiguration,
Tree,
} from '@nrwl/devkit';
import {
joinPathFragments,
offsetFromRoot,
updateJson,
updateProjectConfiguration,
} from '@nrwl/devkit';
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript';
import { basename } from 'path';
import { addBuildableLibrariesPostCssDependencies } from '../../../utils/dependencies';
import type {
Logger,
ProjectMigrationInfo,
ValidationError,
ValidationResult,
} from '../../utilities';
import { arrayToString } from '../../utilities';
import { BuilderMigrator } from './builder.migrator';
export class AngularDevkitNgPackagrMigrator extends BuilderMigrator {
constructor(
tree: Tree,
project: ProjectMigrationInfo,
projectConfig: ProjectConfiguration,
logger: Logger
) {
super(
tree,
'@angular-devkit/build-angular:ng-packagr',
project,
projectConfig,
logger
);
}
override migrate(): void {
if (!this.targets.size) {
this.logger.warn(
`There is no target in the project configuration using the ${this.builderName} builder. This might not be an issue. ` +
`Skipping updating the build configuration.`
);
return;
}
for (const [name, target] of this.targets) {
this.updateTargetConfiguration(name, target);
this.updateNgPackageJson(name, target);
this.updateTsConfigs(name, target);
this.updateCacheableOperations([name]);
addBuildableLibrariesPostCssDependencies(this.tree);
}
}
override validate(): ValidationResult {
const errors: ValidationError[] = [];
// TODO(leo): keeping restriction until the full refactor is done and we start
// expanding what's supported.
if (this.targets.size > 1) {
errors.push({
message: `There is more than one target using a builder that is used to build the project (${arrayToString(
[...this.targets.keys()]
)}).`,
hint: `Make sure the project only has one target with a builder that is used to build the project.`,
});
}
return errors.length ? errors : null;
}
private updateTargetConfiguration(
targetName: string,
target: TargetConfiguration
): void {
target.executor = '@nrwl/angular:package';
if (
!target.options &&
(!target.configurations || !Object.keys(target.configurations).length)
) {
this.logger.warn(
`The target "${targetName}" is not specifying any options or configurations. Skipping updating the target configuration.`
);
return;
}
['project', 'tsConfig'].forEach((option) => {
if (target.options?.[option]) {
target.options[option] = joinPathFragments(
this.project.newRoot,
basename(target.options[option])
);
}
for (const configuration of Object.values(target.configurations ?? {})) {
configuration[option] =
configuration[option] &&
joinPathFragments(
this.project.newRoot,
basename(configuration[option])
);
}
});
updateProjectConfiguration(this.tree, this.project.name, {
...this.projectConfig,
});
}
private updateNgPackageJson(
targetName: string,
target: TargetConfiguration
): void {
if (!target.options?.project) {
this.logger.warn(
`The "${targetName}" target does not have the "project" option configured. Skipping updating the ng-packagr project file ("ng-package.json").`
);
return;
} else if (!this.tree.exists(target.options.project)) {
this.logger.warn(
`The ng-packagr project file "${this.originalProjectConfig.targets[targetName].options.project}" specified in the "${targetName}" ` +
`target could not be found. Skipping updating the ng-packagr project file.`
);
return;
}
updateJson(this.tree, target.options.project, (ngPackageJson) => {
const offset = offsetFromRoot(this.project.newRoot);
ngPackageJson.$schema =
ngPackageJson.$schema &&
`${offset}node_modules/ng-packagr/ng-package.schema.json`;
ngPackageJson.dest = `${offset}dist/${this.project.name}`;
return ngPackageJson;
});
}
private updateTsConfigs(
targetName: string,
target: TargetConfiguration
): void {
const tsConfigPath =
target.options?.tsConfig ?? target.configurations?.development?.tsConfig;
if (!tsConfigPath) {
this.logger.warn(
`The "${targetName}" target does not have the "tsConfig" option configured. Skipping updating the tsConfig file.`
);
return;
} else if (!this.tree.exists(tsConfigPath)) {
const originalTsConfigPath = target.options?.tsConfig
? this.originalProjectConfig.targets[targetName].options.tsConfig
: this.originalProjectConfig.targets[targetName].configurations
?.development?.tsConfig;
this.logger.warn(
`The tsConfig file "${originalTsConfigPath}" specified in the "${targetName}" target could not be found. Skipping updating the tsConfig file.`
);
return;
}
const rootTsConfigFile = getRootTsConfigPathInTree(this.tree);
const projectOffsetFromRoot = offsetFromRoot(this.projectConfig.root);
this.updateTsConfigFile(
tsConfigPath,
rootTsConfigFile,
projectOffsetFromRoot
);
updateJson(this.tree, tsConfigPath, (json) => {
if (!json.include?.length && !json.files?.length) {
json.include = ['**/*.ts'];
}
return json;
});
}
}

View File

@ -0,0 +1,12 @@
import type { ProjectConfiguration, Tree } from '@nrwl/devkit';
import type { Logger, ProjectMigrationInfo } from '../../utilities';
import type { BuilderMigrator } from './builder.migrator';
export type BuilderMigratorClassType = {
new (
tree: Tree,
project: ProjectMigrationInfo,
projectConfig: ProjectConfiguration,
logger: Logger
): BuilderMigrator;
};

View File

@ -0,0 +1,36 @@
import type {
ProjectConfiguration,
TargetConfiguration,
Tree,
} from '@nrwl/devkit';
import type { Logger, ProjectMigrationInfo } from '../../utilities';
import { Migrator } from '../migrator';
export abstract class BuilderMigrator extends Migrator {
protected targets: Map<string, TargetConfiguration> = new Map();
constructor(
tree: Tree,
public readonly builderName: string,
project: ProjectMigrationInfo,
projectConfig: ProjectConfiguration,
logger: Logger
) {
super(tree, projectConfig, logger);
this.project = project;
this.projectConfig = projectConfig;
this.collectBuilderTargets();
}
protected collectBuilderTargets(): void {
for (const [name, target] of Object.entries(
this.projectConfig.targets ?? {}
)) {
if (target.executor === this.builderName) {
this.targets.set(name, target);
}
}
}
}

View File

@ -0,0 +1,3 @@
export * from './angular-devkit-ng-packagr.migrator';
export * from './builder-migrator-class.type';
export * from './builder.migrator';

View File

@ -0,0 +1,2 @@
export * from './builders';
export * from './projects';

View File

@ -0,0 +1,66 @@
import type { ProjectConfiguration, Tree } from '@nrwl/devkit';
import {
readWorkspaceConfiguration,
updateJson,
updateWorkspaceConfiguration,
} from '@nrwl/devkit';
import type { Logger } from '../utilities/logger';
import type {
ProjectMigrationInfo,
ValidationResult,
} from '../utilities/types';
export abstract class Migrator {
protected project: ProjectMigrationInfo;
protected readonly originalProjectConfig: ProjectConfiguration;
constructor(
protected readonly tree: Tree,
protected projectConfig: ProjectConfiguration,
protected readonly logger: Logger
) {
this.originalProjectConfig = Object.freeze(
JSON.parse(JSON.stringify(this.projectConfig))
);
}
abstract migrate(): Promise<void> | void;
abstract validate(): ValidationResult;
// TODO(leo): This should be moved to BuilderMigrator once everything is split into builder migrators.
protected updateCacheableOperations(targetNames: string[]): void {
if (!targetNames.length) {
return;
}
const workspaceConfig = readWorkspaceConfiguration(this.tree);
Object.keys(workspaceConfig.tasksRunnerOptions ?? {}).forEach(
(taskRunnerName) => {
const taskRunner = workspaceConfig.tasksRunnerOptions[taskRunnerName];
taskRunner.options.cacheableOperations = Array.from(
new Set([
...(taskRunner.options.cacheableOperations ?? []),
...targetNames,
])
);
}
);
updateWorkspaceConfiguration(this.tree, workspaceConfig);
}
// TODO(leo): This should be moved to BuilderMigrator once everything is split into builder migrators.
protected updateTsConfigFile(
tsConfigPath: string,
rootTsConfigFile: string,
projectOffsetFromRoot: string
): void {
updateJson(this.tree, tsConfigPath, (json) => {
json.extends = `${projectOffsetFromRoot}${rootTsConfigFile}`;
json.compilerOptions = json.compilerOptions ?? {};
json.compilerOptions.outDir = `${projectOffsetFromRoot}dist/out-tsc`;
return json;
});
}
}

View File

@ -1,15 +1,17 @@
import {
import type {
ProjectConfiguration,
TargetConfiguration,
Tree,
} from '@nrwl/devkit';
import {
readJson,
readProjectConfiguration,
readWorkspaceConfiguration,
TargetConfiguration,
Tree,
writeJson,
} from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import type { MigrationProjectConfiguration } from '../../utilities/types';
import { AppMigrator } from './app.migrator';
import { MigrationProjectConfiguration } from './types';
type AngularCliProjectConfiguration = Omit<ProjectConfiguration, 'targets'> & {
architect?: {

View File

@ -1,9 +1,8 @@
import type { TargetConfiguration, Tree } from '@nrwl/devkit';
import {
joinPathFragments,
offsetFromRoot,
readJson,
TargetConfiguration,
Tree,
updateJson,
updateProjectConfiguration,
} from '@nrwl/devkit';
@ -11,15 +10,15 @@ import { hasRulesRequiringTypeChecking } from '@nrwl/linter';
import { convertToNxProjectGenerator } from '@nrwl/workspace/generators';
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript';
import { basename } from 'path';
import { GeneratorOptions } from '../schema';
import { E2eMigrator } from './e2e.migrator';
import { Logger } from './logger';
import { ProjectMigrator } from './project.migrator';
import {
import type { GeneratorOptions } from '../../schema';
import type {
Logger,
MigrationProjectConfiguration,
Target,
ValidationResult,
} from './types';
} from '../../utilities';
import { E2eMigrator } from './e2e.migrator';
import { ProjectMigrator } from './project.migrator';
type SupportedTargets =
| 'build'
@ -78,7 +77,7 @@ export class AppMigrator extends ProjectMigrator<SupportedTargets> {
}
}
async migrate(): Promise<void> {
override async migrate(): Promise<void> {
await this.e2eMigrator.migrate();
this.moveProjectFiles();

View File

@ -9,21 +9,23 @@ jest.mock('fs', () => {
})),
};
});
import { installedCypressVersion } from '@nrwl/cypress/src/utils/cypress-version';
import type {
ProjectConfiguration,
TargetConfiguration,
Tree,
} from '@nrwl/devkit';
import {
joinPathFragments,
offsetFromRoot,
ProjectConfiguration,
readJson,
readProjectConfiguration,
TargetConfiguration,
Tree,
writeJson,
} from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { lstatSync } from 'fs';
import type { MigrationProjectConfiguration } from '../../utilities';
import { E2eMigrator } from './e2e.migrator';
import { MigrationProjectConfiguration } from './types';
type AngularCliProjectConfiguration = Omit<ProjectConfiguration, 'targets'> & {
architect?: {

View File

@ -1,18 +1,20 @@
import { cypressProjectGenerator } from '@nrwl/cypress';
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
import { installedCypressVersion } from '@nrwl/cypress/src/utils/cypress-version';
import type {
ProjectConfiguration,
TargetConfiguration,
Tree,
} from '@nrwl/devkit';
import {
addProjectConfiguration,
joinPathFragments,
names,
offsetFromRoot,
ProjectConfiguration,
readJson,
readProjectConfiguration,
removeProjectConfiguration,
stripIndents,
TargetConfiguration,
Tree,
updateJson,
updateProjectConfiguration,
visitNotIgnoredFiles,
@ -23,25 +25,27 @@ import { insertImport } from '@nrwl/workspace/src/utilities/ast-utils';
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript';
import { tsquery } from '@phenomnomnominal/tsquery';
import { basename, relative } from 'path';
import type {
Node,
ObjectLiteralExpression,
PropertyAssignment,
} from 'typescript';
import {
isObjectLiteralExpression,
isPropertyAssignment,
isStringLiteralLike,
isTemplateExpression,
Node,
ObjectLiteralExpression,
PropertyAssignment,
SyntaxKind,
} from 'typescript';
import { GeneratorOptions } from '../schema';
import { FileChangeRecorder } from './file-change-recorder';
import { Logger } from './logger';
import { ProjectMigrator } from './project.migrator';
import {
import type { GeneratorOptions } from '../../schema';
import type {
Logger,
MigrationProjectConfiguration,
Target,
ValidationResult,
} from './types';
} from '../../utilities';
import { FileChangeRecorder } from '../../utilities';
import { ProjectMigrator } from './project.migrator';
type SupportedTargets = 'e2e';
const supportedTargets: Record<SupportedTargets, Target> = {
@ -101,7 +105,7 @@ export class E2eMigrator extends ProjectMigrator<SupportedTargets> {
this.initialize();
}
async migrate(): Promise<void> {
override async migrate(): Promise<void> {
if (!this.targetNames.e2e) {
this.logger.info(
'No e2e project was migrated because there was no "e2e" target declared in the "angular.json".'

View File

@ -0,0 +1,4 @@
export * from './app.migrator';
export * from './e2e.migrator';
export * from './lib.migrator';
export * from './project.migrator';

View File

@ -1,15 +1,17 @@
import {
import type {
ProjectConfiguration,
TargetConfiguration,
Tree,
} from '@nrwl/devkit';
import {
readJson,
readProjectConfiguration,
readWorkspaceConfiguration,
TargetConfiguration,
Tree,
writeJson,
} from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import type { MigrationProjectConfiguration } from '../../utilities';
import { LibMigrator } from './lib.migrator';
import { MigrationProjectConfiguration } from './types';
type AngularCliProjectConfiguration = Omit<ProjectConfiguration, 'targets'> & {
architect?: {
@ -126,8 +128,8 @@ describe('lib migrator', () => {
expect(result[0].messageGroup.messages).toStrictEqual([
'The "build" target is using an unsupported builder "@not/supported:builder".',
]);
expect(result[0].hint).toBe(
'The supported builders for libraries are: "@angular-devkit/build-angular:ng-packagr", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint".'
expect(result[0].hint).toMatchInlineSnapshot(
`"The supported builders for libraries are: \\"@angular-devkit/build-angular:karma\\", \\"@angular-eslint/builder:lint\\" and \\"@angular-devkit/build-angular:ng-packagr\\"."`
);
});
@ -149,8 +151,8 @@ describe('lib migrator', () => {
'The "build" target is using an unsupported builder "@not/supported:builder".',
'The "test" target is using an unsupported builder "@other/not-supported:builder".',
]);
expect(result[0].hint).toBe(
'The supported builders for libraries are: "@angular-devkit/build-angular:ng-packagr", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint".'
expect(result[0].hint).toMatchInlineSnapshot(
`"The supported builders for libraries are: \\"@angular-devkit/build-angular:karma\\", \\"@angular-eslint/builder:lint\\" and \\"@angular-devkit/build-angular:ng-packagr\\"."`
);
});
@ -168,8 +170,8 @@ describe('lib migrator', () => {
expect(result[0].messageGroup.messages).toStrictEqual([
'The "my-build" target is using an unsupported builder "@not/supported:builder".',
]);
expect(result[0].hint).toBe(
'The supported builders for libraries are: "@angular-devkit/build-angular:ng-packagr", "@angular-devkit/build-angular:karma" and "@angular-eslint/builder:lint".'
expect(result[0].hint).toMatchInlineSnapshot(
`"The supported builders for libraries are: \\"@angular-devkit/build-angular:karma\\", \\"@angular-eslint/builder:lint\\" and \\"@angular-devkit/build-angular:ng-packagr\\"."`
);
});
@ -210,17 +212,17 @@ describe('lib migrator', () => {
expect(result).toHaveLength(2);
expect(result[0].message).toBe(
'There is more than one target using a builder that is used to build the project ("build1" and "build2").'
);
expect(result[0].hint).toBe(
'Make sure the project only has one target with a builder that is used to build the project.'
);
expect(result[1].message).toBe(
'There is more than one target using a builder that is used to lint the project ("lint1" and "lint2").'
);
expect(result[1].hint).toBe(
expect(result[0].hint).toBe(
'Make sure the project only has one target with a builder that is used to lint the project.'
);
expect(result[1].message).toBe(
'There is more than one target using a builder that is used to build the project ("build1" and "build2").'
);
expect(result[1].hint).toBe(
'Make sure the project only has one target with a builder that is used to build the project.'
);
});
it('should succeed validation', async () => {
@ -274,7 +276,7 @@ describe('lib migrator', () => {
await expect(migrator.migrate()).resolves.not.toThrow();
expect(mockedLogger.warn).toHaveBeenCalledWith(
'There is no build target in the project configuration. This might not be an issue. Skipping updating the build configuration.'
'There is no target in the project configuration using the @angular-devkit/build-angular:ng-packagr builder. This might not be an issue. Skipping updating the build configuration.'
);
});

View File

@ -1,32 +1,36 @@
import type { Tree } from '@nrwl/devkit';
import {
joinPathFragments,
offsetFromRoot,
readJson,
Tree,
updateJson,
updateProjectConfiguration,
writeJson,
} from '@nrwl/devkit';
import { hasRulesRequiringTypeChecking } from '@nrwl/linter';
import { convertToNxProjectGenerator } from '@nrwl/workspace/generators';
import { getRootTsConfigPathInTree } from '@nrwl/workspace/src/utilities/typescript';
import { basename } from 'path';
import { addBuildableLibrariesPostCssDependencies } from '../../utils/dependencies';
import { GeneratorOptions } from '../schema';
import { Logger } from './logger';
import { ProjectMigrator } from './project.migrator';
import {
import type { GeneratorOptions } from '../../schema';
import type {
Logger,
MigrationProjectConfiguration,
Target,
ValidationError,
ValidationResult,
} from './types';
} from '../../utilities';
import type { BuilderMigratorClassType } from '../builders';
import { AngularDevkitNgPackagrMigrator } from '../builders';
import { ProjectMigrator } from './project.migrator';
type SupportedTargets = 'build' | 'test' | 'lint';
type SupportedTargets = 'test' | 'lint';
const supportedTargets: Record<SupportedTargets, Target> = {
build: { builders: ['@angular-devkit/build-angular:ng-packagr'] },
test: { builders: ['@angular-devkit/build-angular:karma'] },
lint: { builders: ['@angular-eslint/builder:lint'] },
};
// TODO(leo): this will replace `supportedTargets` once the full refactor is done.
const supportedBuilderMigrators: BuilderMigratorClassType[] = [
AngularDevkitNgPackagrMigrator,
];
export class LibMigrator extends ProjectMigrator<SupportedTargets> {
private oldEsLintConfigPath: string;
@ -38,7 +42,15 @@ export class LibMigrator extends ProjectMigrator<SupportedTargets> {
project: MigrationProjectConfiguration,
logger?: Logger
) {
super(tree, options, supportedTargets, project, 'libs', logger);
super(
tree,
options,
supportedTargets,
project,
'libs',
logger,
supportedBuilderMigrators
);
if (this.targetNames.lint) {
this.oldEsLintConfigPath =
@ -49,24 +61,29 @@ export class LibMigrator extends ProjectMigrator<SupportedTargets> {
}
}
async migrate(): Promise<void> {
override async migrate(): Promise<void> {
await this.updateProjectConfiguration();
this.moveProjectFiles();
this.updateNgPackageJson();
for (const builderMigrator of this.builderMigrators ?? []) {
await builderMigrator.migrate();
}
this.updateTsConfigs();
this.updateEsLintConfig();
this.updateCacheableOperations(
[
this.targetNames.build,
this.targetNames.lint,
this.targetNames.test,
].filter(Boolean)
[this.targetNames.lint, this.targetNames.test].filter(Boolean)
);
addBuildableLibrariesPostCssDependencies(this.tree);
}
override validate(): ValidationResult {
return super.validate();
const errors: ValidationError[] = [...(super.validate() ?? [])];
for (const builderMigrator of this.builderMigrators) {
errors.push(...(builderMigrator.validate() ?? []));
}
return errors.length ? errors : null;
}
private moveProjectFiles(): void {
@ -85,7 +102,6 @@ export class LibMigrator extends ProjectMigrator<SupportedTargets> {
'The project does not have any targets configured. This might not be an issue. Skipping updating targets.'
);
} else {
this.updateBuildTargetConfiguration();
this.updateLintTargetConfiguration();
this.updateTestTargetConfiguration();
}
@ -100,33 +116,10 @@ export class LibMigrator extends ProjectMigrator<SupportedTargets> {
});
}
private updateNgPackageJson(): void {
const buildTarget = this.projectConfig.targets?.[this.targetNames.build];
if (
!buildTarget?.options?.project ||
!this.tree.exists(buildTarget.options.project)
) {
// we already logged a warning for these cases, so just return
return;
}
const ngPackageJson = readJson(this.tree, buildTarget.options.project);
const offset = offsetFromRoot(this.project.newRoot);
ngPackageJson.$schema =
ngPackageJson.$schema &&
`${offset}node_modules/ng-packagr/ng-package.schema.json`;
ngPackageJson.dest = `${offset}dist/${this.project.name}`;
writeJson(this.tree, buildTarget.options.project, ngPackageJson);
}
private updateTsConfigs(): void {
const rootTsConfigFile = getRootTsConfigPathInTree(this.tree);
const projectOffsetFromRoot = offsetFromRoot(this.projectConfig.root);
this.updateTsConfigFileUsedByBuildTarget(
rootTsConfigFile,
projectOffsetFromRoot
);
this.updateTsConfigFileUsedByTestTarget(
rootTsConfigFile,
projectOffsetFromRoot
@ -176,72 +169,6 @@ export class LibMigrator extends ProjectMigrator<SupportedTargets> {
});
}
private updateBuildTargetConfiguration(): void {
if (!this.targetNames.build) {
this.logger.warn(
'There is no build target in the project configuration. This might not be an issue. Skipping updating the build configuration.'
);
return;
}
const buildTarget = this.projectConfig.targets[this.targetNames.build];
buildTarget.executor = '@nrwl/angular:package';
if (
!buildTarget.options &&
(!buildTarget.configurations ||
!Object.keys(buildTarget.configurations).length)
) {
this.logger.warn(
`The target "${this.targetNames.build}" is not specifying any options or configurations. Skipping updating the target configuration.`
);
return;
}
const buildDevTsConfig =
buildTarget.options?.tsConfig ??
buildTarget.configurations?.development?.tsConfig;
if (!buildDevTsConfig) {
this.logger.warn(
`The "${this.targetNames.build}" target does not have the "tsConfig" option configured. Skipping updating the tsConfig file.`
);
} else if (!this.tree.exists(buildDevTsConfig)) {
this.logger.warn(
`The tsConfig file "${buildDevTsConfig}" specified in the "${this.targetNames.build}" target could not be found. Skipping updating the tsConfig file.`
);
}
if (!buildTarget.options?.project) {
this.logger.warn(
`The "${this.targetNames.build}" target does not have the "project" option configured. Skipping updating the ng-packagr project file ("ng-package.json").`
);
} else if (!this.tree.exists(buildTarget.options.project)) {
this.logger.warn(
`The ng-packagr project file "${buildTarget.options.project}" specified in the "${this.targetNames.build}" target could not be found. Skipping updating the ng-packagr project file.`
);
}
['project', 'tsConfig'].forEach((option) => {
if (buildTarget.options?.[option]) {
buildTarget.options[option] = joinPathFragments(
this.project.newRoot,
basename(buildTarget.options[option])
);
}
for (const configuration of Object.values(
buildTarget.configurations ?? {}
)) {
configuration[option] =
configuration[option] &&
joinPathFragments(
this.project.newRoot,
basename(configuration[option])
);
}
});
}
private updateLintTargetConfiguration(): void {
if (!this.targetNames.lint) {
return;
@ -353,39 +280,6 @@ export class LibMigrator extends ProjectMigrator<SupportedTargets> {
testOptions.scripts.map((script) => this.convertAsset(script));
}
private updateTsConfigFileUsedByBuildTarget(
rootTsConfigFile: string,
projectOffsetFromRoot: string
): void {
if (!this.targetNames.build) {
return;
}
const tsConfigPath =
this.projectConfig.targets[this.targetNames.build].options?.tsConfig ??
this.projectConfig.targets[this.targetNames.build].configurations
?.development?.tsConfig;
if (!tsConfigPath || !this.tree.exists(tsConfigPath)) {
// we already logged a warning for these cases, so just return
return;
}
this.updateTsConfigFile(
tsConfigPath,
rootTsConfigFile,
projectOffsetFromRoot
);
updateJson(this.tree, tsConfigPath, (json) => {
if (!json.include?.length && !json.files?.length) {
json.include = ['**/*.ts'];
}
return json;
});
}
private updateTsConfigFileUsedByTestTarget(
rootTsConfigFile: string,
projectOffsetFromRoot: string

View File

@ -1,50 +1,46 @@
import type { TargetConfiguration, Tree } from '@nrwl/devkit';
import {
joinPathFragments,
normalizePath,
offsetFromRoot,
ProjectConfiguration,
readWorkspaceConfiguration,
TargetConfiguration,
Tree,
updateJson,
updateWorkspaceConfiguration,
visitNotIgnoredFiles,
} from '@nrwl/devkit';
import { basename, dirname } from 'path';
import { GeneratorOptions } from '../schema';
import { Logger } from './logger';
import {
import type { GeneratorOptions } from '../../schema';
import type {
MigrationProjectConfiguration,
Target,
ValidationError,
ValidationResult,
} from './types';
import { arrayToString } from './validation-logging';
} from '../../utilities';
import { arrayToString, Logger } from '../../utilities';
import type { BuilderMigratorClassType } from '../builders';
import { BuilderMigrator } from '../builders';
import { Migrator } from '../migrator';
export abstract class ProjectMigrator<TargetType extends string = any> {
export abstract class ProjectMigrator<
TargetType extends string = string
> extends Migrator {
public get projectName(): string {
return this.project.name;
}
protected projectConfig: ProjectConfiguration;
protected project: {
name: string;
oldRoot: string;
oldSourceRoot: string;
newRoot: string;
newSourceRoot: string;
};
protected logger: Logger;
protected builderMigrators: BuilderMigrator[];
protected readonly targetNames: Partial<Record<TargetType, string>> = {};
constructor(
protected readonly tree: Tree,
tree: Tree,
protected readonly options: GeneratorOptions,
protected readonly targets: Record<TargetType, Target>,
project: MigrationProjectConfiguration,
rootDir: string,
logger?: Logger
logger?: Logger,
// TODO(leo): this will replace `targets` and become required once the full
// refactor is done.
supportedBuilderMigrators?: BuilderMigratorClassType[]
) {
this.projectConfig = project.config;
super(tree, project.config, logger ?? new Logger(project.name));
this.project = {
name: project.name,
oldRoot: this.projectConfig.root ?? '',
@ -55,22 +51,19 @@ export abstract class ProjectMigrator<TargetType extends string = any> {
newSourceRoot: `${rootDir}/${project.name}/src`,
};
this.logger = logger ?? new Logger(this.project.name);
this.collectTargetNames();
this.createBuilderMigrators(supportedBuilderMigrators);
}
abstract migrate(): Promise<void>;
validate(): ValidationResult {
const result: ValidationResult = [];
override validate(): ValidationResult {
const errors: ValidationError[] = [];
// check project root
if (
this.projectConfig.root === undefined ||
this.projectConfig.root === null
) {
result.push({
errors.push({
message:
'The project root is not defined in the project configuration.',
hint:
@ -81,7 +74,7 @@ export abstract class ProjectMigrator<TargetType extends string = any> {
this.projectConfig.root !== '' &&
!this.tree.exists(this.projectConfig.root)
) {
result.push({
errors.push({
message: `The project root "${this.project.oldRoot}" could not be found.`,
hint:
`Make sure the value for "projects.${this.project.name}.root" is correct ` +
@ -94,7 +87,7 @@ export abstract class ProjectMigrator<TargetType extends string = any> {
this.projectConfig.sourceRoot &&
!this.tree.exists(this.projectConfig.sourceRoot)
) {
result.push({
errors.push({
message: `The project source root "${this.project.oldSourceRoot}" could not be found.`,
hint:
`Make sure the value for "projects.${this.project.name}.sourceRoot" is correct ` +
@ -108,6 +101,9 @@ export abstract class ProjectMigrator<TargetType extends string = any> {
.map((x) => x.builders)
.flat(),
];
allSupportedBuilders.push(
...this.builderMigrators.map((migrator) => migrator.builderName)
);
const unsupportedBuilders: [target: string, builder: string][] = [];
Object.entries(this.projectConfig.targets ?? {}).forEach(
@ -119,7 +115,7 @@ export abstract class ProjectMigrator<TargetType extends string = any> {
);
if (unsupportedBuilders.length) {
result.push({
errors.push({
messageGroup: {
title: 'Unsupported builders',
messages: unsupportedBuilders.map(
@ -163,7 +159,7 @@ export abstract class ProjectMigrator<TargetType extends string = any> {
return;
}
result.push({
errors.push({
message: `There is more than one target using a builder that is used to ${targetType} the project (${arrayToString(
targetsByType[targetType]
)}).`,
@ -171,7 +167,7 @@ export abstract class ProjectMigrator<TargetType extends string = any> {
});
});
return result.length ? result : null;
return errors.length ? errors : null;
}
protected convertAsset(asset: string | any): string | any {
@ -310,41 +306,6 @@ export abstract class ProjectMigrator<TargetType extends string = any> {
}
}
protected updateCacheableOperations(targetNames: string[]): void {
if (!targetNames.length) {
return;
}
const workspaceConfig = readWorkspaceConfiguration(this.tree);
Object.keys(workspaceConfig.tasksRunnerOptions ?? {}).forEach(
(taskRunnerName) => {
const taskRunner = workspaceConfig.tasksRunnerOptions[taskRunnerName];
taskRunner.options.cacheableOperations = Array.from(
new Set([
...(taskRunner.options.cacheableOperations ?? []),
...targetNames,
])
);
}
);
updateWorkspaceConfiguration(this.tree, workspaceConfig);
}
protected updateTsConfigFile(
tsConfigPath: string,
rootTsConfigFile: string,
projectOffsetFromRoot: string
): void {
updateJson(this.tree, tsConfigPath, (json) => {
json.extends = `${projectOffsetFromRoot}${rootTsConfigFile}`;
json.compilerOptions = json.compilerOptions ?? {};
json.compilerOptions.outDir = `${projectOffsetFromRoot}dist/out-tsc`;
return json;
});
}
private collectTargetNames(): void {
const targetTypes = Object.keys(this.targets) as TargetType[];
@ -361,4 +322,23 @@ export abstract class ProjectMigrator<TargetType extends string = any> {
}
);
}
private createBuilderMigrators(
supportedBuilderMigrators?: BuilderMigratorClassType[]
): void {
if (!supportedBuilderMigrators) {
this.builderMigrators = [];
return;
}
this.builderMigrators = supportedBuilderMigrators.map(
(migratorClass) =>
new migratorClass(
this.tree,
this.project,
this.projectConfig,
this.logger
)
);
}
}

View File

@ -1,7 +1,7 @@
import * as angularCliMigrator from './migrate-from-angular-cli';
import * as initGenerator from '../init/init';
import { ngAddGenerator } from './ng-add';
import { Tree } from '@nrwl/devkit';
import type { Tree } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
describe('ngAdd generator', () => {

View File

@ -1,5 +1,5 @@
import { getProjects, Tree } from '@nrwl/devkit';
import { MigrationProjectConfiguration, WorkspaceProjects } from './types';
import type { MigrationProjectConfiguration, WorkspaceProjects } from './types';
export function getAllProjects(tree: Tree): WorkspaceProjects {
const projects = getProjects(tree);

View File

@ -0,0 +1,8 @@
export * from './file-change-recorder';
export * from './get-all-projects';
export * from './logger';
export * from './normalize-options';
export * from './types';
export * from './validate-projects';
export * from './validation-logging';
export * from './workspace';

View File

@ -1,12 +1,12 @@
import type { Tree } from '@nrwl/devkit';
import {
detectWorkspaceScope,
joinPathFragments,
names,
readJson,
Tree,
} from '@nrwl/devkit';
import { GeneratorOptions } from '../schema';
import { WorkspaceProjects } from './types';
import type { GeneratorOptions } from '../schema';
import type { WorkspaceProjects } from './types';
export function normalizeOptions(
tree: Tree,

View File

@ -1,4 +1,4 @@
import { ProjectConfiguration } from '@nrwl/devkit';
import type { ProjectConfiguration } from '@nrwl/devkit';
export type MigrationProjectConfiguration = {
config: ProjectConfiguration;
@ -10,6 +10,14 @@ export type WorkspaceProjects = {
libs: MigrationProjectConfiguration[];
};
export type ProjectMigrationInfo = {
name: string;
oldRoot: string;
oldSourceRoot: string;
newRoot: string;
newSourceRoot: string;
};
export type WorkspaceCapabilities = {
karma: boolean;
eslint: boolean;

View File

@ -1,5 +1,5 @@
import * as chalk from 'chalk';
import { ProjectMigrator } from './project.migrator';
import type { ProjectMigrator } from '../migrators';
import { validateProjects } from './validate-projects';
describe('validateProjects', () => {

View File

@ -1,5 +1,6 @@
// TODO(leo): this should probably move into a workspace-wide migrator
import * as chalk from 'chalk';
import { ProjectMigrator } from './project.migrator';
import type { ProjectMigrator } from '../migrators';
import { ValidationError } from './types';
import { workspaceMigrationErrorHeading } from './validation-logging';

View File

@ -1,10 +1,9 @@
import type { NxJsonConfiguration, Tree } from '@nrwl/devkit';
import {
generateFiles,
joinPathFragments,
NxJsonConfiguration,
readJson,
readWorkspaceConfiguration,
Tree,
updateJson,
updateWorkspaceConfiguration,
writeJson,
@ -19,8 +18,8 @@ import { readFileSync } from 'fs';
import { readModulePackageJson } from 'nx/src/utils/package-json';
import { dirname, join } from 'path';
import { angularDevkitVersion, nxVersion } from '../../../utils/versions';
import { GeneratorOptions } from '../schema';
import { WorkspaceCapabilities, WorkspaceProjects } from './types';
import type { GeneratorOptions } from '../schema';
import type { WorkspaceCapabilities, WorkspaceProjects } from './types';
import { workspaceMigrationErrorHeading } from './validation-logging';
export function validateWorkspace(tree: Tree): void {