diff --git a/.gitignore b/.gitignore index 118dc126a9..673eaa7ad2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ node_modules .idea -.vscode +/.vscode dist /build test diff --git a/e2e/schematics/ng-add.test.ts b/e2e/schematics/ng-add.test.ts index ad20490cb2..f1d0a11cf5 100644 --- a/e2e/schematics/ng-add.test.ts +++ b/e2e/schematics/ng-add.test.ts @@ -54,11 +54,18 @@ describe('Nrwl Convert to Nx Workspace', () => { // check that prettier config exits and that files have been moved! checkFilesExist( + '.vscode/extensions.json', '.prettierrc', 'apps/proj/src/main.ts', 'apps/proj/src/app/app.module.ts' ); + expect(readJson('.vscode/extensions.json').recommendations).toEqual([ + 'nrwl.angular-console', + 'angular.ng-template', + 'esbenp.prettier-vscode' + ]); + const appModuleContents = readFile('apps/proj/src/app/app.module.ts'); expect(appModuleContents).toContain(`import { NxModule } from '@nrwl/nx';`); expect(appModuleContents).toContain(`NxModule.forRoot()`); @@ -247,7 +254,7 @@ describe('Nrwl Convert to Nx Workspace', () => { checkFilesExist('dist/apps/proj/main.js'); }); - it('should generate a workspace and not change dependencies or devDependencies if they already exist', () => { + it('should generate a workspace and not change dependencies, devDependencies, or vscode extensions if they already exist', () => { // create a new AngularCLI app runNgNew(); const nxVersion = '0.0.0'; @@ -262,6 +269,13 @@ describe('Nrwl Convert to Nx Workspace', () => { existingPackageJson.dependencies['@ngrx/router-store'] = ngrxVersion; existingPackageJson.devDependencies['@ngrx/store-devtools'] = ngrxVersion; updateFile('package.json', JSON.stringify(existingPackageJson, null, 2)); + + updateFile( + '.vscode/extensions.json', + JSON.stringify({ + recommendations: ['eamodio.gitlens', 'angular.ng-template'] + }) + ); // run the command runCLI('add @nrwl/schematics --npmScope projscope --skip-install'); // check that dependencies and devDependencies remained the same @@ -276,6 +290,13 @@ describe('Nrwl Convert to Nx Workspace', () => { expect(packageJson.devDependencies['@ngrx/store-devtools']).toEqual( ngrxVersion ); + + expect(readJson('.vscode/extensions.json').recommendations).toEqual([ + 'eamodio.gitlens', + 'angular.ng-template', + 'nrwl.angular-console', + 'esbenp.prettier-vscode' + ]); }); it('should generate a workspace from a universal cli project', () => { diff --git a/e2e/utils.ts b/e2e/utils.ts index 5458c9d893..9d45826aa4 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -1,5 +1,6 @@ import { execSync, exec } from 'child_process'; import { readFileSync, statSync, writeFileSync } from 'fs'; +import { ensureDirSync } from 'fs-extra'; import * as path from 'path'; const projectName: string = 'proj'; @@ -150,6 +151,7 @@ export function runCommand(command: string): string { } export function updateFile(f: string, content: string): void { + ensureDirSync(path.dirname(path.join(getCwd(), 'tmp', 'proj', f))); writeFileSync(path.join(getCwd(), 'tmp', 'proj', f), content); } diff --git a/packages/schematics/migrations/migrations.json b/packages/schematics/migrations/migrations.json index 80ac255186..196e8c119f 100644 --- a/packages/schematics/migrations/migrations.json +++ b/packages/schematics/migrations/migrations.json @@ -54,6 +54,11 @@ "version": "7.5.0", "description": "Updated Angular CLI and Typescript", "factory": "./update-7-5-0/update-7-5-0" + }, + "update-7.6.0": { + "version": "7.6.0", + "description": "Add VSCode Extensions", + "factory": "./update-7-6-0/update-7-6-0" } } } diff --git a/packages/schematics/migrations/update-7-6-0/update-7-6-0.spec.ts b/packages/schematics/migrations/update-7-6-0/update-7-6-0.spec.ts new file mode 100644 index 0000000000..30131049a0 --- /dev/null +++ b/packages/schematics/migrations/update-7-6-0/update-7-6-0.spec.ts @@ -0,0 +1,69 @@ +import { Tree } from '@angular-devkit/schematics'; +import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; + +import * as path from 'path'; + +import { serializeJson } from '../../src/utils/fileutils'; +import { readJsonInTree, updateJsonInTree } from '../../src/utils/ast-utils'; + +describe('Update 7.6.0', () => { + let initialTree: Tree; + let schematicRunner: SchematicTestRunner; + + beforeEach(() => { + initialTree = Tree.empty(); + + initialTree.create( + 'package.json', + serializeJson({ + devDependencies: { + '@angular/cli': '7.1.0', + typescript: '~3.1.0' + } + }) + ); + + schematicRunner = new SchematicTestRunner( + '@nrwl/schematics', + path.join(__dirname, '../migrations.json') + ); + }); + + it('should add vscode extension recommendations', async () => { + const result = await schematicRunner + .runSchematicAsync('update-7.6.0', {}, initialTree) + .toPromise(); + + expect(readJsonInTree(result, '.vscode/extensions.json')).toEqual({ + recommendations: [ + 'nrwl.angular-console', + 'angular.ng-template', + 'esbenp.prettier-vscode' + ] + }); + }); + + it('should add to existing vscode extension recommendations', async () => { + initialTree = await schematicRunner + .callRule( + updateJsonInTree('.vscode/extensions.json', () => ({ + recommendations: ['eamodio.gitlens', 'angular.ng-template'] + })), + initialTree + ) + .toPromise(); + + const result = await schematicRunner + .runSchematicAsync('update-7.6.0', {}, initialTree) + .toPromise(); + + expect(readJsonInTree(result, '.vscode/extensions.json')).toEqual({ + recommendations: [ + 'eamodio.gitlens', + 'angular.ng-template', + 'nrwl.angular-console', + 'esbenp.prettier-vscode' + ] + }); + }); +}); diff --git a/packages/schematics/migrations/update-7-6-0/update-7-6-0.ts b/packages/schematics/migrations/update-7-6-0/update-7-6-0.ts new file mode 100644 index 0000000000..2884a0e71f --- /dev/null +++ b/packages/schematics/migrations/update-7-6-0/update-7-6-0.ts @@ -0,0 +1,25 @@ +import { Rule, chain, externalSchematic } from '@angular-devkit/schematics'; + +import { updateJsonInTree } from '../../src/utils/ast-utils'; + +const addExtensionRecommendations = updateJsonInTree( + '.vscode/extensions.json', + (json: { recommendations?: string[] }) => { + json.recommendations = json.recommendations || []; + [ + 'nrwl.angular-console', + 'angular.ng-template', + 'esbenp.prettier-vscode' + ].forEach(extension => { + if (!json.recommendations.includes(extension)) { + json.recommendations.push(extension); + } + }); + + return json; + } +); + +export default function(): Rule { + return chain([addExtensionRecommendations]); +} diff --git a/packages/schematics/src/collection/ng-add/index.ts b/packages/schematics/src/collection/ng-add/index.ts index 2fc5bdf499..9dde158e15 100755 --- a/packages/schematics/src/collection/ng-add/index.ts +++ b/packages/schematics/src/collection/ng-add/index.ts @@ -40,7 +40,7 @@ import { } from '../../utils/ast-utils'; import { editTarget } from '../../utils/cli-config-utils'; import { from } from 'rxjs'; -import { tap, mapTo } from 'rxjs/operators'; +import { tap, mapTo, concatMap } from 'rxjs/operators'; import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; import { getAppModulePath } from '@schematics/angular/utility/ng-ast-utils'; import { insertImport } from '@schematics/angular/utility/ast-utils'; @@ -585,6 +585,24 @@ function createAdditionalFiles(options: Schema): Rule { ); host.create('libs/.gitkeep', ''); + host = updateJsonInTree( + '.vscode/extensions.json', + (json: { recommendations?: string[] }) => { + json.recommendations = json.recommendations || []; + [ + 'nrwl.angular-console', + 'angular.ng-template', + 'esbenp.prettier-vscode' + ].forEach(extension => { + if (!json.recommendations.includes(extension)) { + json.recommendations.push(extension); + } + }); + + return json; + } + )(host, _context) as Tree; + // if the user does not already have a prettier configuration // of any kind, create one return from(resolveUserExistingPrettierConfig()).pipe( diff --git a/packages/schematics/src/collection/ng-new/files/__directory__/.vscode/extensions.json b/packages/schematics/src/collection/ng-new/files/__directory__/.vscode/extensions.json new file mode 100644 index 0000000000..f04b901297 --- /dev/null +++ b/packages/schematics/src/collection/ng-new/files/__directory__/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "nrwl.angular-console", + "angular.ng-template", + "esbenp.prettier-vscode" + ] +} 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 34a81762cf..1f27dcab39 100644 --- a/packages/schematics/src/collection/ng-new/ng-new.spec.ts +++ b/packages/schematics/src/collection/ng-new/ng-new.spec.ts @@ -57,6 +57,24 @@ describe('app', () => { }); }); + 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', + 'esbenp.prettier-vscode' + ]); + }); + it('should create a root karma configuration', () => { const tree = schematicRunner.runSchematic( 'ng-new', diff --git a/packages/schematics/src/utils/ast-utils.ts b/packages/schematics/src/utils/ast-utils.ts index ae3e41fbee..cf43a09226 100755 --- a/packages/schematics/src/utils/ast-utils.ts +++ b/packages/schematics/src/utils/ast-utils.ts @@ -589,6 +589,10 @@ export function updateJsonInTree( callback: (json: T) => O ): Rule { return (host: Tree): Tree => { + if (!host.exists(path)) { + host.create(path, serializeJson(callback({} as T))); + return host; + } host.overwrite(path, serializeJson(callback(readJsonInTree(host, path)))); return host; };