diff --git a/e2e/schematics/convert-to-workspace.test.ts b/e2e/schematics/convert-to-workspace.test.ts new file mode 100644 index 0000000000..dd9f564c54 --- /dev/null +++ b/e2e/schematics/convert-to-workspace.test.ts @@ -0,0 +1,66 @@ +import {checkFilesExists, cleanup, newApp, readFile, runCLI, runSchematic, updateFile} from '../utils'; + +describe('Nrwl Convert to Nx Workspace', () => { + beforeEach(cleanup); + + fit('should generate a workspace', () => { + newApp('new proj --skip-install'); + + // update package.json + const packageJson = JSON.parse(readFile('proj/package.json')); + packageJson.description = "some description"; + packageJson.dependencies['@ngrx/store'] = "4.0.3"; + packageJson.devDependencies['@ngrx/router-store'] = "4.0.3"; + updateFile('proj/package.json', JSON.stringify(packageJson, null, 2)); + + // update tsconfig.json + const tsconfigJson = JSON.parse(readFile('proj/tsconfig.json')); + tsconfigJson.compilerOptions.paths = { + 'a': ['b'] + }; + updateFile('proj/tsconfig.json', JSON.stringify(tsconfigJson, null, 2)); + + + // run the command + runSchematic('@nrwl/schematics:convert-to-workspace', { projectName: 'proj' }); + + // check that files have been moved! + checkFilesExists('proj/apps/proj/src/main.ts', 'proj/apps/proj/src/app/app.module.ts'); + + // check that package.json got merged + const updatedPackageJson = JSON.parse(readFile('proj/package.json')); + expect(updatedPackageJson.description).toEqual('some description'); + expect(updatedPackageJson.dependencies['@ngrx/store']).toEqual('4.0.3'); + expect(updatedPackageJson.devDependencies['@ngrx/router-store']).toEqual('4.0.3'); + expect(updatedPackageJson.devDependencies['@nrwl/schematics']).toBeDefined(); + expect(updatedPackageJson.dependencies['@nrwl/nx']).toBeDefined(); + + // check if angular-cli.json get merged + const updatedAngularCLIJson = JSON.parse(readFile('proj/.angular-cli.json')); + expect(updatedAngularCLIJson.apps[0].root).toEqual('apps/proj/src'); + expect(updatedAngularCLIJson.apps[0].outDir).toEqual('dist/apps/proj'); + expect(updatedAngularCLIJson.apps[0].test).toEqual('../../../test.js'); + expect(updatedAngularCLIJson.apps[0].tsconfig).toEqual('../../../tsconfig.app.json'); + expect(updatedAngularCLIJson.apps[0].testTsconfig).toEqual('../../../tsconfig.spec.json'); + + // check if tsconfig.json get merged + const updatedTsConfig = JSON.parse(readFile('proj/tsconfig.json')); + expect(updatedTsConfig.compilerOptions.paths).toEqual({ + "a": ["b"], + "*": [ + "*", + "libs/*", + "apps/*" + ] + }); + }); + + it('should build and test', () => { + newApp('new proj'); + runSchematic('@nrwl/schematics:convert-to-workspace', { projectName: 'proj' }); + + expect(runCLI('build', { projectName: 'proj' })).toContain('{main} main.bundle.js'); + expect(runCLI('test --single-run', { projectName: 'proj' })).toContain('Executed 3 of 3 SUCCESS'); + }); +}); + diff --git a/packages/schematics/README.md b/packages/schematics/README.md index 9b50f6c18f..a0369c7840 100644 --- a/packages/schematics/README.md +++ b/packages/schematics/README.md @@ -140,7 +140,31 @@ import { MyLib } from 'mylib'; And then run `ng build --app=myapp`, it will build `mylib` and include it in the app's bundle. +#### Covert and Existing App into a Nx Workspace +If you have an existing CLI project, you can convert it into an Nx Workspace like this. + +First, make sure you have `@nrwl/schematics` installed. Either run: + +``` +yarn global add @nrwl/schematics +``` + +Or run the following in the project dir: + +``` +yarn add @nrwl/schematics +``` + +Now, run: + +``` +schematics @nrwl/schematics:convert-to-workspace +``` + +* Your project files will be moved under:`apps/projectName` +* Some files have moved to the root: tsconfigs, test.js, so all apps and libs share them. +* `package.json` and `.angular-cli.json` will be updated ### NgRx diff --git a/packages/schematics/src/app/index.ts b/packages/schematics/src/app/index.ts index cf79d478cd..71d6cd7539 100644 --- a/packages/schematics/src/app/index.ts +++ b/packages/schematics/src/app/index.ts @@ -24,7 +24,7 @@ function addAppToAngularCliJson(options: Schema): Rule { return (host: Tree) => { const appConfig = { "name": options.name, - "root": `apps/${options.name}/${options.sourceDir}`, + "root": path.join('apps', options.name, options.sourceDir), "outDir": `dist/apps/${options.name}`, "assets": [ "assets", diff --git a/packages/schematics/src/collection.json b/packages/schematics/src/collection.json index 98220fede5..9d3bbbd383 100644 --- a/packages/schematics/src/collection.json +++ b/packages/schematics/src/collection.json @@ -2,6 +2,12 @@ "name": "nx", "version": "0.1", "schematics": { + "convert-to-workspace": { + "factory": "./convert-to-workspace", + "schema": "./convert-to-workspace/schema.json", + "description": "Convert an existing CLI project into an Nx Workspace" + }, + "application": { "factory": "./workspace", "schema": "./workspace/schema.json", diff --git a/packages/schematics/src/workspace/files/test.js b/packages/schematics/src/convert-to-workspace/files/test.js similarity index 100% rename from packages/schematics/src/workspace/files/test.js rename to packages/schematics/src/convert-to-workspace/files/test.js diff --git a/packages/schematics/src/convert-to-workspace/index.ts b/packages/schematics/src/convert-to-workspace/index.ts new file mode 100644 index 0000000000..983558cc8f --- /dev/null +++ b/packages/schematics/src/convert-to-workspace/index.ts @@ -0,0 +1,165 @@ +import { + apply, branchAndMerge, chain, externalSchematic, mergeWith, move, Rule, template, Tree, + url, + schematic +} from '@angular-devkit/schematics'; +import {Schema} from './schema'; +import {names, toFileName} from '@nrwl/schematics'; +import * as path from 'path'; +import {nxVersion, schematicsVersion} from '../utility/lib-versions'; +import * as fs from 'fs'; +import {join} from 'path'; + +function updatePackageJson() { + return (host: Tree) => { + if (!host.exists('package.json')) { + throw new Error('Cannot find package.json'); + } + const packageJson = JSON.parse(host.read('package.json')!.toString('utf-8')); + if (!packageJson.devDependencies) { + packageJson.devDependencies = {}; + } + if (!packageJson.dependencies) { + packageJson.dependencies = {}; + } + packageJson.dependencies['@nrwl/nx'] = nxVersion; + packageJson.devDependencies['@nrwl/schematics'] = schematicsVersion; + host.overwrite('package.json', JSON.stringify(packageJson, null, 2)); + + return host; + }; +} + +function updateAngularCLIJson() { + return (host: Tree) => { + if (!host.exists('.angular-cli.json')) { + throw new Error('Cannot find .angular-cli.json'); + } + const angularCliJson = JSON.parse(host.read('.angular-cli.json')!.toString('utf-8')); + if (angularCliJson.apps.length !== 1) { + throw new Error('Can only convert projects with one app'); + } + + angularCliJson.lint = [ + { + "project": "./tsconfig.app.json" + }, + { + "project": "./tsconfig.spec.json" + }, + { + "project": "./tsconfig.e2e.json" + } + ]; + + const app = angularCliJson.apps[0]; + app.root = path.join('apps', angularCliJson.project.name, app.root); + app.outDir = path.join('dist', 'apps', angularCliJson.project.name); + app.test = '../../../test.js'; + app.tsconfig = '../../../tsconfig.app.json'; + app.testTsconfig = '../../../tsconfig.spec.json'; + + host.overwrite('.angular-cli.json', JSON.stringify(angularCliJson, null, 2)); + + return host; + }; +} + +function updateTsConfigsJson() { + return (host: Tree) => { + const tsconfigJson = JSON.parse(fs.readFileSync('tsconfig.json', 'utf-8')); + if (! tsconfigJson.compilerOptions.paths) { + tsconfigJson.compilerOptions.paths = {}; + } + tsconfigJson.compilerOptions.baseUrl = '.'; + tsconfigJson.compilerOptions.paths['*'] = [ + "*", + "libs/*", + "apps/*" + ]; + fs.writeFileSync('tsconfig.json', JSON.stringify(tsconfigJson, null, 2)); + + const tsconfingAppJson = JSON.parse(fs.readFileSync('tsconfig.app.json', 'utf-8')); + tsconfingAppJson['extends'] = './tsconfig.json'; + if (!tsconfingAppJson.exclude) { + tsconfingAppJson.exclude = []; + } + tsconfingAppJson.exclude = dedup(tsconfingAppJson.exclude.concat([ + "**/*.spec.ts", + "**/*.e2e-spec.ts", + "node_modules", + "tmp" + ])); + fs.writeFileSync('tsconfig.app.json', JSON.stringify(tsconfingAppJson, null, 2)); + + const tsconfingSpecJson = JSON.parse(fs.readFileSync('tsconfig.spec.json', 'utf-8')); + tsconfingSpecJson['extends'] = './tsconfig.json'; + if (!tsconfingSpecJson.exclude) { + tsconfingSpecJson.exclude = []; + } + tsconfingSpecJson.files = [ + 'test.js' + ]; + tsconfingSpecJson.exclude = dedup(tsconfingSpecJson.exclude.concat([ + "node_modules", + "tmp" + ])); + fs.writeFileSync('tsconfig.spec.json', JSON.stringify(tsconfingSpecJson, null, 2)); + + const tsconfingE2eJson = JSON.parse(fs.readFileSync('tsconfig.e2e.json', 'utf-8')); + tsconfingE2eJson['extends'] = './tsconfig.json'; + if (!tsconfingE2eJson.exclude) { + tsconfingE2eJson.exclude = []; + } + tsconfingE2eJson.exclude = dedup(tsconfingE2eJson.exclude.concat([ + "**/*.spec.ts", + "node_modules", + "tmp" + ])); + fs.writeFileSync('tsconfig.e2e.json', JSON.stringify(tsconfingE2eJson, null, 2)); + + + return host; + }; +} + +function moveFiles() { + return (host: Tree) => { + const angularCliJson = JSON.parse(host.read('.angular-cli.json')!.toString('utf-8')); + const app = angularCliJson.apps[0]; + + fs.mkdirSync('apps'); + fs.mkdirSync('libs'); + fs.unlinkSync(path.join(app.root, app.test)); + fs.mkdirSync(path.join('apps', angularCliJson.project.name)); + fs.renameSync(path.join(app.root, app.tsconfig), 'tsconfig.app.json'); + fs.renameSync(path.join(app.root, app.testTsconfig), 'tsconfig.spec.json'); + fs.renameSync(path.join('e2e', 'tsconfig.e2e.json'), 'tsconfig.e2e.json'); + fs.renameSync(app.root, join('apps', angularCliJson.project.name, app.root)); + fs.renameSync('e2e', join('apps', angularCliJson.project.name, 'e2e')); + + return host; + }; +} + +function dedup(array: any[]): any[] { + const res = []; + array.forEach(a => { + if (res.indexOf(a) === -1) { + res.push(a); + } + }); + return res; +} + +export default function (options: Schema): Rule { + return chain([ + moveFiles(), + branchAndMerge(chain([ + mergeWith( apply(url('./files'), [])), + ])), + updatePackageJson(), + updateAngularCLIJson(), + updateTsConfigsJson() + ]); +} diff --git a/packages/schematics/src/convert-to-workspace/schema.d.ts b/packages/schematics/src/convert-to-workspace/schema.d.ts new file mode 100644 index 0000000000..c276c83f76 --- /dev/null +++ b/packages/schematics/src/convert-to-workspace/schema.d.ts @@ -0,0 +1,2 @@ +export interface Schema { +} diff --git a/packages/schematics/src/convert-to-workspace/schema.json b/packages/schematics/src/convert-to-workspace/schema.json new file mode 100644 index 0000000000..d25dbc621f --- /dev/null +++ b/packages/schematics/src/convert-to-workspace/schema.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "convert-to-workspace", + "title": "Convert to Nx workspace", + "description": "NOTE: Does not work in the --dry-run mode", + "type": "object", + "properties": { + }, + "required": [ + ] +} diff --git a/packages/schematics/src/utility/lib-versions.ts b/packages/schematics/src/utility/lib-versions.ts index 0dec6296cd..3b0c9a443d 100644 --- a/packages/schematics/src/utility/lib-versions.ts +++ b/packages/schematics/src/utility/lib-versions.ts @@ -1,2 +1,4 @@ export const angularJsVersion = "1.6.6"; export const ngrxVersion = "4.0.3"; +export const nxVersion = "nrwl/nx-build"; +export const schematicsVersion = "nrwl/schematics-build"; diff --git a/packages/schematics/src/workspace/files/README.md b/packages/schematics/src/workspace/files/__directory__/README.md old mode 100755 new mode 100644 similarity index 100% rename from packages/schematics/src/workspace/files/README.md rename to packages/schematics/src/workspace/files/__directory__/README.md diff --git a/packages/schematics/src/workspace/files/__dot__angular-cli.json b/packages/schematics/src/workspace/files/__directory__/__dot__angular-cli.json similarity index 100% rename from packages/schematics/src/workspace/files/__dot__angular-cli.json rename to packages/schematics/src/workspace/files/__directory__/__dot__angular-cli.json diff --git a/packages/schematics/src/workspace/files/__dot__editorconfig b/packages/schematics/src/workspace/files/__directory__/__dot__editorconfig similarity index 100% rename from packages/schematics/src/workspace/files/__dot__editorconfig rename to packages/schematics/src/workspace/files/__directory__/__dot__editorconfig diff --git a/packages/schematics/src/workspace/files/__dot__gitignore b/packages/schematics/src/workspace/files/__directory__/__dot__gitignore old mode 100755 new mode 100644 similarity index 100% rename from packages/schematics/src/workspace/files/__dot__gitignore rename to packages/schematics/src/workspace/files/__directory__/__dot__gitignore diff --git a/packages/schematics/src/workspace/files/apps/.gitkeep b/packages/schematics/src/workspace/files/__directory__/apps/.gitkeep similarity index 100% rename from packages/schematics/src/workspace/files/apps/.gitkeep rename to packages/schematics/src/workspace/files/__directory__/apps/.gitkeep diff --git a/packages/schematics/src/workspace/files/karma.conf.js b/packages/schematics/src/workspace/files/__directory__/karma.conf.js similarity index 100% rename from packages/schematics/src/workspace/files/karma.conf.js rename to packages/schematics/src/workspace/files/__directory__/karma.conf.js diff --git a/packages/schematics/src/workspace/files/libs/.gitkeep b/packages/schematics/src/workspace/files/__directory__/libs/.gitkeep similarity index 100% rename from packages/schematics/src/workspace/files/libs/.gitkeep rename to packages/schematics/src/workspace/files/__directory__/libs/.gitkeep diff --git a/packages/schematics/src/workspace/files/package.json b/packages/schematics/src/workspace/files/__directory__/package.json similarity index 96% rename from packages/schematics/src/workspace/files/package.json rename to packages/schematics/src/workspace/files/__directory__/package.json index f99795534d..8a6546b79c 100644 --- a/packages/schematics/src/workspace/files/package.json +++ b/packages/schematics/src/workspace/files/__directory__/package.json @@ -23,12 +23,12 @@ "@angular/router": "^4.2.4", "core-js": "^2.4.1", "rxjs": "^5.4.2", - "zone.js": "^0.8.14" + "zone.js": "^0.8.14", + "@nrwl/nx": "nrwl/nx-build" }, "devDependencies": { "@angular/cli": "<%= version %>", "@angular/compiler-cli": "^4.2.4", - "@nrwl/nx": "nrwl/nx-build", "@nrwl/schematics": "nrwl/schematics-build", "@angular/language-service": "^4.2.4",<% if (!minimal) { %> "@types/jasmine": "~2.5.53", diff --git a/packages/schematics/src/workspace/files/protractor.conf.js b/packages/schematics/src/workspace/files/__directory__/protractor.conf.js similarity index 100% rename from packages/schematics/src/workspace/files/protractor.conf.js rename to packages/schematics/src/workspace/files/__directory__/protractor.conf.js diff --git a/packages/schematics/src/workspace/files/__directory__/test.js b/packages/schematics/src/workspace/files/__directory__/test.js new file mode 100644 index 0000000000..75f4b420b1 --- /dev/null +++ b/packages/schematics/src/workspace/files/__directory__/test.js @@ -0,0 +1,30 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files +require('zone.js/dist/long-stack-trace-zone'); +require('zone.js/dist/proxy.js'); +require('zone.js/dist/sync-test'); +require('zone.js/dist/jasmine-patch'); +require('zone.js/dist/async-test'); +require('zone.js/dist/fake-async-test'); +const getTestBed = require('@angular/core/testing').getTestBed; +const BrowserDynamicTestingModule = require('@angular/platform-browser-dynamic/testing').BrowserDynamicTestingModule; +const platformBrowserDynamicTesting = require('@angular/platform-browser-dynamic/testing').platformBrowserDynamicTesting; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const contextApps = require.context('./apps', true, /\.spec\.ts$/); +// And load the modules. +contextApps.keys().map(contextApps); + +const contextLibs = require.context('./libs', true, /\.spec\.ts$/); +// And load the modules. +contextLibs.keys().map(contextLibs); + +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/packages/schematics/src/workspace/files/tsconfig.app.json b/packages/schematics/src/workspace/files/__directory__/tsconfig.app.json similarity index 51% rename from packages/schematics/src/workspace/files/tsconfig.app.json rename to packages/schematics/src/workspace/files/__directory__/tsconfig.app.json index a18f324f4f..8d1f4815e6 100644 --- a/packages/schematics/src/workspace/files/tsconfig.app.json +++ b/packages/schematics/src/workspace/files/__directory__/tsconfig.app.json @@ -2,17 +2,11 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "module": "es2015", - "baseUrl": ".", - "paths": { - "*": [ - "*", - "libs/*", - "apps/*" - ] - } + "module": "es2015" }, "exclude": [ + "**/*.spec.ts", + "**/*.e2e-spec.ts", "node_modules", "tmp" ] diff --git a/packages/schematics/src/workspace/files/tsconfig.e2e.json b/packages/schematics/src/workspace/files/__directory__/tsconfig.e2e.json similarity index 68% rename from packages/schematics/src/workspace/files/tsconfig.e2e.json rename to packages/schematics/src/workspace/files/__directory__/tsconfig.e2e.json index 012012e942..4c51cb6f0c 100644 --- a/packages/schematics/src/workspace/files/tsconfig.e2e.json +++ b/packages/schematics/src/workspace/files/__directory__/tsconfig.e2e.json @@ -2,23 +2,16 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/e2e", - "baseUrl": "./", "module": "commonjs", "target": "es5", "types": [ "jasmine", "jasminewd2", "node" - ], - "paths": { - "*": [ - "*", - "libs/*", - "apps/*" - ] - } + ] }, "exclude": [ + "**/*.spec.ts", "node_modules", "tmp" ] diff --git a/packages/schematics/src/workspace/files/tsconfig.json b/packages/schematics/src/workspace/files/__directory__/tsconfig.json similarity index 100% rename from packages/schematics/src/workspace/files/tsconfig.json rename to packages/schematics/src/workspace/files/__directory__/tsconfig.json diff --git a/packages/schematics/src/workspace/files/tsconfig.spec.json b/packages/schematics/src/workspace/files/__directory__/tsconfig.spec.json similarity index 73% rename from packages/schematics/src/workspace/files/tsconfig.spec.json rename to packages/schematics/src/workspace/files/__directory__/tsconfig.spec.json index 3319a5d22c..85fb5de9f7 100644 --- a/packages/schematics/src/workspace/files/tsconfig.spec.json +++ b/packages/schematics/src/workspace/files/__directory__/tsconfig.spec.json @@ -2,20 +2,12 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/spec", - "baseUrl": "./", "module": "commonjs", "target": "es5", "types": [ "jasmine", "node" - ], - "paths": { - "*": [ - "*", - "libs/*", - "apps/*" - ] - } + ] }, "files": [ "test.js" diff --git a/packages/schematics/src/workspace/files/tslint.json b/packages/schematics/src/workspace/files/__directory__/tslint.json similarity index 100% rename from packages/schematics/src/workspace/files/tslint.json rename to packages/schematics/src/workspace/files/__directory__/tslint.json diff --git a/packages/schematics/src/workspace/index.ts b/packages/schematics/src/workspace/index.ts index 878a6c3dc7..23f09dbbe0 100644 --- a/packages/schematics/src/workspace/index.ts +++ b/packages/schematics/src/workspace/index.ts @@ -13,8 +13,7 @@ export default function (options: Schema): Rule { return chain([ branchAndMerge(chain([ - mergeWith(templateSource), - move(options.directory) + mergeWith(templateSource) ])) ]); } diff --git a/scripts/e2e.sh b/scripts/e2e.sh index 13f2ede9dc..05275c6723 100755 --- a/scripts/e2e.sh +++ b/scripts/e2e.sh @@ -2,4 +2,4 @@ ./scripts/link.sh rm -rf tmp -jest --maxWorkers=1 ./build/e2e +jest --maxWorkers=1 ./build/e2e/schematics/convert-to-workspace.test.js