cleanup(angular): move the angular cli migration generator from @nrwl/workspace to @nrwl/angular (#9244)
* cleanup(angular): move the angular cli migration generator from @nrwl/workspace to @nrwl/angular * cleanup(angular): support merged packages (cli, tao, nx) * cleanup(angular): update make-angular-cli-faster to support packages consolidation
This commit is contained in:
parent
3824eebc23
commit
88a7ad7654
@ -157,12 +157,28 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
|
||||
npmScope: 'projscope',
|
||||
affected: { defaultBase: 'main' },
|
||||
implicitDependencies: {
|
||||
'angular.json': '*',
|
||||
'package.json': '*',
|
||||
'tslint.json': '*',
|
||||
'package.json': {
|
||||
dependencies: '*',
|
||||
devDependencies: '*',
|
||||
},
|
||||
'.eslintrc.json': '*',
|
||||
'tsconfig.base.json': '*',
|
||||
'nx.json': '*',
|
||||
},
|
||||
tasksRunnerOptions: {
|
||||
default: {
|
||||
runner: '@nrwl/workspace/tasks-runners/default',
|
||||
options: {
|
||||
cacheableOperations: ['build', 'lint', 'test', 'e2e'],
|
||||
},
|
||||
},
|
||||
},
|
||||
targetDependencies: {
|
||||
build: [
|
||||
{
|
||||
target: 'build',
|
||||
projects: 'dependencies',
|
||||
},
|
||||
],
|
||||
},
|
||||
cli: { defaultCollection: '@nrwl/angular', packageManager },
|
||||
defaultProject: project,
|
||||
|
||||
@ -125,8 +125,8 @@ describe('list', () => {
|
||||
|
||||
// check for schematics
|
||||
expect(listOutput).toContain('workspace');
|
||||
expect(listOutput).toContain('ng-add');
|
||||
expect(listOutput).toContain('library');
|
||||
expect(listOutput).toContain('workspace-generator');
|
||||
|
||||
// check for builders
|
||||
expect(listOutput).toContain('run-commands');
|
||||
|
||||
@ -422,8 +422,8 @@ export function runNgAdd(
|
||||
}
|
||||
): string {
|
||||
try {
|
||||
packageInstall('@nrwl/workspace');
|
||||
return execSync(`npx ng add @nrwl/workspace ${command}`, {
|
||||
packageInstall('@nrwl/angular');
|
||||
return execSync(`npx ng add @nrwl/angular ${command}`, {
|
||||
cwd: tmpProjPath(),
|
||||
env: { ...(opts.env || process.env), NX_INVOKED_BY_RUNNER: undefined },
|
||||
encoding: 'utf-8',
|
||||
|
||||
@ -47,7 +47,6 @@
|
||||
"factory": "./src/generators/init/init.compat#initSchematic",
|
||||
"schema": "./src/generators/init/schema.json",
|
||||
"description": "Initializes the @nrwl/angular plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true
|
||||
},
|
||||
"karma": {
|
||||
@ -91,6 +90,12 @@
|
||||
"aliases": ["host"],
|
||||
"description": "Generate a Host Angular Micro Frontend Application."
|
||||
},
|
||||
"ng-add": {
|
||||
"factory": "./src/generators/ng-add/compat",
|
||||
"schema": "./src/generators/ng-add/schema.json",
|
||||
"description": "Migrates an Angular CLI workspace to Nx or adds the Angular plugin to an Nx workspace.",
|
||||
"hidden": true
|
||||
},
|
||||
"ngrx": {
|
||||
"factory": "./src/generators/ngrx/compat",
|
||||
"schema": "./src/generators/ngrx/schema.json",
|
||||
@ -197,7 +202,6 @@
|
||||
"factory": "./src/generators/init/init",
|
||||
"schema": "./src/generators/init/schema.json",
|
||||
"description": "Initializes the @nrwl/angular plugin.",
|
||||
"aliases": ["ng-add"],
|
||||
"hidden": true
|
||||
},
|
||||
"karma": {
|
||||
@ -241,6 +245,12 @@
|
||||
"aliases": ["host"],
|
||||
"description": "Generate a Host Angular Micro Frontend Application."
|
||||
},
|
||||
"ng-add": {
|
||||
"factory": "./src/generators/ng-add/ng-add",
|
||||
"schema": "./src/generators/ng-add/schema.json",
|
||||
"description": "Migrates an Angular CLI workspace to Nx or adds the Angular plugin to an Nx workspace.",
|
||||
"hidden": true
|
||||
},
|
||||
"ngrx": {
|
||||
"factory": "./src/generators/ngrx/ngrx",
|
||||
"schema": "./src/generators/ngrx/schema.json",
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
updateWorkspaceConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { jestInitGenerator } from '@nrwl/jest';
|
||||
import { Linter } from '@nrwl/linter';
|
||||
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
||||
import { setDefaultCollection } from '@nrwl/workspace/src/utilities/set-default-collection';
|
||||
import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners';
|
||||
@ -22,8 +23,9 @@ import { Schema } from './schema';
|
||||
|
||||
export async function angularInitGenerator(
|
||||
host: Tree,
|
||||
options: Schema
|
||||
rawOptions: Schema
|
||||
): Promise<GeneratorCallback> {
|
||||
const options = normalizeOptions(rawOptions);
|
||||
setDefaults(host, options);
|
||||
addPostInstall(host);
|
||||
|
||||
@ -41,6 +43,18 @@ export async function angularInitGenerator(
|
||||
return runTasksInSerial(depsTask, unitTestTask, e2eTask);
|
||||
}
|
||||
|
||||
function normalizeOptions(options: Schema): Required<Schema> {
|
||||
return {
|
||||
e2eTestRunner: options.e2eTestRunner ?? E2eTestRunner.Cypress,
|
||||
linter: options.linter ?? Linter.EsLint,
|
||||
skipFormat: options.skipFormat ?? false,
|
||||
skipInstall: options.skipInstall ?? false,
|
||||
skipPackageJson: options.skipPackageJson ?? false,
|
||||
style: options.style ?? 'css',
|
||||
unitTestRunner: options.unitTestRunner ?? UnitTestRunner.Jest,
|
||||
};
|
||||
}
|
||||
|
||||
function setDefaults(host: Tree, options: Schema) {
|
||||
const workspace = readWorkspaceConfiguration(host);
|
||||
|
||||
|
||||
@ -3,11 +3,11 @@ import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners';
|
||||
import type { Styles } from '../utils/types';
|
||||
|
||||
export interface Schema {
|
||||
unitTestRunner: UnitTestRunner;
|
||||
unitTestRunner?: UnitTestRunner;
|
||||
e2eTestRunner?: E2eTestRunner;
|
||||
skipFormat?: boolean;
|
||||
skipInstall?: boolean;
|
||||
style?: Styles;
|
||||
linter: Exclude<Linter, Linter.TsLint>;
|
||||
linter?: Exclude<Linter, Linter.TsLint>;
|
||||
skipPackageJson?: boolean;
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
"description": "The file extension to be used for style files.",
|
||||
"type": "string",
|
||||
"default": "css",
|
||||
"enum": ["css", "scss", "sass", "less"],
|
||||
"x-prompt": {
|
||||
"message": "Which stylesheet format would you like to use?",
|
||||
"type": "list",
|
||||
@ -50,6 +51,10 @@
|
||||
"value": "scss",
|
||||
"label": "SASS(.scss) [ http://sass-lang.com ]"
|
||||
},
|
||||
{
|
||||
"value": "sass",
|
||||
"label": "SASS(.sass) [ http://sass-lang.com ]"
|
||||
},
|
||||
{
|
||||
"value": "less",
|
||||
"label": "LESS [ http://lesscss.org ]"
|
||||
|
||||
@ -167,13 +167,34 @@ Object {
|
||||
"defaultProject": "myApp",
|
||||
"implicitDependencies": Object {
|
||||
".eslintrc.json": "*",
|
||||
"angular.json": "*",
|
||||
"nx.json": "*",
|
||||
"package.json": "*",
|
||||
"package.json": Object {
|
||||
"dependencies": "*",
|
||||
"devDependencies": "*",
|
||||
},
|
||||
"tsconfig.base.json": "*",
|
||||
"tslint.json": "*",
|
||||
},
|
||||
"npmScope": "my-app",
|
||||
"targetDependencies": Object {
|
||||
"build": Array [
|
||||
Object {
|
||||
"projects": "dependencies",
|
||||
"target": "build",
|
||||
},
|
||||
],
|
||||
},
|
||||
"tasksRunnerOptions": Object {
|
||||
"default": Object {
|
||||
"options": Object {
|
||||
"cacheableOperations": Array [
|
||||
"build",
|
||||
"lint",
|
||||
"test",
|
||||
"e2e",
|
||||
],
|
||||
},
|
||||
"runner": "@nrwl/workspace/tasks-runners/default",
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
4
packages/angular/src/generators/ng-add/compat.ts
Normal file
4
packages/angular/src/generators/ng-add/compat.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { convertNxGenerator } from '@nrwl/devkit';
|
||||
import { ngAddGenerator } from './ng-add';
|
||||
|
||||
export default convertNxGenerator(ngAddGenerator);
|
||||
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching
|
||||
* and faster execution of tasks.
|
||||
*
|
||||
* It does this by:
|
||||
*
|
||||
* - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command.
|
||||
* - Symlinking the ng to nx command, so all commands run through the Nx CLI
|
||||
* - Updating the package.json postinstall script to give you control over this script
|
||||
*
|
||||
* The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it.
|
||||
* Every command you run should work the same when using the Nx CLI, except faster.
|
||||
*
|
||||
* Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case,
|
||||
* will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked.
|
||||
* The Nx CLI simply does some optimizations before invoking the Angular CLI.
|
||||
*
|
||||
* To opt out of this patch:
|
||||
* - Replace occurrences of nx with ng in your package.json
|
||||
* - Remove the script from your postinstall script in your package.json
|
||||
* - Delete and reinstall your node_modules
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const cp = require('child_process');
|
||||
const isWindows = os.platform() === 'win32';
|
||||
let output;
|
||||
try {
|
||||
output = require('@nrwl/workspace').output;
|
||||
} catch (e) {
|
||||
console.warn('Angular CLI could not be decorated to enable computation caching. Please ensure @nrwl/workspace is installed.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Symlink of ng to nx, so you can keep using `ng build/test/lint` and still
|
||||
* invoke the Nx CLI and get the benefits of computation caching.
|
||||
*/
|
||||
function symlinkNgCLItoNxCLI() {
|
||||
try {
|
||||
const ngPath = './node_modules/.bin/ng';
|
||||
const nxPath = './node_modules/.bin/nx';
|
||||
if (isWindows) {
|
||||
/**
|
||||
* This is the most reliable way to create symlink-like behavior on Windows.
|
||||
* Such that it works in all shells and works with npx.
|
||||
*/
|
||||
['', '.cmd', '.ps1'].forEach(ext => {
|
||||
if (fs.existsSync(nxPath + ext)) fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext));
|
||||
});
|
||||
} else {
|
||||
// If unix-based, symlink
|
||||
cp.execSync(`ln -sf ./nx ${ngPath}`);
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
output.error({ title: 'Unable to create a symlink from the Angular CLI to the Nx CLI:' + e.message });
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
symlinkNgCLItoNxCLI();
|
||||
require('nx/src/cli/decorate-cli').decorateCli();
|
||||
output.log({ title: 'Angular CLI has been decorated to enable computation caching.' });
|
||||
} catch(e) {
|
||||
output.error({ title: 'Decoration of the Angular CLI did not complete successfully' });
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
{
|
||||
"npmScope": "<%= npmScope %>",
|
||||
"affected": {
|
||||
"defaultBase": "<%= defaultBase %>"
|
||||
},
|
||||
"implicitDependencies": {
|
||||
"package.json": {
|
||||
"dependencies": "*",
|
||||
"devDependencies": "*"
|
||||
},
|
||||
".eslintrc.json": "*"
|
||||
},
|
||||
"tasksRunnerOptions": {
|
||||
"default": {
|
||||
"runner": "@nrwl/workspace/tasks-runners/default",
|
||||
"options": {
|
||||
"cacheableOperations": ["build", "lint", "test", "e2e"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"targetDependencies": {
|
||||
"build": [
|
||||
{
|
||||
"target": "build",
|
||||
"projects": "dependencies"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ import {
|
||||
} from '@nrwl/devkit';
|
||||
import { createTree } from '@nrwl/devkit/testing';
|
||||
|
||||
import { initGenerator } from './init';
|
||||
import { migrateFromAngularCli } from './migrate-from-angular-cli';
|
||||
|
||||
describe('workspace', () => {
|
||||
let tree: Tree;
|
||||
@ -77,7 +77,7 @@ describe('workspace', () => {
|
||||
it('should error if no package.json is present', async () => {
|
||||
tree.delete('package.json');
|
||||
try {
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
fail('should throw');
|
||||
} catch (e) {
|
||||
expect(e.message).toContain('Cannot find package.json');
|
||||
@ -88,7 +88,7 @@ describe('workspace', () => {
|
||||
tree.delete('/e2e/protractor.conf.js');
|
||||
|
||||
try {
|
||||
await initGenerator(tree, { name: 'proj1' });
|
||||
await migrateFromAngularCli(tree, { name: 'proj1' });
|
||||
} catch (e) {
|
||||
expect(e.message).toContain(
|
||||
'An e2e project with Protractor was found but "e2e/protractor.conf.js" could not be found.'
|
||||
@ -101,7 +101,9 @@ describe('workspace', () => {
|
||||
project.targets.e2e.executor = '@cypress/schematic:cypress';
|
||||
updateProjectConfiguration(tree, 'myApp', project);
|
||||
|
||||
await expect(initGenerator(tree, { name: 'myApp' })).rejects.toThrow(
|
||||
await expect(
|
||||
migrateFromAngularCli(tree, { name: 'myApp' })
|
||||
).rejects.toThrow(
|
||||
'An e2e project with Cypress was found but "cypress.json" could not be found.'
|
||||
);
|
||||
});
|
||||
@ -116,7 +118,9 @@ describe('workspace', () => {
|
||||
};
|
||||
updateProjectConfiguration(tree, 'myApp', project);
|
||||
|
||||
await expect(initGenerator(tree, { name: 'myApp' })).rejects.toThrow(
|
||||
await expect(
|
||||
migrateFromAngularCli(tree, { name: 'myApp' })
|
||||
).rejects.toThrow(
|
||||
'An e2e project with Cypress was found but "cypress.config.json" could not be found.'
|
||||
);
|
||||
});
|
||||
@ -127,7 +131,9 @@ describe('workspace', () => {
|
||||
updateProjectConfiguration(tree, 'myApp', project);
|
||||
tree.write('cypress.json', '{}');
|
||||
|
||||
await expect(initGenerator(tree, { name: 'myApp' })).rejects.toThrow(
|
||||
await expect(
|
||||
migrateFromAngularCli(tree, { name: 'myApp' })
|
||||
).rejects.toThrow(
|
||||
'An e2e project with Cypress was found but the "cypress" directory could not be found.'
|
||||
);
|
||||
});
|
||||
@ -137,7 +143,9 @@ describe('workspace', () => {
|
||||
project.targets.e2e.executor = '@my-org/my-package:my-executor';
|
||||
updateProjectConfiguration(tree, 'myApp', project);
|
||||
|
||||
await expect(initGenerator(tree, { name: 'myApp' })).rejects.toThrow(
|
||||
await expect(
|
||||
migrateFromAngularCli(tree, { name: 'myApp' })
|
||||
).rejects.toThrow(
|
||||
`An e2e project was found but it's using an unsupported executor "@my-org/my-package:my-executor".`
|
||||
);
|
||||
});
|
||||
@ -145,7 +153,7 @@ describe('workspace', () => {
|
||||
it('should error if no angular.json is present', async () => {
|
||||
try {
|
||||
tree.delete('angular.json');
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
} catch (e) {
|
||||
expect(e.message).toContain('Cannot find angular.json');
|
||||
}
|
||||
@ -164,7 +172,7 @@ describe('workspace', () => {
|
||||
})
|
||||
);
|
||||
try {
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
} catch (e) {
|
||||
expect(e.message).toContain('Can only convert projects with one app');
|
||||
}
|
||||
@ -183,7 +191,7 @@ describe('workspace', () => {
|
||||
})
|
||||
);
|
||||
try {
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
} catch (e) {
|
||||
expect(e.message).toContain('Can only convert projects with one app');
|
||||
}
|
||||
@ -239,7 +247,7 @@ describe('workspace', () => {
|
||||
tree.write('/projects/myApp/e2e/protractor.conf.js', '// content');
|
||||
tree.write('/projects/myApp/src/app/app.module.ts', '// content');
|
||||
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
|
||||
const a = readJson(tree, '/angular.json');
|
||||
|
||||
@ -247,30 +255,30 @@ describe('workspace', () => {
|
||||
});
|
||||
|
||||
it('should set the default collection to @nrwl/angular', async () => {
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
expect(readJson(tree, 'nx.json').cli.defaultCollection).toBe(
|
||||
'@nrwl/angular'
|
||||
);
|
||||
});
|
||||
|
||||
it('should create nx.json', async () => {
|
||||
await initGenerator(tree, { name: 'myApp', defaultBase: 'main' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp', defaultBase: 'main' });
|
||||
expect(readJson(tree, 'nx.json')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should work if angular-cli workspace had tsconfig.base.json', async () => {
|
||||
tree.rename('tsconfig.json', 'tsconfig.base.json');
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
expect(readJson(tree, 'tsconfig.base.json')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should update tsconfig.base.json if present', async () => {
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
expect(readJson(tree, 'tsconfig.base.json')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should work without nested tsconfig files', async () => {
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true);
|
||||
});
|
||||
|
||||
@ -321,7 +329,7 @@ describe('workspace', () => {
|
||||
'/src/tsconfig.spec.json',
|
||||
'{"extends": "../tsconfig.json", "compilerOptions": {}}'
|
||||
);
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true);
|
||||
});
|
||||
|
||||
@ -374,7 +382,7 @@ describe('workspace', () => {
|
||||
tree.write('/projects/myApp/e2e/protractor.conf.js', '// content');
|
||||
tree.write('/projects/myApp/src/app/app.module.ts', '// content');
|
||||
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
|
||||
expect(tree.exists('/tslint.json')).toBe(true);
|
||||
expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true);
|
||||
@ -406,7 +414,7 @@ describe('workspace', () => {
|
||||
);
|
||||
tree.write('/karma.conf.js', '// content');
|
||||
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
|
||||
expect(tree.exists('/apps/myApp/tsconfig.app.json')).toBe(true);
|
||||
expect(tree.exists('/apps/myApp/karma.conf.js')).toBe(true);
|
||||
@ -416,7 +424,7 @@ describe('workspace', () => {
|
||||
|
||||
it('should work with existing .prettierignore file', async () => {
|
||||
tree.write('/.prettierignore', '# existing ignore rules');
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
|
||||
const prettierIgnore = tree.read('/.prettierignore').toString();
|
||||
expect(prettierIgnore).toBe('# existing ignore rules');
|
||||
@ -424,7 +432,7 @@ describe('workspace', () => {
|
||||
|
||||
it('should update tsconfigs', async () => {
|
||||
tree.write('/.prettierignore', '# existing ignore rules');
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
|
||||
const prettierIgnore = tree.read('/.prettierignore').toString();
|
||||
expect(prettierIgnore).toBe('# existing ignore rules');
|
||||
@ -432,7 +440,7 @@ describe('workspace', () => {
|
||||
|
||||
it('should work with no root tslint.json', async () => {
|
||||
tree.delete('/tslint.json');
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
|
||||
expect(tree.exists('/tslint.json')).toBe(false);
|
||||
});
|
||||
@ -518,7 +526,7 @@ describe('workspace', () => {
|
||||
});
|
||||
|
||||
it('should migrate e2e tests correctly', async () => {
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
|
||||
expect(tree.exists('cypress.json')).toBe(false);
|
||||
expect(tree.exists('cypress')).toBe(false);
|
||||
@ -550,7 +558,7 @@ describe('workspace', () => {
|
||||
updateProjectConfiguration(tree, 'myApp', project);
|
||||
tree.delete('cypress.json');
|
||||
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
|
||||
expect(tree.exists('cypress.json')).toBe(false);
|
||||
expect(tree.exists('cypress')).toBe(false);
|
||||
@ -569,7 +577,7 @@ describe('workspace', () => {
|
||||
delete project.targets['cypress-open'];
|
||||
updateProjectConfiguration(tree, 'myApp', project);
|
||||
|
||||
await initGenerator(tree, { name: 'myApp' });
|
||||
await migrateFromAngularCli(tree, { name: 'myApp' });
|
||||
|
||||
expect(tree.exists('cypress.json')).toBe(false);
|
||||
expect(tree.exists('cypress')).toBe(false);
|
||||
@ -589,7 +597,7 @@ describe('workspace', () => {
|
||||
});
|
||||
|
||||
it('should update package.json', async () => {
|
||||
await initGenerator(tree, {
|
||||
await migrateFromAngularCli(tree, {
|
||||
name: 'myApp',
|
||||
preserveAngularCliLayout: true,
|
||||
});
|
||||
@ -600,7 +608,7 @@ describe('workspace', () => {
|
||||
});
|
||||
|
||||
it('should create nx.json', async () => {
|
||||
await initGenerator(tree, {
|
||||
await migrateFromAngularCli(tree, {
|
||||
name: 'myApp',
|
||||
preserveAngularCliLayout: true,
|
||||
});
|
||||
@ -610,7 +618,7 @@ describe('workspace', () => {
|
||||
});
|
||||
|
||||
it('should create decorate-angular-cli.js', async () => {
|
||||
await initGenerator(tree, {
|
||||
await migrateFromAngularCli(tree, {
|
||||
name: 'myApp',
|
||||
preserveAngularCliLayout: true,
|
||||
});
|
||||
@ -1,7 +1,6 @@
|
||||
import {
|
||||
addDependenciesToPackageJson,
|
||||
addProjectConfiguration,
|
||||
convertNxGenerator,
|
||||
formatFiles,
|
||||
generateFiles,
|
||||
getProjects,
|
||||
@ -12,30 +11,26 @@ import {
|
||||
normalizePath,
|
||||
NxJsonConfiguration,
|
||||
offsetFromRoot,
|
||||
ProjectConfiguration,
|
||||
readJson,
|
||||
readProjectConfiguration,
|
||||
readWorkspaceConfiguration,
|
||||
TargetConfiguration,
|
||||
Tree,
|
||||
updateJson,
|
||||
updateProjectConfiguration,
|
||||
updateWorkspaceConfiguration,
|
||||
visitNotIgnoredFiles,
|
||||
writeJson,
|
||||
ProjectConfiguration,
|
||||
TargetConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { readFileSync } from 'fs';
|
||||
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 { basename, relative } from 'path';
|
||||
import { resolveUserExistingPrettierConfig } from '../../utilities/prettier';
|
||||
import { deduceDefaultBase } from '../../utilities/default-base';
|
||||
import {
|
||||
angularCliVersion,
|
||||
nxVersion,
|
||||
prettierVersion,
|
||||
} from '../../utils/versions';
|
||||
import { DEFAULT_NRWL_PRETTIER_CONFIG } from '../workspace/workspace';
|
||||
import { angularDevkitVersion, nxVersion } from '../../utils/versions';
|
||||
import { Schema } from './schema';
|
||||
import { getRootTsConfigPathInTree } from '../../utilities/typescript';
|
||||
|
||||
function updatePackageJson(tree) {
|
||||
updateJson(tree, 'package.json', (packageJson) => {
|
||||
@ -66,12 +61,14 @@ function updatePackageJson(tree) {
|
||||
if (!packageJson.dependencies['@nrwl/angular']) {
|
||||
packageJson.dependencies['@nrwl/angular'] = nxVersion;
|
||||
}
|
||||
delete packageJson.dependencies['@nrwl/workspace'];
|
||||
if (!packageJson.devDependencies['@angular/cli']) {
|
||||
packageJson.devDependencies['@angular/cli'] = angularDevkitVersion;
|
||||
}
|
||||
if (!packageJson.devDependencies['@nrwl/workspace']) {
|
||||
packageJson.devDependencies['@nrwl/workspace'] = nxVersion;
|
||||
}
|
||||
if (!packageJson.devDependencies['@angular/cli']) {
|
||||
packageJson.devDependencies['@angular/cli'] = angularCliVersion;
|
||||
if (!packageJson.devDependencies['nx']) {
|
||||
packageJson.devDependencies['nx'] = nxVersion;
|
||||
}
|
||||
if (!packageJson.devDependencies['prettier']) {
|
||||
packageJson.devDependencies['prettier'] = prettierVersion;
|
||||
@ -623,25 +620,7 @@ function replaceCypressGlobConfig(
|
||||
);
|
||||
}
|
||||
|
||||
async function createAdditionalFiles(host: Tree, options: Schema) {
|
||||
const nxJson: NxJsonConfiguration = {
|
||||
npmScope: options.npmScope,
|
||||
affected: {
|
||||
defaultBase: options.defaultBase
|
||||
? `${options.defaultBase}`
|
||||
: deduceDefaultBase(),
|
||||
},
|
||||
implicitDependencies: {
|
||||
'angular.json': '*',
|
||||
'package.json': '*',
|
||||
'tslint.json': '*',
|
||||
'.eslintrc.json': '*',
|
||||
'nx.json': '*',
|
||||
},
|
||||
};
|
||||
writeJson(host, 'nx.json', nxJson);
|
||||
host.write('libs/.gitkeep', '');
|
||||
|
||||
async function createAdditionalFiles(host: Tree) {
|
||||
const recommendations = [
|
||||
'nrwl.angular-console',
|
||||
'angular.ng-template',
|
||||
@ -749,8 +728,7 @@ function checkCanConvertToWorkspace(host: Tree) {
|
||||
}
|
||||
|
||||
function createNxJson(host: Tree) {
|
||||
const json = JSON.parse(host.read('angular.json').toString());
|
||||
const projects = json.projects || {};
|
||||
const { projects = {}, newProjectRoot = '' } = readJson(host, 'angular.json');
|
||||
const hasLibraries = Object.keys(projects).find(
|
||||
(project) =>
|
||||
projects[project].projectType &&
|
||||
@ -782,19 +760,17 @@ function createNxJson(host: Tree) {
|
||||
},
|
||||
},
|
||||
},
|
||||
workspaceLayout: { appsDir: newProjectRoot, libsDir: newProjectRoot },
|
||||
});
|
||||
}
|
||||
|
||||
function decorateAngularClI(host: Tree) {
|
||||
const decorateCli = readFileSync(
|
||||
joinPathFragments(
|
||||
__dirname as any,
|
||||
'..',
|
||||
'utils',
|
||||
'decorate-angular-cli.js__tmpl__'
|
||||
)
|
||||
).toString();
|
||||
host.write('decorate-angular-cli.js', decorateCli);
|
||||
function decorateAngularCli(host: Tree) {
|
||||
generateFiles(
|
||||
host,
|
||||
joinPathFragments(__dirname, 'files', 'decorate-angular-cli'),
|
||||
'.',
|
||||
{ tmpl: '' }
|
||||
);
|
||||
updateJson(host, 'package.json', (json) => {
|
||||
if (
|
||||
json.scripts &&
|
||||
@ -815,11 +791,13 @@ function decorateAngularClI(host: Tree) {
|
||||
});
|
||||
}
|
||||
|
||||
function addFiles(host: Tree) {
|
||||
function addFiles(host: Tree, options: Schema) {
|
||||
generateFiles(host, joinPathFragments(__dirname, './files/root'), '.', {
|
||||
tmpl: '',
|
||||
dot: '.',
|
||||
rootTsConfigPath: getRootTsConfigPathInTree(host),
|
||||
npmScope: options.npmScope,
|
||||
defaultBase: options.defaultBase ?? deduceDefaultBase(),
|
||||
});
|
||||
|
||||
if (!host.exists('.prettierignore')) {
|
||||
@ -857,15 +835,18 @@ function renameDirSyncInTree(tree: Tree, from: string, to: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function initGenerator(tree: Tree, schema: Schema) {
|
||||
export async function migrateFromAngularCli(tree: Tree, schema: Schema) {
|
||||
if (schema.preserveAngularCliLayout) {
|
||||
updateJson(tree, 'package.json', (json) => {
|
||||
delete json.dependencies?.['@nrwl/workspace'];
|
||||
return json;
|
||||
});
|
||||
addDependenciesToPackageJson(tree, {}, { '@nrwl/workspace': nxVersion });
|
||||
addDependenciesToPackageJson(
|
||||
tree,
|
||||
{},
|
||||
{
|
||||
nx: nxVersion,
|
||||
'@nrwl/workspace': nxVersion,
|
||||
}
|
||||
);
|
||||
createNxJson(tree);
|
||||
decorateAngularClI(tree);
|
||||
decorateAngularCli(tree);
|
||||
} else {
|
||||
const options = {
|
||||
...schema,
|
||||
@ -874,15 +855,15 @@ export async function initGenerator(tree: Tree, schema: Schema) {
|
||||
|
||||
checkCanConvertToWorkspace(tree);
|
||||
moveExistingFiles(tree, options);
|
||||
addFiles(tree);
|
||||
await createAdditionalFiles(tree, options);
|
||||
addFiles(tree, options);
|
||||
await createAdditionalFiles(tree);
|
||||
updatePackageJson(tree);
|
||||
updateAngularCLIJson(tree, options);
|
||||
updateTsLint(tree);
|
||||
updateProjectTsLint(tree, options);
|
||||
updateTsConfig(tree);
|
||||
updateTsConfigsJson(tree, options);
|
||||
decorateAngularClI(tree);
|
||||
decorateAngularCli(tree);
|
||||
await formatFiles(tree);
|
||||
}
|
||||
if (!schema.skipInstall) {
|
||||
@ -891,7 +872,3 @@ export async function initGenerator(tree: Tree, schema: Schema) {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const initSchematic = convertNxGenerator(initGenerator);
|
||||
|
||||
export default initGenerator;
|
||||
36
packages/angular/src/generators/ng-add/ng-add.spec.ts
Normal file
36
packages/angular/src/generators/ng-add/ng-add.spec.ts
Normal file
@ -0,0 +1,36 @@
|
||||
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 { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
|
||||
|
||||
describe('ngAdd generator', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace(2);
|
||||
jest
|
||||
.spyOn(angularCliMigrator, 'migrateFromAngularCli')
|
||||
.mockImplementation(() => Promise.resolve(() => {}));
|
||||
jest
|
||||
.spyOn(initGenerator, 'angularInitGenerator')
|
||||
.mockImplementation(() => Promise.resolve(() => {}));
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should initialize the Angular plugin when in an Nx workspace', async () => {
|
||||
await ngAddGenerator(tree, {});
|
||||
|
||||
expect(initGenerator.angularInitGenerator).toHaveBeenCalled();
|
||||
expect(angularCliMigrator.migrateFromAngularCli).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should perform a migration when in an Angular CLI workspace', async () => {
|
||||
tree.delete('nx.json');
|
||||
|
||||
await ngAddGenerator(tree, {});
|
||||
|
||||
expect(angularCliMigrator.migrateFromAngularCli).toHaveBeenCalled();
|
||||
expect(initGenerator.angularInitGenerator).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
18
packages/angular/src/generators/ng-add/ng-add.ts
Normal file
18
packages/angular/src/generators/ng-add/ng-add.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Tree } from '@nrwl/devkit';
|
||||
import { migrateFromAngularCli } from './migrate-from-angular-cli';
|
||||
import { angularInitGenerator } from '../init/init';
|
||||
import { Schema } from './schema';
|
||||
|
||||
function getWorkspaceType(tree: Tree): 'angular' | 'nx' {
|
||||
return tree.exists('nx.json') ? 'nx' : 'angular';
|
||||
}
|
||||
|
||||
export async function ngAddGenerator(tree: Tree, options: Schema) {
|
||||
if (getWorkspaceType(tree) === 'angular') {
|
||||
return await migrateFromAngularCli(tree, options);
|
||||
}
|
||||
|
||||
return angularInitGenerator(tree, options);
|
||||
}
|
||||
|
||||
export default ngAddGenerator;
|
||||
19
packages/angular/src/generators/ng-add/schema.d.ts
vendored
Normal file
19
packages/angular/src/generators/ng-add/schema.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import { Linter } from '@nrwl/linter';
|
||||
import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners';
|
||||
import type { Styles } from '../utils/types';
|
||||
|
||||
export interface Schema {
|
||||
name?: string;
|
||||
skipInstall?: boolean;
|
||||
npmScope?: string;
|
||||
preserveAngularCliLayout?: boolean;
|
||||
defaultBase?: string;
|
||||
|
||||
unitTestRunner?: UnitTestRunner;
|
||||
e2eTestRunner?: E2eTestRunner;
|
||||
skipFormat?: boolean;
|
||||
skipInstall?: boolean;
|
||||
style?: Styles;
|
||||
linter?: Exclude<Linter, Linter.TsLint>;
|
||||
skipPackageJson?: boolean;
|
||||
}
|
||||
71
packages/angular/src/generators/ng-add/schema.json
Normal file
71
packages/angular/src/generators/ng-add/schema.json
Normal file
@ -0,0 +1,71 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "NxAngularNgAddGenerator",
|
||||
"title": "Angular plugin initialization",
|
||||
"cli": "nx",
|
||||
"description": "Migrates an Angular CLI workspace to Nx or adds the Angular plugin to an Nx workspace. NOTE: Does not work in the `--dry-run` mode.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"npmScope": {
|
||||
"type": "string",
|
||||
"description": "Npm scope for importing libs. NOTE: only used if running the generator in an Angular CLI workspace."
|
||||
},
|
||||
"defaultBase": {
|
||||
"type": "string",
|
||||
"description": "Default base branch for affected. NOTE: only used if running the generator in an Angular CLI workspace."
|
||||
},
|
||||
"skipInstall": {
|
||||
"type": "boolean",
|
||||
"description": "Skip installing added packages.",
|
||||
"default": false
|
||||
},
|
||||
"preserveAngularCliLayout": {
|
||||
"type": "boolean",
|
||||
"description": "Preserve the Angular CLI layout instead of moving the app into apps. NOTE: only used if running the generator in an Angular CLI workspace.",
|
||||
"default": false
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Project name. NOTE: only used if running the generator in an Angular CLI workspace.",
|
||||
"$default": {
|
||||
"$source": "projectName"
|
||||
}
|
||||
},
|
||||
|
||||
"unitTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["karma", "jest", "none"],
|
||||
"description": "Test runner to use for unit tests. NOTE: only used if running the generator in an Nx workspace.",
|
||||
"default": "jest"
|
||||
},
|
||||
"e2eTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["protractor", "cypress", "none"],
|
||||
"description": "Test runner to use for end to end (e2e) tests. NOTE: only used if running the generator in an Nx workspace.",
|
||||
"default": "cypress"
|
||||
},
|
||||
"skipFormat": {
|
||||
"description": "Skip formatting files. NOTE: only used if running the generator in an Nx workspace.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"linter": {
|
||||
"description": "The tool to use for running lint checks. NOTE: only used if running the generator in an Nx workspace.",
|
||||
"type": "string",
|
||||
"enum": ["eslint", "none"],
|
||||
"default": "eslint"
|
||||
},
|
||||
"style": {
|
||||
"description": "The file extension to be used for style files. NOTE: only used if running the generator in an Nx workspace.",
|
||||
"type": "string",
|
||||
"default": "css",
|
||||
"enum": ["css", "scss", "sass", "less"]
|
||||
},
|
||||
"skipPackageJson": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not add dependencies to `package.json`. NOTE: only used if running the generator in an Nx workspace."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@ -16,8 +16,10 @@
|
||||
"Cypress",
|
||||
"CLI"
|
||||
],
|
||||
"main": "src/index.js",
|
||||
"typings": "src/index.d.ts",
|
||||
"bin": {
|
||||
"make-angular-cli-faster": "./make-angular-cli-faster.js"
|
||||
"make-angular-cli-faster": "./src/index.js"
|
||||
},
|
||||
"author": "Victor Savkin",
|
||||
"license": "MIT",
|
||||
@ -26,9 +28,13 @@
|
||||
},
|
||||
"homepage": "https://nx.dev",
|
||||
"dependencies": {
|
||||
"yargs-parser": "20.0.0",
|
||||
"yargs": "15.4.1",
|
||||
"@nrwl/devkit": "*",
|
||||
"@nrwl/workspace": "*",
|
||||
"enquirer": "^2.3.6",
|
||||
"@nrwl/workspace": "*"
|
||||
"nx": "*",
|
||||
"semver": "7.3.4",
|
||||
"tmp": "~0.2.1",
|
||||
"yargs": "15.4.1",
|
||||
"yargs-parser": "20.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
"options": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "node ./scripts/chmod build/packages/make-angular-cli-faster/src/make-angular-cli-faster.js"
|
||||
"command": "node ./scripts/chmod build/packages/make-angular-cli-faster/src/index.js"
|
||||
},
|
||||
{
|
||||
"command": "node ./scripts/copy-readme.js make-angular-cli-faster"
|
||||
|
||||
21
packages/make-angular-cli-faster/src/index.ts
Normal file
21
packages/make-angular-cli-faster/src/index.ts
Normal file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as yargsParser from 'yargs-parser';
|
||||
import {
|
||||
Args,
|
||||
makeAngularCliFaster,
|
||||
} from './utilities/make-angular-cli-faster';
|
||||
|
||||
const args = yargsParser(process.argv, {
|
||||
string: ['version'],
|
||||
boolean: ['verbose'],
|
||||
});
|
||||
|
||||
makeAngularCliFaster(args as Args)
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
process.exit(1);
|
||||
});
|
||||
@ -1,94 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { statSync } from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
import * as yargsParser from 'yargs-parser';
|
||||
|
||||
const enquirer = require('enquirer');
|
||||
|
||||
const parsedArgs = yargsParser(process.argv, {
|
||||
string: ['version'],
|
||||
boolean: ['verbose'],
|
||||
});
|
||||
|
||||
function isYarn() {
|
||||
try {
|
||||
statSync('yarn.lock');
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function addDependency(dep: string) {
|
||||
const stdio = parsedArgs.verbose ? [0, 1, 2] : ['ignore', 'ignore', 'ignore'];
|
||||
if (isYarn()) {
|
||||
execSync(`yarn add -D ${dep}`, { stdio: stdio as any });
|
||||
} else {
|
||||
execSync(`npm i --save-dev ${dep}`, { stdio: stdio as any });
|
||||
}
|
||||
}
|
||||
|
||||
function addNxCloud() {
|
||||
return enquirer
|
||||
.prompt([
|
||||
{
|
||||
name: 'NxCloud',
|
||||
message: `Use the free tier of the distributed cache provided by Nx Cloud?`,
|
||||
type: 'list',
|
||||
choices: [
|
||||
{
|
||||
value: 'yes',
|
||||
name: 'Yes [Faster command execution, faster CI. Learn more at https://nx.app]',
|
||||
},
|
||||
|
||||
{
|
||||
value: 'no',
|
||||
name: 'No [Only use local computation cache]',
|
||||
},
|
||||
],
|
||||
default: 'no',
|
||||
},
|
||||
])
|
||||
.then((a: { NxCloud: 'yes' | 'no' }) => a.NxCloud === 'yes');
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const version = parsedArgs.version ? parsedArgs.version : `latest`;
|
||||
|
||||
const output = require('@nrwl/workspace/src/utils/output').output;
|
||||
output.log({ title: 'Nx initialization' });
|
||||
|
||||
addDependency(`nx@${version}`);
|
||||
addDependency(`@nrwl/cli@${version}`);
|
||||
addDependency(`@nrwl/workspace@${version}`);
|
||||
execSync(`nx g @nrwl/workspace:ng-add --preserve-angular-cli-layout`, {
|
||||
stdio: [0, 1, 2],
|
||||
});
|
||||
|
||||
if (await addNxCloud()) {
|
||||
output.log({ title: 'Nx Cloud initialization' });
|
||||
addDependency(`@nrwl/nx-cloud`);
|
||||
execSync(`nx g @nrwl/nx-cloud:init`, { stdio: [0, 1, 2] });
|
||||
}
|
||||
|
||||
execSync('npm install', { stdio: ['ignore', 'ignore', 'ignore'] });
|
||||
|
||||
output.success({
|
||||
title: 'Angular CLI is faster now!',
|
||||
bodyLines: [
|
||||
`Execute 'npx ng build' twice to see the computation caching in action.`,
|
||||
`Learn more about computation caching, how it is shared with your teammates,`,
|
||||
`and how it can speed up your CI by up to 10 times at https://nx.dev/angular`,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
process.exit(1);
|
||||
});
|
||||
@ -0,0 +1,38 @@
|
||||
import { output } from '@nrwl/workspace/src/utilities/output';
|
||||
import { determineMigration, migrateWorkspace } from './migration';
|
||||
import { initNxCloud, promptForNxCloud } from './nx-cloud';
|
||||
import { installDependencies } from './package-manager';
|
||||
|
||||
export interface Args {
|
||||
version?: string;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
export async function makeAngularCliFaster(args: Args) {
|
||||
output.log({ title: '🐳 Nx initialization' });
|
||||
|
||||
output.log({ title: '🧐 Checking versions compatibility' });
|
||||
const migration = await determineMigration(args.version);
|
||||
|
||||
const useNxCloud = await promptForNxCloud();
|
||||
|
||||
output.log({ title: '📦 Installing dependencies' });
|
||||
installDependencies(migration, useNxCloud);
|
||||
|
||||
output.log({ title: '📝 Setting up workspace for faster computation' });
|
||||
migrateWorkspace(migration);
|
||||
|
||||
if (useNxCloud) {
|
||||
output.log({ title: '🛠️ Setting up Nx Cloud' });
|
||||
initNxCloud();
|
||||
}
|
||||
|
||||
output.success({
|
||||
title: '🎉 Angular CLI is faster now!',
|
||||
bodyLines: [
|
||||
`Execute 'npx ng build' twice to see the computation caching in action.`,
|
||||
'Learn more about computation caching, how it is shared with your teammates,' +
|
||||
' and how it can speed up your CI by up to 10 times at https://nx.dev/getting-started/nx-and-angular',
|
||||
],
|
||||
});
|
||||
}
|
||||
182
packages/make-angular-cli-faster/src/utilities/migration.ts
Normal file
182
packages/make-angular-cli-faster/src/utilities/migration.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import { getPackageManagerCommand, readJsonFile } from '@nrwl/devkit';
|
||||
import { output } from '@nrwl/workspace/src/utilities/output';
|
||||
import { execSync } from 'child_process';
|
||||
import { prompt } from 'enquirer';
|
||||
import { appRootPath } from 'nx/src/utils/app-root';
|
||||
import { lt, lte, major, satisfies } from 'semver';
|
||||
import { resolvePackageVersion } from './package-manager';
|
||||
import { MigrationDefinition } from './types';
|
||||
|
||||
// up to this version the generator was in the @nrwl/workspace package
|
||||
const latestWorkspaceVersionWithMigration = '13.9.3';
|
||||
// up to this version the preserveAngularCLILayout option was used
|
||||
const latestVersionWithOldFlag = '13.8.3';
|
||||
// map of Angular major versions to the versions of Nx that are compatible with them,
|
||||
// key is the Angular major version, value is an object with the range of supported
|
||||
// versions and the max version of the range if there's a bigger major version that
|
||||
// is already supported
|
||||
const nxAngularVersionMap: Record<number, { range: string; max?: string }> = {
|
||||
13: { range: '>= 13.2.0' },
|
||||
};
|
||||
// latest major version of Angular that is compatible with Nx, based on the map above
|
||||
const latestCompatibleAngularMajorVersion = Math.max(
|
||||
...Object.keys(nxAngularVersionMap).map((x) => +x)
|
||||
);
|
||||
|
||||
export async function determineMigration(
|
||||
version: string | undefined
|
||||
): Promise<MigrationDefinition> {
|
||||
const angularVersion = getInstalledAngularVersion();
|
||||
const majorAngularVersion = major(angularVersion);
|
||||
|
||||
if (version) {
|
||||
const normalizedVersion = normalizeVersion(version);
|
||||
if (lte(normalizedVersion, latestWorkspaceVersionWithMigration)) {
|
||||
// specified version should use @nrwl/workspace:ng-add
|
||||
return { packageName: '@nrwl/workspace', version: normalizedVersion };
|
||||
}
|
||||
|
||||
// if greater than the latest workspace version with the migration,
|
||||
// check the versions map for compatibility with Angular
|
||||
if (
|
||||
nxAngularVersionMap[majorAngularVersion] &&
|
||||
satisfies(
|
||||
normalizedVersion,
|
||||
nxAngularVersionMap[majorAngularVersion].range
|
||||
)
|
||||
) {
|
||||
// there's a match, use @nrwl/angular:ng-add
|
||||
return { packageName: '@nrwl/angular', version: normalizedVersion };
|
||||
}
|
||||
|
||||
// it's not compatible with the currently installed Angular version,
|
||||
// suggest the latest possible version to use
|
||||
return await findAndSuggestVersionToUse(
|
||||
angularVersion,
|
||||
majorAngularVersion,
|
||||
version
|
||||
);
|
||||
}
|
||||
|
||||
const latestNxCompatibleVersion = getNxVersionBasedOnInstalledAngularVersion(
|
||||
angularVersion,
|
||||
majorAngularVersion
|
||||
);
|
||||
|
||||
// should use @nrwl/workspace:ng-add if the version is less than the
|
||||
// latest workspace version that has the migration, otherwise use
|
||||
// @nrwl/angular:ng-add
|
||||
return {
|
||||
packageName: lte(
|
||||
latestNxCompatibleVersion,
|
||||
latestWorkspaceVersionWithMigration
|
||||
)
|
||||
? '@nrwl/workspace'
|
||||
: '@nrwl/angular',
|
||||
version: latestNxCompatibleVersion,
|
||||
};
|
||||
}
|
||||
|
||||
export function migrateWorkspace(migration: MigrationDefinition): void {
|
||||
const preserveAngularCliLayoutFlag = lte(
|
||||
migration.version,
|
||||
latestVersionWithOldFlag
|
||||
)
|
||||
? '--preserveAngularCLILayout'
|
||||
: '--preserve-angular-cli-layout';
|
||||
execSync(
|
||||
`${getPackageManagerCommand().exec} nx g ${
|
||||
migration.packageName
|
||||
}:ng-add ${preserveAngularCliLayoutFlag}`,
|
||||
{ stdio: [0, 1, 2] }
|
||||
);
|
||||
}
|
||||
|
||||
async function findAndSuggestVersionToUse(
|
||||
angularVersion: string,
|
||||
majorAngularVersion: number,
|
||||
userSpecifiedVersion: string
|
||||
): Promise<MigrationDefinition> {
|
||||
const latestNxCompatibleVersion = getNxVersionBasedOnInstalledAngularVersion(
|
||||
angularVersion,
|
||||
majorAngularVersion
|
||||
);
|
||||
const useSuggestedVersion = await promptForVersion(latestNxCompatibleVersion);
|
||||
if (useSuggestedVersion) {
|
||||
// should use @nrwl/workspace:ng-add if the version is less than the
|
||||
// latest workspace version that has the migration, otherwise use
|
||||
// @nrwl/angular:ng-add
|
||||
return {
|
||||
packageName: lte(
|
||||
latestNxCompatibleVersion,
|
||||
latestWorkspaceVersionWithMigration
|
||||
)
|
||||
? '@nrwl/workspace'
|
||||
: '@nrwl/angular',
|
||||
version: latestNxCompatibleVersion,
|
||||
};
|
||||
}
|
||||
|
||||
output.error({
|
||||
title: `❌ Cannot proceed with the migration.`,
|
||||
bodyLines: [
|
||||
`The specified Nx version "${userSpecifiedVersion}" is not compatible with the installed Angular version "${angularVersion}".`,
|
||||
],
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function getNxVersionBasedOnInstalledAngularVersion(
|
||||
angularVersion: string,
|
||||
majorAngularVersion: number
|
||||
): string {
|
||||
if (lt(angularVersion, '13.0.0')) {
|
||||
// the @nrwl/angular:ng-add generator is only available for versions supporting
|
||||
// Angular >= 13.0.0, fall back to @nrwl/workspace:ng-add
|
||||
return latestWorkspaceVersionWithMigration;
|
||||
}
|
||||
if (nxAngularVersionMap[majorAngularVersion]?.max) {
|
||||
// use the max of the range
|
||||
return nxAngularVersionMap[majorAngularVersion].max;
|
||||
}
|
||||
if (majorAngularVersion > latestCompatibleAngularMajorVersion) {
|
||||
// installed Angular version is not supported yet, we can't @nrwl/angular:ng-add,
|
||||
// fall back to @nrwl/workspace:ng-add
|
||||
return latestWorkspaceVersionWithMigration;
|
||||
}
|
||||
|
||||
// use latest, only the last version in the map should not contain a max
|
||||
return resolvePackageVersion('@nrwl/angular', 'latest');
|
||||
}
|
||||
|
||||
async function promptForVersion(version: string): Promise<boolean> {
|
||||
const { useVersion } = await prompt<{ useVersion: boolean }>([
|
||||
{
|
||||
name: 'useVersion',
|
||||
message: `The provided version of Nx is not compatible with the installed Angular CLI version. Would you like to use the recommended version "${version}" instead?`,
|
||||
type: 'confirm',
|
||||
initial: true,
|
||||
},
|
||||
]);
|
||||
|
||||
return useVersion;
|
||||
}
|
||||
|
||||
function getInstalledAngularVersion(): string {
|
||||
const packageJsonPath = require.resolve('@angular/core/package.json', {
|
||||
paths: [appRootPath],
|
||||
});
|
||||
return readJsonFile(packageJsonPath).version;
|
||||
}
|
||||
|
||||
function normalizeVersion(version: string): string {
|
||||
if (
|
||||
version.startsWith('^') ||
|
||||
version.startsWith('~') ||
|
||||
version.split('.').length < 3
|
||||
) {
|
||||
return resolvePackageVersion('@nrwl/angular', version);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
29
packages/make-angular-cli-faster/src/utilities/nx-cloud.ts
Normal file
29
packages/make-angular-cli-faster/src/utilities/nx-cloud.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { getPackageManagerCommand } from '@nrwl/devkit';
|
||||
import { execSync } from 'child_process';
|
||||
import { prompt } from 'enquirer';
|
||||
|
||||
export async function promptForNxCloud(): Promise<boolean> {
|
||||
const { useNxCloud } = await prompt<{ useNxCloud: 'Yes' | 'No' }>([
|
||||
{
|
||||
name: 'useNxCloud',
|
||||
message: `Use Nx Cloud? (It's free and doesn't require registration.)`,
|
||||
type: 'select',
|
||||
choices: [
|
||||
{
|
||||
name: 'Yes',
|
||||
hint: 'Yes [Faster builds, run details, Github integration. Learn more at https://nx.app]',
|
||||
},
|
||||
{ name: 'No' },
|
||||
],
|
||||
initial: 'Yes' as any,
|
||||
},
|
||||
]);
|
||||
|
||||
return useNxCloud === 'Yes';
|
||||
}
|
||||
|
||||
export function initNxCloud(): void {
|
||||
execSync(`${getPackageManagerCommand().exec} nx g @nrwl/nx-cloud:init`, {
|
||||
stdio: [0, 1, 2],
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
import {
|
||||
getPackageManagerCommand,
|
||||
readJsonFile,
|
||||
writeJsonFile,
|
||||
} from '@nrwl/devkit';
|
||||
import { execSync } from 'child_process';
|
||||
import { copyFileSync, existsSync, unlinkSync, writeFileSync } from 'fs';
|
||||
import { appRootPath } from 'nx/src/utils/app-root';
|
||||
import { sortObjectByKeys } from 'nx/src/utils/object-sort';
|
||||
import { dirname, join } from 'path';
|
||||
import { gte, lt, major } from 'semver';
|
||||
import { dirSync } from 'tmp';
|
||||
import { MigrationDefinition } from './types';
|
||||
|
||||
// version when the Nx CLI changed from @nrwl/tao & @nrwl/cli to nx
|
||||
const versionWithConsolidatedPackages = '13.9.0';
|
||||
|
||||
export function installDependencies(
|
||||
{ packageName, version }: MigrationDefinition,
|
||||
useNxCloud: boolean
|
||||
): void {
|
||||
const json = readJsonFile(join(appRootPath, 'package.json'));
|
||||
|
||||
json.devDependencies ??= {};
|
||||
json.devDependencies['@nrwl/workspace'] = version;
|
||||
|
||||
if (gte(version, versionWithConsolidatedPackages)) {
|
||||
json.devDependencies['nx'] = version;
|
||||
} else {
|
||||
json.devDependencies['@nrwl/cli'] = version;
|
||||
json.devDependencies['@nrwl/tao'] = version;
|
||||
}
|
||||
|
||||
if (useNxCloud) {
|
||||
// get the latest @nrwl/nx-cloud version compatible with the Nx major
|
||||
// version being installed
|
||||
json.devDependencies['@nrwl/nx-cloud'] = resolvePackageVersion(
|
||||
'@nrwl/nx-cloud',
|
||||
`^${major(version)}.0.0`
|
||||
);
|
||||
}
|
||||
json.devDependencies = sortObjectByKeys(json.devDependencies);
|
||||
|
||||
if (packageName === '@nrwl/angular') {
|
||||
json.dependencies ??= {};
|
||||
json.dependencies['@nrwl/angular'] = version;
|
||||
json.dependencies = sortObjectByKeys(json.dependencies);
|
||||
}
|
||||
writeFileSync(`package.json`, JSON.stringify(json, null, 2));
|
||||
|
||||
execSync(getPackageManagerCommand().install, {
|
||||
stdio: [0, 1, 2],
|
||||
});
|
||||
}
|
||||
|
||||
export function resolvePackageVersion(
|
||||
packageName: string,
|
||||
version: string
|
||||
): string {
|
||||
const dir = dirSync().name;
|
||||
const npmrc = checkForNPMRC();
|
||||
if (npmrc) {
|
||||
// Creating a package.json is needed for .npmrc to resolve
|
||||
writeJsonFile(`${dir}/package.json`, {});
|
||||
// Copy npmrc if it exists, so that npm still follows it.
|
||||
copyFileSync(npmrc, `${dir}/.npmrc`);
|
||||
}
|
||||
|
||||
const pmc = getPackageManagerCommand();
|
||||
execSync(`${pmc.add} ${packageName}@${version}`, { stdio: [], cwd: dir });
|
||||
|
||||
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
||||
paths: [dir],
|
||||
});
|
||||
const { version: resolvedVersion } = readJsonFile(packageJsonPath);
|
||||
|
||||
try {
|
||||
unlinkSync(dir);
|
||||
} catch {
|
||||
// It's okay if this fails, the OS will clean it up eventually
|
||||
}
|
||||
|
||||
return resolvedVersion;
|
||||
}
|
||||
|
||||
function checkForNPMRC(): string | null {
|
||||
let directory = process.cwd();
|
||||
while (!existsSync(join(directory, 'package.json'))) {
|
||||
directory = dirname(directory);
|
||||
}
|
||||
const path = join(directory, '.npmrc');
|
||||
return existsSync(path) ? path : null;
|
||||
}
|
||||
4
packages/make-angular-cli-faster/src/utilities/types.ts
Normal file
4
packages/make-angular-cli-faster/src/utilities/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface MigrationDefinition {
|
||||
packageName: string;
|
||||
version: string;
|
||||
}
|
||||
@ -9,13 +9,6 @@
|
||||
"hidden": true
|
||||
},
|
||||
|
||||
"ng-add": {
|
||||
"factory": "./src/generators/init/init#initSchematic",
|
||||
"schema": "./src/generators/init/schema.json",
|
||||
"description": "Convert an existing Angular CLI project into an Nx Workspace",
|
||||
"hidden": true
|
||||
},
|
||||
|
||||
"preset": {
|
||||
"factory": "./src/generators/preset/preset#presetSchematic",
|
||||
"schema": "./src/generators/preset/schema.json",
|
||||
@ -87,13 +80,6 @@
|
||||
"hidden": true
|
||||
},
|
||||
|
||||
"ng-add": {
|
||||
"factory": "./src/generators/init/init#initGenerator",
|
||||
"schema": "./src/generators/init/schema.json",
|
||||
"description": "Convert an existing Angular CLI project into an Nx Workspace",
|
||||
"hidden": true
|
||||
},
|
||||
|
||||
"preset": {
|
||||
"factory": "./src/generators/preset/preset#presetGenerator",
|
||||
"schema": "./src/generators/preset/schema.json",
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
export interface Schema {
|
||||
name: string;
|
||||
skipInstall?: boolean;
|
||||
npmScope?: string;
|
||||
preserveAngularCliLayout?: boolean;
|
||||
defaultBase?: string;
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "SchematicsNxNgAdd",
|
||||
"title": "Init Workspace",
|
||||
"cli": "ng",
|
||||
"description": "NOTE: Does not work in the --dry-run mode",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"npmScope": {
|
||||
"type": "string",
|
||||
"description": "Npm scope for importing libs."
|
||||
},
|
||||
"defaultBase": {
|
||||
"type": "string",
|
||||
"description": "Default base branch for affected."
|
||||
},
|
||||
"skipInstall": {
|
||||
"type": "boolean",
|
||||
"description": "Skip installing after adding @nrwl/workspace",
|
||||
"default": false
|
||||
},
|
||||
"preserveAngularCliLayout": {
|
||||
"type": "boolean",
|
||||
"description": "Preserve the Angular CLI layout instead of moving the app into apps.",
|
||||
"default": false
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Project name.",
|
||||
"$default": {
|
||||
"$source": "projectName"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,6 +116,7 @@ const IGNORE_MATCHES = {
|
||||
'@nrwl/devkit',
|
||||
],
|
||||
nest: ['semver'],
|
||||
'make-angular-cli-faster': ['@angular/core'],
|
||||
};
|
||||
|
||||
export default async function getMissingDependencies(
|
||||
|
||||
@ -189,7 +189,6 @@ function build(nxVersion: string) {
|
||||
].map((f) => `${f}/package.json`),
|
||||
'create-nx-workspace/bin/create-nx-workspace.js',
|
||||
'create-nx-plugin/bin/create-nx-plugin.js',
|
||||
'make-angular-cli-faster/src/make-angular-cli-faster.js',
|
||||
'add-nx-to-monorepo/src/add-nx-to-monorepo.js',
|
||||
].map((f) => `${BUILD_DIR}/${f}`);
|
||||
|
||||
|
||||
@ -28,7 +28,6 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i "" "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js
|
||||
sed -i "" "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js
|
||||
sed -i "" "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js
|
||||
sed -i "" "s|NX_VERSION|$NX_VERSION|g" make-angular-cli-faster/src/make-angular-cli-faster.js
|
||||
sed -i "" "s|NX_VERSION|$NX_VERSION|g" add-nx-to-monorepo/src/add-nx-to-monorepo.js
|
||||
else
|
||||
sed -i "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin,react-native,detox,js}/src/utils/versions.js
|
||||
@ -41,7 +40,6 @@ else
|
||||
sed -i "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js
|
||||
sed -i "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js
|
||||
sed -i "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js
|
||||
sed -i "s|NX_VERSION|$NX_VERSION|g" make-angular-cli-faster/src/make-angular-cli-faster.js
|
||||
sed -i "s|NX_VERSION|$NX_VERSION|g" add-nx-to-monorepo/src/add-nx-to-monorepo.js
|
||||
fi
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user