463 lines
13 KiB
TypeScript
463 lines
13 KiB
TypeScript
import {
|
|
Rule,
|
|
chain,
|
|
SchematicContext,
|
|
Tree,
|
|
TaskConfigurationGenerator,
|
|
TaskConfiguration,
|
|
TaskExecutorFactory,
|
|
externalSchematic
|
|
} from '@angular-devkit/schematics';
|
|
import { stripIndents } from '@angular-devkit/core/src/utils/literals';
|
|
import {
|
|
readJsonInTree,
|
|
addDepsToPackageJson,
|
|
updateJsonInTree,
|
|
insert,
|
|
formatFiles
|
|
} from '@nrwl/workspace';
|
|
import {
|
|
createSourceFile,
|
|
ScriptTarget,
|
|
isImportDeclaration,
|
|
isStringLiteral
|
|
} from 'typescript';
|
|
import {
|
|
getSourceNodes,
|
|
ReplaceChange
|
|
} from '@nrwl/workspace/src/utils/ast-utils';
|
|
import { relative } from 'path';
|
|
import { RunSchematicTaskOptions } from '@angular-devkit/schematics/tasks/run-schematic/options';
|
|
import * as path from 'path';
|
|
import { platform } from 'os';
|
|
import { Observable } from 'rxjs';
|
|
import { spawn } from 'child_process';
|
|
|
|
const ignore = require('ignore');
|
|
|
|
export class RunUpdateTask implements TaskConfigurationGenerator<any> {
|
|
protected _package: string;
|
|
|
|
constructor(pkg: string) {
|
|
this._package = pkg;
|
|
}
|
|
|
|
toConfiguration(): TaskConfiguration<any> {
|
|
return {
|
|
name: 'RunUpdate',
|
|
options: {
|
|
package: this._package
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
function createRunUpdateTask(): TaskExecutorFactory<any> {
|
|
return {
|
|
name: 'RunUpdate',
|
|
create: () => {
|
|
return Promise.resolve((options: any, context: SchematicContext) => {
|
|
context.logger.info(`Updating ${options.package}`);
|
|
const spawnOptions = {
|
|
stdio: [process.stdin, process.stdout, process.stderr],
|
|
shell: true
|
|
};
|
|
const ng =
|
|
platform() === 'win32'
|
|
? '.\\node_modules\\.bin\\ng'
|
|
: './node_modules/.bin/ng';
|
|
const args = [
|
|
'update',
|
|
options.package,
|
|
'--force',
|
|
'--allow-dirty'
|
|
].filter(e => !!e);
|
|
return new Observable(obs => {
|
|
spawn(ng, args, spawnOptions).on('close', (code: number) => {
|
|
if (code === 0) {
|
|
obs.next();
|
|
obs.complete();
|
|
} else {
|
|
const message = `${options.package} migration failed, see above.`;
|
|
obs.error(new Error(message));
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
function addDependencies() {
|
|
return (host: Tree, context: SchematicContext) => {
|
|
const dependencies = readJsonInTree(host, 'package.json').dependencies;
|
|
const builders = new Set<string>();
|
|
const projects = readJsonInTree(host, 'angular.json').projects;
|
|
Object.values<any>(projects).forEach(project => {
|
|
Object.values<any>(project.architect).forEach(target => {
|
|
const [builderDependency] = target.builder.split(':');
|
|
builders.add(builderDependency);
|
|
});
|
|
});
|
|
const newDependencies = {};
|
|
const newDevDependencies = {
|
|
'@nrwl/workspace': '8.0.0'
|
|
};
|
|
context.logger.info(`Adding @nrwl/workspace as a dependency`);
|
|
if (dependencies['@angular/core']) {
|
|
newDependencies['@nrwl/angular'] = '8.0.0';
|
|
context.logger.info(`Adding @nrwl/angular as a dependency`);
|
|
}
|
|
if (dependencies['react']) {
|
|
newDevDependencies['@nrwl/react'] = '8.0.0';
|
|
context.logger.info(`Adding @nrwl/react as a dependency`);
|
|
}
|
|
if (dependencies['@nestjs/core']) {
|
|
newDevDependencies['@nrwl/nest'] = '8.0.0';
|
|
context.logger.info(`Adding @nrwl/nest as a dependency`);
|
|
}
|
|
if (dependencies.express) {
|
|
newDevDependencies['@nrwl/express'] = '8.0.0';
|
|
newDevDependencies['@nrwl/node'] = '8.0.0';
|
|
context.logger.info(`Adding @nrwl/express as a dependency`);
|
|
}
|
|
if (builders.has('@nrwl/web')) {
|
|
newDevDependencies['@nrwl/web'] = '8.0.0';
|
|
context.logger.info(`Adding @nrwl/web as a dependency`);
|
|
}
|
|
if (builders.has('@nrwl/node')) {
|
|
newDevDependencies['@nrwl/node'] = '8.0.0';
|
|
context.logger.info(`Adding @nrwl/node as a dependency`);
|
|
}
|
|
if (builders.has('@nrwl/jest')) {
|
|
newDevDependencies['@nrwl/jest'] = '8.0.0';
|
|
context.logger.info(`Adding @nrwl/jest as a dependency`);
|
|
}
|
|
if (builders.has('@nrwl/cypress')) {
|
|
newDevDependencies['@nrwl/cypress'] = '8.0.0';
|
|
context.logger.info(`Adding @nrwl/cypress as a dependency`);
|
|
}
|
|
return chain([addDepsToPackageJson(newDependencies, newDevDependencies)]);
|
|
};
|
|
}
|
|
|
|
const removeOldDependencies = updateJsonInTree(
|
|
'package.json',
|
|
(json, context: SchematicContext) => {
|
|
json.dependencies = json.dependencies || {};
|
|
json.devDependencies = json.devDependencies || {};
|
|
delete json.dependencies['@nrwl/nx'];
|
|
delete json.devDependencies['@nrwl/nx'];
|
|
delete json.dependencies['@nrwl/schematics'];
|
|
delete json.devDependencies['@nrwl/schematics'];
|
|
delete json.dependencies['@nrwl/builders'];
|
|
delete json.devDependencies['@nrwl/builders'];
|
|
context.logger.info(`Removing @nrwl/schematics as a dependency`);
|
|
context.logger.info(`Removing @nrwl/builders as a dependency`);
|
|
context.logger.info(`Removing @nrwl/nx as a dependency`);
|
|
|
|
return json;
|
|
}
|
|
);
|
|
|
|
const updateUpdateScript = updateJsonInTree('package.json', json => {
|
|
json.scripts = json.scripts || {};
|
|
json.scripts.update = 'ng update @nrwl/workspace';
|
|
return json;
|
|
});
|
|
|
|
const updateBuilders = updateJsonInTree('angular.json', json => {
|
|
Object.entries<any>(json.projects).forEach(([projectKey, project]) => {
|
|
Object.entries<any>(project.architect).forEach(([targetKey, target]) => {
|
|
if (target.builder === '@nrwl/builders:jest') {
|
|
json.projects[projectKey].architect[targetKey].builder =
|
|
'@nrwl/jest:jest';
|
|
}
|
|
if (target.builder === '@nrwl/builders:cypress') {
|
|
json.projects[projectKey].architect[targetKey].builder =
|
|
'@nrwl/cypress:cypress';
|
|
}
|
|
if (target.builder === '@nrwl/builders:web-build') {
|
|
json.projects[projectKey].architect[targetKey].builder =
|
|
'@nrwl/web:build';
|
|
}
|
|
if (target.builder === '@nrwl/builders:web-dev-server') {
|
|
json.projects[projectKey].architect[targetKey].builder =
|
|
'@nrwl/web:dev-server';
|
|
}
|
|
if (target.builder === '@nrwl/builders:node-build') {
|
|
json.projects[projectKey].architect[targetKey].builder =
|
|
'@nrwl/node:build';
|
|
}
|
|
if (target.builder === '@nrwl/builders:node-execute') {
|
|
json.projects[projectKey].architect[targetKey].builder =
|
|
'@nrwl/node:execute';
|
|
}
|
|
if (target.builder === '@nrwl/builders:run-commands') {
|
|
json.projects[projectKey].architect[targetKey].builder =
|
|
'@nrwl/workspace:run-commands';
|
|
}
|
|
});
|
|
});
|
|
return json;
|
|
});
|
|
|
|
const displayInformation = (host: Tree, context: SchematicContext) => {
|
|
context.logger.info(stripIndents`
|
|
Nx has been repackaged. We are installing and migrating your dependencies to the ones necessary.
|
|
|
|
If you have workspace schematics, we tried to migrate your imports from "@nrwl/schematics" to "@nrwl/workspace" but your externalSchematics may be broken.
|
|
|
|
Read this guide to see where to find familiar features: https://nx.dev/guides/nx7-to-nx8
|
|
|
|
This migration may take a few minutes.
|
|
`);
|
|
};
|
|
|
|
const updateNxModuleImports = (host: Tree) => {
|
|
let ig;
|
|
|
|
if (host.exists('.gitignore')) {
|
|
ig = ignore();
|
|
ig.add(host.read('.gitignore').toString());
|
|
}
|
|
|
|
host.visit(path => {
|
|
if (!path.endsWith('.ts')) {
|
|
return;
|
|
}
|
|
|
|
if (ig && ig.ignores(relative('/', path))) {
|
|
return;
|
|
}
|
|
|
|
const sourceFile = createSourceFile(
|
|
path,
|
|
host.read(path).toString(),
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
const changes = [];
|
|
sourceFile.statements.forEach(statement => {
|
|
if (
|
|
isImportDeclaration(statement) &&
|
|
isStringLiteral(statement.moduleSpecifier)
|
|
) {
|
|
const nodeText = statement.moduleSpecifier.getText(sourceFile);
|
|
const modulePath = statement.moduleSpecifier
|
|
.getText(sourceFile)
|
|
.substr(1, nodeText.length - 2);
|
|
if (modulePath === '@nrwl/nx') {
|
|
changes.push(
|
|
new ReplaceChange(
|
|
path,
|
|
statement.moduleSpecifier.getStart(sourceFile),
|
|
nodeText,
|
|
`'@nrwl/angular'`
|
|
)
|
|
);
|
|
}
|
|
|
|
if (modulePath === '@nrwl/nx/testing') {
|
|
changes.push(
|
|
new ReplaceChange(
|
|
path,
|
|
statement.moduleSpecifier.getStart(sourceFile),
|
|
nodeText,
|
|
`'@nrwl/angular/testing'`
|
|
)
|
|
);
|
|
}
|
|
|
|
if (modulePath.startsWith('@nrwl/schematics')) {
|
|
changes.push(
|
|
new ReplaceChange(
|
|
path,
|
|
statement.moduleSpecifier.getStart(sourceFile),
|
|
nodeText,
|
|
nodeText.replace('@nrwl/schematics', '@nrwl/workspace')
|
|
)
|
|
);
|
|
}
|
|
}
|
|
});
|
|
insert(host, path, changes);
|
|
});
|
|
};
|
|
|
|
const updateJestPlugin = (host: Tree) => {
|
|
if (!host.exists('jest.config.js')) {
|
|
return host;
|
|
}
|
|
|
|
const sourceFile = createSourceFile(
|
|
'jest.config.js',
|
|
host.read('jest.config.js').toString(),
|
|
ScriptTarget.Latest,
|
|
true
|
|
);
|
|
const changes = [];
|
|
|
|
getSourceNodes(sourceFile).forEach(node => {
|
|
if (isStringLiteral(node)) {
|
|
const value = node
|
|
.getText(sourceFile)
|
|
.substr(1, node.getText(sourceFile).length - 2);
|
|
if (value === '@nrwl/builders/plugins/jest/resolver') {
|
|
changes.push(
|
|
new ReplaceChange(
|
|
'jest.config.js',
|
|
node.getStart(sourceFile),
|
|
node.getText(sourceFile),
|
|
`'@nrwl/jest/plugins/resolver'`
|
|
)
|
|
);
|
|
}
|
|
}
|
|
});
|
|
insert(host, 'jest.config.js', changes);
|
|
};
|
|
|
|
const updateTslintRules = updateJsonInTree('tslint.json', json => {
|
|
const { rulesDirectory } = json;
|
|
json.rulesDirectory = rulesDirectory.map(directory => {
|
|
return directory === 'node_modules/@nrwl/schematics/src/tslint'
|
|
? 'node_modules/@nrwl/workspace/src/tslint'
|
|
: directory;
|
|
});
|
|
return json;
|
|
});
|
|
|
|
const updateDefaultCollection = (host: Tree, context: SchematicContext) => {
|
|
const { dependencies, devDependencies } = readJsonInTree(
|
|
host,
|
|
'package.json'
|
|
);
|
|
|
|
return updateJsonInTree('angular.json', json => {
|
|
json.cli = json.cli || {};
|
|
if (dependencies['@nrwl/angular']) {
|
|
json.cli.defaultCollection = '@nrwl/angular';
|
|
} else if (devDependencies['@nrwl/react']) {
|
|
json.cli.defaultCollection = '@nrwl/react';
|
|
} else if (devDependencies['@nrwl/nest']) {
|
|
json.cli.defaultCollection = '@nrwl/nest';
|
|
} else if (devDependencies['@nrwl/express']) {
|
|
json.cli.defaultCollection = '@nrwl/express';
|
|
} else if (devDependencies['@nrwl/web']) {
|
|
json.cli.defaultCollection = '@nrwl/web';
|
|
} else if (devDependencies['@nrwl/node']) {
|
|
json.cli.defaultCollection = '@nrwl/node';
|
|
} else {
|
|
json.cli.defaultCollection = '@nrwl/workspace';
|
|
}
|
|
context.logger.info(
|
|
`Default collection is now set to ${json.cli.defaultCollection}`
|
|
);
|
|
return json;
|
|
});
|
|
};
|
|
|
|
const setRootDirAndUpdateOurDir = (host: Tree) => {
|
|
let ig;
|
|
|
|
if (host.exists('.gitignore')) {
|
|
ig = ignore();
|
|
ig.add(host.read('.gitignore').toString());
|
|
}
|
|
|
|
host.visit(path => {
|
|
if (!path.endsWith('.json')) {
|
|
return;
|
|
}
|
|
|
|
if (ig && ig.ignores(relative('/', path))) {
|
|
return;
|
|
}
|
|
|
|
const json = host.read(path).toString();
|
|
const match = json.match(/"outDir"\s*:\s*"([^"]+)"/);
|
|
if (match) {
|
|
const outParts = match[1].split('out-tsc');
|
|
if (outParts.length > 1) {
|
|
const updatedJson = json.replace(
|
|
/"outDir"\s*:\s*"([^"]+)"/,
|
|
`"outDir": "${outParts[0]}out-tsc"`
|
|
);
|
|
host.overwrite(path, updatedJson);
|
|
}
|
|
}
|
|
});
|
|
|
|
updateJsonInTree('tsconfig.json', json => {
|
|
json.compilerOptions = json.compilerOptions || {};
|
|
json.compilerOptions.rootDir = '.';
|
|
return json;
|
|
})(host, null);
|
|
};
|
|
|
|
export const runAngularMigrations: Rule = (
|
|
host: Tree,
|
|
context: SchematicContext
|
|
) => {
|
|
// Should always be there during ng update but not during tests.
|
|
if (!context.engine.workflow) {
|
|
return;
|
|
}
|
|
|
|
const packageJson = readJsonInTree(host, 'package.json');
|
|
|
|
const engineHost = (context.engine.workflow as any)._engineHost;
|
|
engineHost.registerTaskExecutor(createRunUpdateTask());
|
|
const cliUpgrade = context.addTask(new RunUpdateTask('@angular/cli'));
|
|
|
|
if (packageJson.dependencies['@angular/core']) {
|
|
context.addTask(new RunUpdateTask('@angular/core'), [cliUpgrade]);
|
|
}
|
|
};
|
|
|
|
const updateNestDependencies = updateJsonInTree('package.json', json => {
|
|
json.dependencies = json.dependencies || {};
|
|
json.devDependencies = json.devDependencies || {};
|
|
|
|
if (!json.devDependencies['@nrwl/nest']) {
|
|
return json;
|
|
}
|
|
|
|
const nestFrameworkVersion = '^6.2.4';
|
|
|
|
json.dependencies = {
|
|
...json.dependencies,
|
|
'@nestjs/common': nestFrameworkVersion,
|
|
'@nestjs/core': nestFrameworkVersion,
|
|
'@nestjs/platform-express': nestFrameworkVersion,
|
|
'reflect-metadata': '^0.1.12'
|
|
};
|
|
|
|
json.devDependencies = {
|
|
...json.devDependencies,
|
|
'@nestjs/schematics': '^6.3.0',
|
|
'@nestjs/testing': nestFrameworkVersion
|
|
};
|
|
|
|
return json;
|
|
});
|
|
|
|
export default function(): Rule {
|
|
return chain([
|
|
displayInformation,
|
|
runAngularMigrations,
|
|
removeOldDependencies,
|
|
updateUpdateScript,
|
|
updateBuilders,
|
|
updateJestPlugin,
|
|
updateNxModuleImports,
|
|
updateTslintRules,
|
|
addDependencies(),
|
|
updateNestDependencies,
|
|
updateDefaultCollection,
|
|
setRootDirAndUpdateOurDir,
|
|
formatFiles()
|
|
]);
|
|
}
|