diff --git a/.prettierignore b/.prettierignore index a6de2f2967..ade4f81136 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,3 +6,4 @@ node_modules packages/schematics/src/collection/**/files/*.json /.vscode /.idea +/.github diff --git a/e2e/schematics/ng-new.test.ts b/e2e/schematics/ng-new.test.ts index c1784784a2..a8e5fb8ae5 100644 --- a/e2e/schematics/ng-new.test.ts +++ b/e2e/schematics/ng-new.test.ts @@ -19,7 +19,7 @@ import { import { toClassName } from '@nrwl/schematics/src/utils/name-utils'; describe('Nrwl Workspace', () => { - fit('should work', async () => { + it('should work', async () => { ensureProject(); const myapp = uniq('myapp'); const mylib = uniq('mylib'); diff --git a/e2e/schematics/node.test.ts b/e2e/schematics/node.test.ts index 3ef880d0e4..376734e4d1 100644 --- a/e2e/schematics/node.test.ts +++ b/e2e/schematics/node.test.ts @@ -14,7 +14,7 @@ import * as http from 'http'; import * as path from 'path'; import * as treeKill from 'tree-kill'; -function getData() { +function getData(): Promise { return new Promise(resolve => { http.get('http://localhost:3333/api', res => { expect(res.statusCode).toEqual(200); @@ -23,7 +23,7 @@ function getData() { data += chunk; }); res.once('end', () => { - resolve(data); + resolve(JSON.parse(data)); }); }); }); @@ -77,7 +77,7 @@ describe('Node Applications', () => { expect(data.toString()).toContain('Listening at http://localhost:3333'); const result = await getData(); - expect(result).toEqual(`Welcome to ${nodeapp}!`); + expect(result.message).toEqual(`Welcome to ${nodeapp}!`); treeKill(server.pid, 'SIGTERM', err => { expect(err).toBeFalsy(); resolve(); @@ -118,7 +118,7 @@ describe('Node Applications', () => { } const result = await getData(); - expect(result).toEqual(`Welcome to ${nodeapp}!`); + expect(result.message).toEqual(`Welcome to ${nodeapp}!`); treeKill(process.pid, 'SIGTERM', err => { expect(collectedOutput.startsWith('DONE')).toBeTruthy(); expect(err).toBeFalsy(); @@ -164,7 +164,7 @@ describe('Node Applications', () => { if (message.includes('Listening at http://localhost:3333')) { const result = await getData(); - expect(result).toEqual(`Welcome to ${nestapp}!`); + expect(result.message).toEqual(`Welcome to ${nestapp}!`); treeKill(server.pid, 'SIGTERM', err => { expect(err).toBeFalsy(); resolve(); @@ -186,7 +186,7 @@ describe('Node Applications', () => { return; } const result = await getData(); - expect(result).toEqual(`Welcome to ${nestapp}!`); + expect(result.message).toEqual(`Welcome to ${nestapp}!`); treeKill(process.pid, 'SIGTERM', err => { expect(err).toBeFalsy(); done(); diff --git a/packages/schematics/package.json b/packages/schematics/package.json index 53105df18d..6a4258a26d 100644 --- a/packages/schematics/package.json +++ b/packages/schematics/package.json @@ -51,6 +51,7 @@ "tmp": "0.0.33", "viz.js": "^1.8.1", "yargs-parser": "10.0.0", - "yargs": "^11.0.0" + "yargs": "^11.0.0", + "prettier": "1.15.3" } } diff --git a/packages/schematics/src/collection.json b/packages/schematics/src/collection.json index c8a36090fd..26d6d9a78f 100644 --- a/packages/schematics/src/collection.json +++ b/packages/schematics/src/collection.json @@ -13,7 +13,14 @@ "ng-new": { "factory": "./collection/ng-new", "schema": "./collection/ng-new/schema.json", - "description": "Create an empty workspace" + "description": "Create a workspace" + }, + + "workspace": { + "factory": "./collection/workspace", + "schema": "./collection/workspace/schema.json", + "description": "Create an empty workspace", + "hidden": true }, "application": { diff --git a/packages/schematics/src/collection/ng-new/index.ts b/packages/schematics/src/collection/ng-new/index.ts index 74efe74e8c..0bd41b6f3f 100644 --- a/packages/schematics/src/collection/ng-new/index.ts +++ b/packages/schematics/src/collection/ng-new/index.ts @@ -1,73 +1,217 @@ import { - apply, - branchAndMerge, chain, - mergeWith, + move, + noop, Rule, + schematic, SchematicContext, - template, - Tree, - url + Tree } from '@angular-devkit/schematics'; import { Schema } from './schema'; -import { strings } from '@angular-devkit/core'; +import { addImportToModule, insert } from '../../utils/ast-utils'; +import * as ts from 'typescript'; +import { insertImport } from '@schematics/angular/utility/ast-utils'; import { NodePackageInstallTask, RepositoryInitializerTask } from '@angular-devkit/schematics/tasks'; -import { libVersions } from '../../lib-versions'; -import { DEFAULT_NRWL_PRETTIER_CONFIG } from '../../utils/common'; export default function(options: Schema): Rule { - if (!options.name) { - throw new Error(`Invalid options, "name" is required.`); - } if (!options.directory) { options.directory = options.name; } + const workspaceOpts = { ...options, preset: undefined }; return (host: Tree, context: SchematicContext) => { - addTasks(options, context); - const npmScope = options.npmScope ? options.npmScope : options.name; - const templateSource = apply(url('./files'), [ - template({ - utils: strings, - dot: '.', - tmpl: '', - ...libVersions, - ...(options as object), - npmScope, - defaultNrwlPrettierConfig: JSON.stringify( - DEFAULT_NRWL_PRETTIER_CONFIG, - null, - 2 - ) - }) + return chain([ + schematic('workspace', workspaceOpts), + createPreset(options), + move('/', options.directory), + addTasks(options) + ])(Tree.empty(), context); + }; +} + +function createPreset(options: Schema): Rule { + if (options.preset === 'empty') { + return noop(); + } else if (options.preset === 'angular') { + return chain([ + schematic( + 'application', + { name: options.name, style: options.style }, + { interactive: false } + ) ]); - return chain([branchAndMerge(chain([mergeWith(templateSource)]))])( - host, - context + } else { + return chain([ + schematic( + 'application', + { name: options.name, style: options.style }, + { interactive: false } + ), + schematic( + 'node-application', + { + name: 'api', + frontendProject: options.name + }, + { interactive: false } + ), + schematic( + 'library', + { name: 'api-interface', framework: 'none' }, + { interactive: false } + ), + connectFrontendAndApi(options) + ]); + } +} + +function connectFrontendAndApi(options: Schema) { + return (host: Tree) => { + host.create( + 'libs/api-interface/src/lib/interfaces.ts', + `export interface Message { message: string }` + ); + host.overwrite( + 'libs/api-interface/src/index.ts', + `export * from './lib/interfaces';` + ); + + const modulePath = `apps/${options.name}/src/app/app.module.ts`; + const moduleFile = ts.createSourceFile( + modulePath, + host.read(modulePath).toString(), + ts.ScriptTarget.Latest, + true + ); + insert(host, modulePath, [ + insertImport( + moduleFile, + modulePath, + 'HttpClientModule', + `@angular/common/http` + ), + ...addImportToModule( + moduleFile, + `@angular/common/http`, + `HttpClientModule` + ) + ]); + + const scope = options.npmScope ? options.npmScope : options.name; + const style = options.style ? options.style : 'css'; + host.overwrite( + `apps/${options.name}/src/app/app.component.ts`, + `import { Component } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Message } from '@${scope}/api-interface'; + +@Component({ + selector: '${scope}-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.${style}'] +}) +export class AppComponent { + hello$ = this.http.get('/api/hello') + constructor(private http: HttpClient) {} +} + ` + ); + + host.overwrite( + `apps/${options.name}/src/app/app.component.spec.ts`, + `import { Component } from '@angular/core'; +import { TestBed, async } from '@angular/core/testing'; +import { HttpClientModule } from '@angular/common/http'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [AppComponent], + imports: [HttpClientModule] + }).compileComponents(); + })); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + }); +}); + ` + ); + + host.overwrite( + `apps/${options.name}/src/app/app.component.html`, + `
+

Welcome to ${options.name}!

+ +
+
Message: {{ (hello$|async)|json }}
+ ` + ); + + host.overwrite( + `apps/api/src/app/app.controller.ts`, + `import { Controller, Get } from '@nestjs/common'; + +import { Message } from '@${scope}/api-interface'; + +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get('hello') + getData(): Message { + return this.appService.getData(); + } +} + ` + ); + + host.overwrite( + `apps/api/src/app/app.service.ts`, + `import { Injectable } from '@nestjs/common'; +import { Message } from '@${scope}/api-interface'; + +@Injectable() +export class AppService { + getData(): Message { + return { message: 'Welcome to api!' }; + } +} + ` ); }; } -function addTasks(options: Schema, context: SchematicContext) { - let packageTask; - if (!options.skipInstall) { - packageTask = context.addTask( - new NodePackageInstallTask(options.directory) - ); - } - if (!options.skipGit) { - const commit = - typeof options.commit == 'object' - ? options.commit - : !!options.commit - ? {} - : false; - context.addTask( - new RepositoryInitializerTask(options.directory, commit), - packageTask ? [packageTask] : [] - ); - } +function addTasks(options: Schema) { + return (host: Tree, context: SchematicContext) => { + let packageTask; + if (!options.skipInstall) { + packageTask = context.addTask( + new NodePackageInstallTask(options.directory) + ); + } + if (!options.skipGit) { + const commit = + typeof options.commit == 'object' + ? options.commit + : !!options.commit + ? {} + : false; + context.addTask( + new RepositoryInitializerTask(options.directory, commit), + packageTask ? [packageTask] : [] + ); + } + }; } diff --git a/packages/schematics/src/collection/ng-new/ng-new.spec.ts b/packages/schematics/src/collection/ng-new/ng-new.spec.ts index d14141184d..78574f01ce 100644 --- a/packages/schematics/src/collection/ng-new/ng-new.spec.ts +++ b/packages/schematics/src/collection/ng-new/ng-new.spec.ts @@ -1,10 +1,8 @@ import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; import * as path from 'path'; import { Tree } from '@angular-devkit/schematics'; -import { readJsonInTree } from '../../utils/ast-utils'; -import { NxJson } from '../../command-line/shared'; -describe('app', () => { +describe('ng-new', () => { const schematicRunner = new SchematicTestRunner( '@nrwl/schematics', path.join(__dirname, '../../collection.json') @@ -16,80 +14,29 @@ describe('app', () => { projectTree = Tree.empty(); }); - it('should update angular.json', () => { - const tree = schematicRunner.runSchematic( - 'ng-new', - { name: 'proj' }, - projectTree - ); + it('should create files (preset = angular)', async () => { + const tree = await schematicRunner + .runSchematicAsync( + 'ng-new', + { name: 'proj', preset: 'angular' }, + projectTree + ) + .toPromise(); + expect(tree.exists('/proj/apps/proj/src/app/app.component.ts')).toBe(true); }); - it('should create files', () => { - const tree = schematicRunner.runSchematic( - 'ng-new', - { name: 'proj' }, - projectTree + it('should create files (preset = fullstack)', async () => { + const tree = await schematicRunner + .runSchematicAsync( + 'ng-new', + { name: 'proj', preset: 'fullstack' }, + projectTree + ) + .toPromise(); + expect(tree.exists('/proj/apps/proj/src/app/app.component.ts')).toBe(true); + expect(tree.exists('/proj/apps/api/src/app/app.controller.ts')).toBe(true); + expect(tree.exists('/proj/libs/api-interface/src/lib/interfaces.ts')).toBe( + true ); - expect(tree.exists('/proj/nx.json')).toBe(true); - expect(tree.exists('/proj/angular.json')).toBe(true); - expect(tree.exists('/proj/.prettierrc')).toBe(true); - expect(tree.exists('/proj/.prettierignore')).toBe(true); - }); - - it('should create nx.json', () => { - const tree = schematicRunner.runSchematic( - 'ng-new', - { name: 'proj' }, - projectTree - ); - const nxJson = readJsonInTree(tree, '/proj/nx.json'); - expect(nxJson).toEqual({ - npmScope: 'proj', - implicitDependencies: { - 'angular.json': '*', - 'package.json': '*', - 'tsconfig.json': '*', - 'tslint.json': '*', - 'nx.json': '*' - }, - projects: {} - }); - }); - - it('should recommend vscode extensions', () => { - const tree = schematicRunner.runSchematic( - 'ng-new', - { name: 'proj' }, - projectTree - ); - const recommendations = readJsonInTree<{ recommendations: string[] }>( - tree, - '/proj/.vscode/extensions.json' - ).recommendations; - - expect(recommendations).toEqual([ - 'nrwl.angular-console', - 'angular.ng-template', - 'ms-vscode.vscode-typescript-tslint-plugin', - 'esbenp.prettier-vscode' - ]); - }); - - it('should configure the project to use style argument', () => { - const tree = schematicRunner.runSchematic( - 'ng-new', - { name: 'proj', style: 'scss' }, - projectTree - ); - expect( - JSON.parse(tree.readContent('/proj/angular.json')).schematics - ).toEqual({ - '@nrwl/schematics:application': { - style: 'scss' - }, - '@nrwl/schematics:library': { - style: 'scss' - } - }); }); }); diff --git a/packages/schematics/src/collection/ng-new/schema.d.ts b/packages/schematics/src/collection/ng-new/schema.d.ts index 89f184af90..dd33b4f399 100644 --- a/packages/schematics/src/collection/ng-new/schema.d.ts +++ b/packages/schematics/src/collection/ng-new/schema.d.ts @@ -1,5 +1,3 @@ -import { UnitTestRunner } from '../../utils/test-runners'; - export interface Schema { directory: string; name: string; @@ -7,5 +5,6 @@ export interface Schema { skipInstall?: boolean; skipGit?: boolean; style?: string; + preset: 'empty' | 'angular' | 'fullstack'; commit?: { name: string; email: string; message?: string }; } diff --git a/packages/schematics/src/collection/ng-new/schema.json b/packages/schematics/src/collection/ng-new/schema.json index cd07589d85..96d62a6746 100644 --- a/packages/schematics/src/collection/ng-new/schema.json +++ b/packages/schematics/src/collection/ng-new/schema.json @@ -74,6 +74,29 @@ } ], "default": true + }, + "preset": { + "description": "What to create in the new workspace", + "enum": ["empty", "angular", "fullstack"], + "default": "empty", + "x-prompt": { + "message": "What to create in the new workspace (You can create other applications and libraries at any point using 'ng g')", + "type": "list", + "items": [ + { + "value": "empty", + "label": "empty [an empty workspace]" + }, + { + "value": "angular", + "label": "angular [a workspace with a single Angular application]" + }, + { + "value": "fullstack", + "label": "full-stack [a workspace with a full stack application (NestJS + Angular)]" + } + ] + } } } } diff --git a/packages/schematics/src/collection/node-application/files/express/main.ts__tmpl__ b/packages/schematics/src/collection/node-application/files/express/main.ts__tmpl__ index c7ab20b76e..64912c0e35 100644 --- a/packages/schematics/src/collection/node-application/files/express/main.ts__tmpl__ +++ b/packages/schematics/src/collection/node-application/files/express/main.ts__tmpl__ @@ -8,7 +8,7 @@ import * as express from 'express'; const app = express(); app.get('/api', (req, res) => { - res.send(`Welcome to <%= name %>!`); + res.send({message: `Welcome to <%= name %>!`}); }); const port = process.env.port || 3333; diff --git a/packages/schematics/src/collection/node-application/files/nestjs/app/app.controller.spec.ts__tmpl__ b/packages/schematics/src/collection/node-application/files/nestjs/app/app.controller.spec.ts__tmpl__ index bf41628283..a99a57355c 100644 --- a/packages/schematics/src/collection/node-application/files/nestjs/app/app.controller.spec.ts__tmpl__ +++ b/packages/schematics/src/collection/node-application/files/nestjs/app/app.controller.spec.ts__tmpl__ @@ -16,7 +16,7 @@ describe('AppController', () => { describe('getData', () => { it('should return "Welcome to <%= name %>!"', () => { const appController = app.get(AppController); - expect(appController.getData()).toBe('Welcome to <%= name %>!'); + expect(appController.getData()).toEqual({message: 'Welcome to <%= name %>!'}); }); }); }); diff --git a/packages/schematics/src/collection/node-application/files/nestjs/app/app.controller.ts__tmpl__ b/packages/schematics/src/collection/node-application/files/nestjs/app/app.controller.ts__tmpl__ index 90180fccf4..dff210a841 100644 --- a/packages/schematics/src/collection/node-application/files/nestjs/app/app.controller.ts__tmpl__ +++ b/packages/schematics/src/collection/node-application/files/nestjs/app/app.controller.ts__tmpl__ @@ -7,7 +7,7 @@ export class AppController { constructor(private readonly appService: AppService) {} @Get() - getData(): string { + getData() { return this.appService.getData(); } } diff --git a/packages/schematics/src/collection/node-application/files/nestjs/app/app.service.spec.ts__tmpl__ b/packages/schematics/src/collection/node-application/files/nestjs/app/app.service.spec.ts__tmpl__ index cbb841361b..c35c26984b 100644 --- a/packages/schematics/src/collection/node-application/files/nestjs/app/app.service.spec.ts__tmpl__ +++ b/packages/schematics/src/collection/node-application/files/nestjs/app/app.service.spec.ts__tmpl__ @@ -15,7 +15,7 @@ describe('AppService', () => { describe('getData', () => { it('should return "Welcome to <%= name %>!"', () => { - expect(service.getData()).toBe('Welcome to <%= name %>!'); + expect(service.getData()).toEqual({message: 'Welcome to <%= name %>!'}); }); }); }); diff --git a/packages/schematics/src/collection/node-application/files/nestjs/app/app.service.ts__tmpl__ b/packages/schematics/src/collection/node-application/files/nestjs/app/app.service.ts__tmpl__ index ec8eba6020..1f5dc013bc 100644 --- a/packages/schematics/src/collection/node-application/files/nestjs/app/app.service.ts__tmpl__ +++ b/packages/schematics/src/collection/node-application/files/nestjs/app/app.service.ts__tmpl__ @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { - getData(): string { - return 'Welcome to <%= name %>!'; + getData(): { message: string } { + return ({ message: 'Welcome to <%= name %>!' }); } } diff --git a/packages/schematics/src/collection/node-application/node-application.spec.ts b/packages/schematics/src/collection/node-application/node-application.spec.ts index f104cbf2b0..9ff517e8d4 100644 --- a/packages/schematics/src/collection/node-application/node-application.spec.ts +++ b/packages/schematics/src/collection/node-application/node-application.spec.ts @@ -94,7 +94,7 @@ describe('node-app', () => { 'const app = express();' ); expect(tree.readContent('apps/my-node-app/src/main.ts')).toContain( - 'res.send(`Welcome to my-node-app!`);' + 'res.send({message: `Welcome to my-node-app!`});' ); const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/.vscode/extensions.json b/packages/schematics/src/collection/workspace/files/.vscode/extensions.json similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/.vscode/extensions.json rename to packages/schematics/src/collection/workspace/files/.vscode/extensions.json diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/README.md b/packages/schematics/src/collection/workspace/files/README.md similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/README.md rename to packages/schematics/src/collection/workspace/files/README.md diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/__dot__editorconfig b/packages/schematics/src/collection/workspace/files/__dot__editorconfig similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/__dot__editorconfig rename to packages/schematics/src/collection/workspace/files/__dot__editorconfig diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/__dot__gitignore b/packages/schematics/src/collection/workspace/files/__dot__gitignore similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/__dot__gitignore rename to packages/schematics/src/collection/workspace/files/__dot__gitignore diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/.prettierignore b/packages/schematics/src/collection/workspace/files/__dot__prettierignore similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/.prettierignore rename to packages/schematics/src/collection/workspace/files/__dot__prettierignore diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/.prettierrc b/packages/schematics/src/collection/workspace/files/__dot__prettierrc similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/.prettierrc rename to packages/schematics/src/collection/workspace/files/__dot__prettierrc diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/angular.json b/packages/schematics/src/collection/workspace/files/angular.json similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/angular.json rename to packages/schematics/src/collection/workspace/files/angular.json diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/apps/.gitkeep b/packages/schematics/src/collection/workspace/files/apps/.gitkeep similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/apps/.gitkeep rename to packages/schematics/src/collection/workspace/files/apps/.gitkeep diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/libs/.gitkeep b/packages/schematics/src/collection/workspace/files/libs/.gitkeep similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/libs/.gitkeep rename to packages/schematics/src/collection/workspace/files/libs/.gitkeep diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/nx.json b/packages/schematics/src/collection/workspace/files/nx.json similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/nx.json rename to packages/schematics/src/collection/workspace/files/nx.json diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/package.json b/packages/schematics/src/collection/workspace/files/package.json old mode 100755 new mode 100644 similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/package.json rename to packages/schematics/src/collection/workspace/files/package.json diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/tools/schematics/.gitkeep b/packages/schematics/src/collection/workspace/files/tools/schematics/.gitkeep similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/tools/schematics/.gitkeep rename to packages/schematics/src/collection/workspace/files/tools/schematics/.gitkeep diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/tools/tsconfig.tools.json b/packages/schematics/src/collection/workspace/files/tools/tsconfig.tools.json similarity index 71% rename from packages/schematics/src/collection/ng-new/files/__directory__/tools/tsconfig.tools.json rename to packages/schematics/src/collection/workspace/files/tools/tsconfig.tools.json index c74be19b06..82bd1f098d 100644 --- a/packages/schematics/src/collection/ng-new/files/__directory__/tools/tsconfig.tools.json +++ b/packages/schematics/src/collection/workspace/files/tools/tsconfig.tools.json @@ -5,11 +5,7 @@ "rootDir": ".", "module": "commonjs", "target": "es5", - "types": [ - "node" - ] + "types": ["node"] }, - "include": [ - "**/*.ts" - ] + "include": ["**/*.ts"] } diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/tsconfig.json b/packages/schematics/src/collection/workspace/files/tsconfig.json similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/tsconfig.json rename to packages/schematics/src/collection/workspace/files/tsconfig.json diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/tslint.json b/packages/schematics/src/collection/workspace/files/tslint.json similarity index 100% rename from packages/schematics/src/collection/ng-new/files/__directory__/tslint.json rename to packages/schematics/src/collection/workspace/files/tslint.json diff --git a/packages/schematics/src/collection/workspace/index.ts b/packages/schematics/src/collection/workspace/index.ts new file mode 100644 index 0000000000..eccd47925b --- /dev/null +++ b/packages/schematics/src/collection/workspace/index.ts @@ -0,0 +1,44 @@ +import { + apply, + branchAndMerge, + chain, + mergeWith, + Rule, + SchematicContext, + template, + Tree, + url +} from '@angular-devkit/schematics'; +import { Schema } from './schema'; +import { strings } from '@angular-devkit/core'; +import { libVersions } from '../../lib-versions'; +import { DEFAULT_NRWL_PRETTIER_CONFIG } from '../../utils/common'; + +export default function(options: Schema): Rule { + if (!options.name) { + throw new Error(`Invalid options, "name" is required.`); + } + + return (host: Tree, context: SchematicContext) => { + const npmScope = options.npmScope ? options.npmScope : options.name; + const templateSource = apply(url('./files'), [ + template({ + utils: strings, + dot: '.', + tmpl: '', + ...libVersions, + ...(options as object), + npmScope, + defaultNrwlPrettierConfig: JSON.stringify( + DEFAULT_NRWL_PRETTIER_CONFIG, + null, + 2 + ) + }) + ]); + return chain([branchAndMerge(chain([mergeWith(templateSource)]))])( + host, + context + ); + }; +} diff --git a/packages/schematics/src/collection/workspace/schema.d.ts b/packages/schematics/src/collection/workspace/schema.d.ts new file mode 100644 index 0000000000..89f184af90 --- /dev/null +++ b/packages/schematics/src/collection/workspace/schema.d.ts @@ -0,0 +1,11 @@ +import { UnitTestRunner } from '../../utils/test-runners'; + +export interface Schema { + directory: string; + name: string; + npmScope?: string; + skipInstall?: boolean; + skipGit?: boolean; + style?: string; + commit?: { name: string; email: string; message?: string }; +} diff --git a/packages/schematics/src/collection/workspace/schema.json b/packages/schematics/src/collection/workspace/schema.json new file mode 100644 index 0000000000..cd07589d85 --- /dev/null +++ b/packages/schematics/src/collection/workspace/schema.json @@ -0,0 +1,79 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "SchematicsNxNgNew", + "title": "Create an empty workspace", + "type": "object", + "properties": { + "name": { + "description": "The name of the workspace.", + "type": "string", + "format": "html-selector", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-prompt": "What name would you like to use for the workspace?" + }, + "style": { + "description": "The file extension to be used for style files.", + "type": "string", + "default": "css", + "x-prompt": { + "message": "Which stylesheet format would you like to use?", + "type": "list", + "items": [ + { "value": "css", "label": "CSS" }, + { "value": "scss", "label": "SCSS [ http://sass-lang.com ]" }, + { "value": "sass", "label": "SASS [ http://sass-lang.com ]" }, + { "value": "less", "label": "LESS [ http://lesscss.org ]" }, + { "value": "styl", "label": "Stylus [ http://stylus-lang.com ]" } + ] + } + }, + "directory": { + "type": "string", + "format": "path", + "description": "The directory name to create the workspace in.", + "default": "" + }, + "npmScope": { + "type": "string", + "description": "Npm scope for importing libs.", + "x-prompt": "What is the npm scope you would like to use for your Nx Workspace?" + }, + "skipInstall": { + "description": "Skip installing dependency packages.", + "type": "boolean", + "default": false + }, + "skipGit": { + "description": "Skip initializing a git repository.", + "type": "boolean", + "default": false, + "alias": "g" + }, + "commit": { + "description": "Initial repository commit information.", + "oneOf": [ + { "type": "boolean" }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "message": { + "type": "string" + } + }, + "required": ["name", "email"] + } + ], + "default": true + } + } +} diff --git a/packages/schematics/src/collection/workspace/workspace.spec.ts b/packages/schematics/src/collection/workspace/workspace.spec.ts new file mode 100644 index 0000000000..9ae4844bd6 --- /dev/null +++ b/packages/schematics/src/collection/workspace/workspace.spec.ts @@ -0,0 +1,93 @@ +import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; +import * as path from 'path'; +import { Tree } from '@angular-devkit/schematics'; +import { readJsonInTree } from '../../utils/ast-utils'; +import { NxJson } from '../../command-line/shared'; + +describe('workspace', () => { + const schematicRunner = new SchematicTestRunner( + '@nrwl/schematics', + path.join(__dirname, '../../collection.json') + ); + + let projectTree: Tree; + + beforeEach(() => { + projectTree = Tree.empty(); + }); + + it('should update angular.json', () => { + const tree = schematicRunner.runSchematic( + 'workspace', + { name: 'proj' }, + projectTree + ); + }); + + it('should create files', () => { + const tree = schematicRunner.runSchematic( + 'workspace', + { name: 'proj' }, + projectTree + ); + expect(tree.exists('/nx.json')).toBe(true); + expect(tree.exists('/angular.json')).toBe(true); + expect(tree.exists('/.prettierrc')).toBe(true); + expect(tree.exists('/.prettierignore')).toBe(true); + }); + + it('should create nx.json', () => { + const tree = schematicRunner.runSchematic( + 'workspace', + { name: 'proj' }, + projectTree + ); + const nxJson = readJsonInTree(tree, '/nx.json'); + expect(nxJson).toEqual({ + npmScope: 'proj', + implicitDependencies: { + 'angular.json': '*', + 'package.json': '*', + 'tsconfig.json': '*', + 'tslint.json': '*', + 'nx.json': '*' + }, + projects: {} + }); + }); + + it('should recommend vscode extensions', () => { + const tree = schematicRunner.runSchematic( + 'workspace', + { name: 'proj' }, + projectTree + ); + const recommendations = readJsonInTree<{ recommendations: string[] }>( + tree, + '/.vscode/extensions.json' + ).recommendations; + + expect(recommendations).toEqual([ + 'nrwl.angular-console', + 'angular.ng-template', + 'ms-vscode.vscode-typescript-tslint-plugin', + 'esbenp.prettier-vscode' + ]); + }); + + it('should configure the project to use style argument', () => { + const tree = schematicRunner.runSchematic( + 'workspace', + { name: 'proj', style: 'scss' }, + projectTree + ); + expect(JSON.parse(tree.readContent('/angular.json')).schematics).toEqual({ + '@nrwl/schematics:application': { + style: 'scss' + }, + '@nrwl/schematics:library': { + style: 'scss' + } + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 749684cacc..aa53481742 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@angular-devkit/architect@0.13.1", "@angular-devkit/architect@~0.13.1": +"@angular-devkit/architect@0.13.1": version "0.13.1" resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.13.1.tgz#39597ce94f72d89bdd89ee567cb937cff4c13b98" integrity sha512-QDmIbqde75ZZSEFbw6Q6kQWq4cY6C7D67yujXw6XTyubDNAs1tyXJyxTIB8vjSlEKwRizTTDd/B0ZXVcke3Mvw== @@ -10,7 +10,7 @@ "@angular-devkit/core" "7.3.1" rxjs "6.3.3" -"@angular-devkit/build-angular@~0.13.1": +"@angular-devkit/build-angular@0.13.1": version "0.13.1" resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.13.1.tgz#369febda48dd40e47a4f0077064e792612a8e1c1" integrity sha512-vkKwMVQ+NNCcVR3HFMffS+Mq4b2afXeUjI+02N38hBuFTppnC83uivUB6Uu2NUk5NTSQA4BnJlG5CbMs6N4QYg== @@ -62,7 +62,7 @@ optionalDependencies: node-sass "4.11.0" -"@angular-devkit/build-ng-packagr@^0.13.1": +"@angular-devkit/build-ng-packagr@0.13.1": version "0.13.1" resolved "https://registry.yarnpkg.com/@angular-devkit/build-ng-packagr/-/build-ng-packagr-0.13.1.tgz#3508523a039f71ccff1364db553a904e4db2c8ea" integrity sha512-9qvdNvtlgJ3WDppbzwD9fOQzAsVogBlDeLE5zUH1ap+zcoyZEGjS1BKluiYSJ1u5Q5Nlfb3FSI/D1r9LuDQS/A== @@ -82,7 +82,7 @@ typescript "3.2.4" webpack-sources "1.3.0" -"@angular-devkit/build-webpack@0.13.1", "@angular-devkit/build-webpack@~0.13.1": +"@angular-devkit/build-webpack@0.13.1": version "0.13.1" resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.13.1.tgz#98d666765705e9379c9b2e0a3b6dfcd0347a2a32" integrity sha512-OGwC7bAl3u+w7Glw+OqIrN7OD1BkDXgrWbeQSpKAmsx6VdNPCnI4NPS+JldWNp70LVlE2nQlJUhtEqMVfBMnlg== @@ -102,10 +102,10 @@ rxjs "6.3.3" source-map "0.7.3" -"@angular-devkit/core@7.2.4": - version "7.2.4" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-7.2.4.tgz#4464d536f4838e5c61ab4bc1ff6b7f221fd43056" - integrity sha512-XHF59tIHg2qEM1Wd415xhykBLjjfOK6yMB7CjNk1bToUMX2QDT87izJF4y1Vwa0lIw9G0jdgP/4/M/OqXcbYmA== +"@angular-devkit/core@7.2.2": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-7.2.2.tgz#f0daf3e24f0ce8105341118f4505c1db4e284ab0" + integrity sha512-gDF8iXiPN870WuBMl7bCQ5+Qz5SjGL/qMcvP4hli5hkn+kMAhgG38ligUK1bbhPQUJ+Z/nSOEmyv8gLHO09SZg== dependencies: ajv "6.6.2" chokidar "2.0.4" @@ -132,12 +132,12 @@ "@angular-devkit/core" "7.1.2" rxjs "6.3.3" -"@angular-devkit/schematics@7.2.4": - version "7.2.4" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-7.2.4.tgz#d92b2b8b9114135806d593ea6a2b376c777354dc" - integrity sha512-ObIDnIxXRpts+Jzs0PQ7JVuK4d5vWEh9K+Ow8nMO5/LmYJQ8/2nMEQo/9lhdKPMiXmhbuvB7qZL5J+cxwwijhw== +"@angular-devkit/schematics@7.2.2": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-7.2.2.tgz#d8d667684603e1debcc4598d88a254560e787f87" + integrity sha512-3qONTeqe+bUJ967PNDeITuD4F+3huTEs3u89zZLV+yvaxoK9XJlvaRoQXAkNAMUkij37BoFrGgBfGNijserd6A== dependencies: - "@angular-devkit/core" "7.2.4" + "@angular-devkit/core" "7.2.2" rxjs "6.3.3" "@angular-devkit/schematics@7.3.1", "@angular-devkit/schematics@~7.3.1": @@ -148,7 +148,7 @@ "@angular-devkit/core" "7.3.1" rxjs "6.3.3" -"@angular/cli@~7.3.1": +"@angular/cli@7.3.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-7.3.1.tgz#a18acdec84deb03a1fae79cae415bbc8f9c87ffa" integrity sha512-8EvXYRhTqTaTk5PKv7VZxIWJiyG51R9RC9gtpRFx4bbnurqBHdEUxGMmaRsGT8QDbfvVsWnuakE0eeW1CrfZAQ== @@ -461,6 +461,15 @@ universal-user-agent "^2.0.0" url-template "^2.0.8" +"@schematics/angular@7.2.2": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-7.2.2.tgz#5a466ebbbd7e1fbb13851f26446ec308b822d1dc" + integrity sha512-Yonddct1XBG1H5rTikagFTIT2/RhszJnNa2Iz+rvc26ffAl1mmYPB4sQb7gkOaZQSzK6SE7bT2QW32PVjYFoSQ== + dependencies: + "@angular-devkit/core" "7.2.2" + "@angular-devkit/schematics" "7.2.2" + typescript "3.2.2" + "@schematics/angular@7.3.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-7.3.1.tgz#6fcd7004210fa9305310c3109c084df5c5521776" @@ -470,15 +479,6 @@ "@angular-devkit/schematics" "7.3.1" typescript "3.2.4" -"@schematics/angular@~7.2.2": - version "7.2.4" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-7.2.4.tgz#4c3c942e2fd0c32e177602a20f8fa16a88552fca" - integrity sha512-aflQwIX4E9tDhp6ZASuQCm8CzxLxdkuOe6qN1FbCxpxMUc9E+iK9jhOjw+Xnl3boJpWHAA+k9JO1sYe3wrh3Ng== - dependencies: - "@angular-devkit/core" "7.2.4" - "@angular-devkit/schematics" "7.2.4" - typescript "3.2.2" - "@schematics/update@0.13.1": version "0.13.1" resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.13.1.tgz#481475aee18b4a9472a06512b2e4d6429af68231" @@ -3650,7 +3650,7 @@ cyclist@~0.2.2: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= -cypress@^3.1.0: +cypress@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.1.0.tgz#b718ba64289b887c7ab7a7f09245d871a4a409ba" integrity sha512-UqLbXgHvM8Y6Y+roHrepZMWcyMN5u4KcjpTbJTZi0d5O2Prvtqmnpoky7a4C65q4oRQXeSc6cBZUhxJkhU4pbQ== @@ -12090,7 +12090,7 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@3.2.2, typescript@~3.2.2: +typescript@3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5" integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==