Alan Agius 2e0493130a refactor(@angular/cli): replace command line arguments parser
With this change we refactor the Angular CLI and replace the underlying args parser and command builder. We choose to use Yargs as our parser and command builder of choice. The main advantages of Yargs over other command builders are;

- Highly configurable.
- We already use it in other packages such as the compiler-cli/dev-infra etc..
- Commands and options can be added during runtime. This is a requirement that is needed to support architect and schematics commands.
- Outstanding documentation.
- The possibility to parse args without parser configuration (Free form).
- Commands are built lazily based on the arguments passed.

BREAKING CHANGE:

Several changes in the Angular CLI commands and arguments handling.

- `ng help` has been removed in favour of the `—-help` option.
- `ng —-version` has been removed in favour of `ng version` and `ng v`.
- Deprecated camel cased arguments are no longer supported. Ex. using `—-sourceMap` instead of `—-source-map` will result in an error.
- `ng update`, `—-migrate-only` option no longer accepts a string of migration name, instead use `—-migrate-only -—name <migration-name>`.
- `—-help json` help has been removed.

Closes #20976, closes #16614 and closes #16241
2022-03-09 17:18:53 +01:00

213 lines
8.0 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { Schema as ApplicationOptions } from '../application/schema';
import { Schema as WorkspaceOptions } from '../workspace/schema';
import { Schema as AppShellOptions } from './schema';
describe('App Shell Schematic', () => {
const schematicRunner = new SchematicTestRunner(
'@schematics/angular',
require.resolve('../collection.json'),
);
const defaultOptions: AppShellOptions = {
project: 'bar',
};
const workspaceOptions: WorkspaceOptions = {
name: 'workspace',
newProjectRoot: 'projects',
version: '6.0.0',
};
const appOptions: ApplicationOptions = {
name: 'bar',
inlineStyle: false,
inlineTemplate: false,
routing: true,
skipTests: false,
skipPackageJson: false,
};
let appTree: UnitTestTree;
beforeEach(async () => {
appTree = await schematicRunner.runSchematicAsync('workspace', workspaceOptions).toPromise();
appTree = await schematicRunner
.runSchematicAsync('application', appOptions, appTree)
.toPromise();
});
it('should ensure the client app has a router-outlet', async () => {
appTree = await schematicRunner.runSchematicAsync('workspace', workspaceOptions).toPromise();
appTree = await schematicRunner
.runSchematicAsync('application', { ...appOptions, routing: false }, appTree)
.toPromise();
await expectAsync(
schematicRunner.runSchematicAsync('app-shell', defaultOptions, appTree).toPromise(),
).toBeRejected();
});
it('should add a universal app', async () => {
const tree = await schematicRunner
.runSchematicAsync('app-shell', defaultOptions, appTree)
.toPromise();
const filePath = '/projects/bar/src/app/app.server.module.ts';
expect(tree.exists(filePath)).toEqual(true);
});
it('should add app shell configuration', async () => {
const tree = await schematicRunner
.runSchematicAsync('app-shell', defaultOptions, appTree)
.toPromise();
const filePath = '/angular.json';
const content = tree.readContent(filePath);
const workspace = JSON.parse(content);
const target = workspace.projects.bar.architect['app-shell'];
expect(target.options.route).toEqual('shell');
expect(target.configurations.development.browserTarget).toEqual('bar:build:development');
expect(target.configurations.development.serverTarget).toEqual('bar:server:development');
expect(target.configurations.production.browserTarget).toEqual('bar:build:production');
expect(target.configurations.production.serverTarget).toEqual('bar:server:production');
});
it('should add router module to client app module', async () => {
const tree = await schematicRunner
.runSchematicAsync('app-shell', defaultOptions, appTree)
.toPromise();
const filePath = '/projects/bar/src/app/app.module.ts';
const content = tree.readContent(filePath);
expect(content).toMatch(/import { RouterModule } from '@angular\/router';/);
});
it('should not fail when AppModule have imported RouterModule already', async () => {
const updateRecorder = appTree.beginUpdate('/projects/bar/src/app/app.module.ts');
updateRecorder.insertLeft(0, "import { RouterModule } from '@angular/router';");
appTree.commitUpdate(updateRecorder);
const tree = await schematicRunner
.runSchematicAsync('app-shell', defaultOptions, appTree)
.toPromise();
const filePath = '/projects/bar/src/app/app.module.ts';
const content = tree.readContent(filePath);
expect(content).toMatch(/import { RouterModule } from '@angular\/router';/);
});
describe('Add router-outlet', () => {
function makeInlineTemplate(tree: UnitTestTree, template?: string): void {
template =
template ||
`
<p>
App works!
</p>`;
const newText = `
import { Component, OnInit } from '@angular/core';
@Component({
selector: ''
template: \`
${template}
\`,
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
`;
tree.overwrite('/projects/bar/src/app/app.component.ts', newText);
tree.delete('/projects/bar/src/app/app.component.html');
}
it('should not re-add the router outlet (external template)', async () => {
const htmlPath = '/projects/bar/src/app/app.component.html';
appTree.overwrite(htmlPath, '<router-outlet></router-outlet>');
const tree = await schematicRunner
.runSchematicAsync('app-shell', defaultOptions, appTree)
.toPromise();
const content = tree.readContent(htmlPath);
const matches = content.match(/<router-outlet><\/router-outlet>/g);
const numMatches = matches ? matches.length : 0;
expect(numMatches).toEqual(1);
});
it('should not re-add the router outlet (inline template)', async () => {
makeInlineTemplate(appTree, '<router-outlet></router-outlet>');
const tree = await schematicRunner
.runSchematicAsync('app-shell', defaultOptions, appTree)
.toPromise();
const content = tree.readContent('/projects/bar/src/app/app.component.ts');
const matches = content.match(/<router-outlet><\/router-outlet>/g);
const numMatches = matches ? matches.length : 0;
expect(numMatches).toEqual(1);
});
});
it('should add router imports to server module', async () => {
const tree = await schematicRunner
.runSchematicAsync('app-shell', defaultOptions, appTree)
.toPromise();
const filePath = '/projects/bar/src/app/app.server.module.ts';
const content = tree.readContent(filePath);
expect(content).toMatch(/import { Routes, RouterModule } from '@angular\/router';/);
});
it('should work after adding nguniversal', async () => {
let tree = await schematicRunner
.runSchematicAsync('universal', defaultOptions, appTree)
.toPromise();
// change main tsconfig to mimic ng add for nguniveral
const workspace = JSON.parse(appTree.readContent('/angular.json'));
workspace.projects.bar.architect.server.options.main = 'server.ts';
appTree.overwrite('angular.json', JSON.stringify(workspace, undefined, 2));
tree = await schematicRunner.runSchematicAsync('app-shell', defaultOptions, tree).toPromise();
const filePath = '/projects/bar/src/app/app.server.module.ts';
const content = tree.readContent(filePath);
expect(content).toMatch(/import { Routes, RouterModule } from '@angular\/router';/);
});
it('should define a server route', async () => {
const tree = await schematicRunner
.runSchematicAsync('app-shell', defaultOptions, appTree)
.toPromise();
const filePath = '/projects/bar/src/app/app.server.module.ts';
const content = tree.readContent(filePath);
expect(content).toMatch(/const routes: Routes = \[/);
});
it('should import RouterModule with forRoot', async () => {
const tree = await schematicRunner
.runSchematicAsync('app-shell', defaultOptions, appTree)
.toPromise();
const filePath = '/projects/bar/src/app/app.server.module.ts';
const content = tree.readContent(filePath);
expect(content).toMatch(
/const routes: Routes = \[ { path: 'shell', component: AppShellComponent }\];/,
);
expect(content).toMatch(/ServerModule,\r?\n\s*RouterModule\.forRoot\(routes\),/);
});
it('should create the shell component', async () => {
const tree = await schematicRunner
.runSchematicAsync('app-shell', defaultOptions, appTree)
.toPromise();
expect(tree.exists('/projects/bar/src/app/app-shell/app-shell.component.ts')).toBe(true);
const content = tree.readContent('/projects/bar/src/app/app.server.module.ts');
expect(content).toMatch(/app-shell\.component/);
});
});