diff --git a/packages/devkit/index.ts b/packages/devkit/index.ts index dd1100ce3f..d24a13b94c 100644 --- a/packages/devkit/index.ts +++ b/packages/devkit/index.ts @@ -11,6 +11,7 @@ export { NxJsonProjectConfiguration, } from '@nrwl/tao/src/shared/nx'; export { logger } from '@nrwl/tao/src/shared/logger'; +export { getPackageManagerCommand } from '@nrwl/tao/src/shared/package-manager'; export { TargetContext } from '@nrwl/tao/src/commands/run'; export { formatFiles } from './src/generators/format-files'; @@ -25,7 +26,10 @@ export { readJson, writeJson, updateJson } from './src/utils/json'; export { addDependenciesToPackageJson } from './src/utils/package-json'; export { installPackagesTask } from './src/tasks/install-packages-task'; export { names } from './src/utils/names'; -export { getWorkspaceLayout } from './src/utils/get-workspace-layout'; +export { + getWorkspaceLayout, + getWorkspacePath, +} from './src/utils/get-workspace-layout'; export { applyChangesToString, ChangeType, diff --git a/packages/devkit/src/tasks/install-packages-task.ts b/packages/devkit/src/tasks/install-packages-task.ts index bd839d7497..fd51c63102 100644 --- a/packages/devkit/src/tasks/install-packages-task.ts +++ b/packages/devkit/src/tasks/install-packages-task.ts @@ -1,6 +1,7 @@ import { Tree } from '@nrwl/tao/src/shared/tree'; import { execSync } from 'child_process'; import { getPackageManagerCommand } from '@nrwl/tao/src/shared/package-manager'; +import { join } from 'path'; let storedPackageJsonValue; @@ -11,14 +12,21 @@ let storedPackageJsonValue; * @param host - the file system tree * @param alwaysRun - always run the command even if `package.json` hasn't changed. */ -export function installPackagesTask(host: Tree, alwaysRun: boolean = false) { - const packageJsonValue = host.read('package.json').toString(); - if (host.listChanges().find((f) => f.path === 'package.json') || alwaysRun) { +export function installPackagesTask( + host: Tree, + alwaysRun: boolean = false, + cwd: string = '' +) { + const packageJsonValue = host.read(join(cwd, 'package.json')).toString(); + if ( + host.listChanges().find((f) => f.path === join(cwd, 'package.json')) || + alwaysRun + ) { if (storedPackageJsonValue != packageJsonValue || alwaysRun) { - storedPackageJsonValue = host.read('package.json').toString(); + storedPackageJsonValue = host.read(join(cwd, 'package.json')).toString(); const pmc = getPackageManagerCommand(); execSync(pmc.install, { - cwd: host.root, + cwd: join(host.root, cwd), stdio: [0, 1, 2], }); } diff --git a/packages/tao/src/commands/generate.ts b/packages/tao/src/commands/generate.ts index 6cf64761ca..bcfc9f9e35 100644 --- a/packages/tao/src/commands/generate.ts +++ b/packages/tao/src/commands/generate.ts @@ -179,14 +179,31 @@ export async function taoNew(cwd: string, args: string[], isVerbose = false) { null, null ); - return (await import('./ngcli-adapter')).invokeNew( - cwd, - { - ...opts, - generatorOptions: combinedOpts, - }, - isVerbose - ); + + if (ws.isNxGenerator(opts.collectionName, normalizedGeneratorName)) { + const host = new FsTree(cwd, isVerbose); + const task = await implementation(host, combinedOpts); + const changes = host.listChanges(); + + printChanges(changes); + if (!opts.dryRun) { + flushChanges(cwd, changes); + if (task) { + await task(); + } + } else { + logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`); + } + } else { + return (await import('./ngcli-adapter')).generate( + cwd, + { + ...opts, + generatorOptions: combinedOpts, + }, + isVerbose + ); + } }); } diff --git a/packages/tao/src/shared/tree.spec.ts b/packages/tao/src/shared/tree.spec.ts index 6355a282bd..571f722fe8 100644 --- a/packages/tao/src/shared/tree.spec.ts +++ b/packages/tao/src/shared/tree.spec.ts @@ -95,6 +95,42 @@ describe('tree', () => { ).toEqual('new child content'); }); + it('should normalize paths', () => { + tree.write('dir/file1', 'File 1 Contents'); + tree.write('/dir/file2', 'File 2 Contents'); + tree.write('./dir/file3', 'File 3 Contents'); + + expect(tree.read('dir/file1').toString()).toEqual('File 1 Contents'); + expect(tree.read('/dir/file1').toString()).toEqual('File 1 Contents'); + expect(tree.read('./dir/file1').toString()).toEqual('File 1 Contents'); + + expect(tree.read('dir/file2').toString()).toEqual('File 2 Contents'); + expect(tree.read('/dir/file2').toString()).toEqual('File 2 Contents'); + expect(tree.read('./dir/file2').toString()).toEqual('File 2 Contents'); + + expect(tree.read('dir/file3').toString()).toEqual('File 3 Contents'); + expect(tree.read('/dir/file3').toString()).toEqual('File 3 Contents'); + expect(tree.read('./dir/file3').toString()).toEqual('File 3 Contents'); + + tree.rename('dir/file1', 'dir/file-a'); + + expect(tree.read('dir/file-a').toString()).toEqual('File 1 Contents'); + expect(tree.read('/dir/file-a').toString()).toEqual('File 1 Contents'); + expect(tree.read('./dir/file-a').toString()).toEqual('File 1 Contents'); + + tree.rename('/dir/file2', '/dir/file-b'); + + expect(tree.read('dir/file-b').toString()).toEqual('File 2 Contents'); + expect(tree.read('/dir/file-b').toString()).toEqual('File 2 Contents'); + expect(tree.read('./dir/file-b').toString()).toEqual('File 2 Contents'); + + tree.rename('./dir/file3', './dir/file-c'); + + expect(tree.read('dir/file-c').toString()).toEqual('File 3 Contents'); + expect(tree.read('/dir/file-c').toString()).toEqual('File 3 Contents'); + expect(tree.read('./dir/file-c').toString()).toEqual('File 3 Contents'); + }); + it('should be able to delete files', () => { tree.delete('parent/parent-file.txt'); tree.write('parent/new-child/new-child-file.txt', 'new child content'); diff --git a/packages/tao/src/shared/tree.ts b/packages/tao/src/shared/tree.ts index b8a565818c..2bcce0554f 100644 --- a/packages/tao/src/shared/tree.ts +++ b/packages/tao/src/shared/tree.ts @@ -1,4 +1,3 @@ -import * as path from 'path'; import { readdirSync, readFileSync, @@ -8,6 +7,7 @@ import { } from 'fs'; import { mkdirpSync, rmdirSync } from 'fs-extra'; import { logger } from './logger'; +import { dirname, join, relative } from 'path'; const chalk = require('chalk'); /** @@ -88,6 +88,7 @@ export class FsTree implements Tree { constructor(readonly root: string, private readonly isVerbose: boolean) {} read(filePath: string): Buffer | null { + filePath = this.normalize(filePath); try { if (this.recordedChanges[this.rp(filePath)]) { return this.recordedChanges[this.rp(filePath)].content; @@ -103,6 +104,7 @@ export class FsTree implements Tree { } write(filePath: string, content: Buffer | string): void { + filePath = this.normalize(filePath); try { this.recordedChanges[this.rp(filePath)] = { content: Buffer.from(content), @@ -116,10 +118,12 @@ export class FsTree implements Tree { } overwrite(filePath: string, content: Buffer | string): void { + filePath = this.normalize(filePath); this.write(filePath, content); } exists(filePath: string): boolean { + filePath = this.normalize(filePath); try { if (this.recordedChanges[this.rp(filePath)]) { return !this.recordedChanges[this.rp(filePath)].isDeleted; @@ -134,6 +138,7 @@ export class FsTree implements Tree { } delete(filePath: string): void { + filePath = this.normalize(filePath); if (this.filesForDir(this.rp(filePath)).length > 0) { this.filesForDir(this.rp(filePath)).forEach( (f) => (this.recordedChanges[f] = { content: null, isDeleted: true }) @@ -146,12 +151,15 @@ export class FsTree implements Tree { } rename(from: string, to: string): void { + from = this.normalize(from); + to = this.normalize(to); const content = this.read(this.rp(from)); this.recordedChanges[this.rp(from)] = { content: null, isDeleted: true }; this.recordedChanges[this.rp(to)] = { content: content, isDeleted: false }; } isFile(filePath: string): boolean { + filePath = this.normalize(filePath); try { if (this.recordedChanges[this.rp(filePath)]) { return !this.recordedChanges[this.rp(filePath)].isDeleted; @@ -164,11 +172,12 @@ export class FsTree implements Tree { } children(dirPath: string): string[] { + dirPath = this.normalize(dirPath); let res = this.fsReadDir(dirPath); res = [...res, ...this.directChildrenOfDir(this.rp(dirPath))]; return res.filter((q) => { - const r = this.recordedChanges[path.join(this.rp(dirPath), q)]; + const r = this.recordedChanges[join(this.rp(dirPath), q)]; if (r && r.isDeleted) return false; return true; }); @@ -200,10 +209,14 @@ export class FsTree implements Tree { return res; } + private normalize(path: string) { + return relative(this.root, join(this.root, path)); + } + private fsReadDir(dirPath: string) { if (!this.delegateToFs) return []; try { - return readdirSync(path.join(this.root, dirPath)); + return readdirSync(join(this.root, dirPath)); } catch (e) { return []; } @@ -211,19 +224,19 @@ export class FsTree implements Tree { private fsIsFile(filePath: string) { if (!this.delegateToFs) return false; - const stat = statSync(path.join(this.root, filePath)); + const stat = statSync(join(this.root, filePath)); return stat.isFile(); } private fsReadFile(filePath: string) { if (!this.delegateToFs) return null; - return readFileSync(path.join(this.root, filePath)); + return readFileSync(join(this.root, filePath)); } private fsExists(filePath: string): boolean { if (!this.delegateToFs) return false; try { - const stat = statSync(path.join(this.root, filePath)); + const stat = statSync(join(this.root, filePath)); return stat.isFile() || stat.isDirectory(); } catch (e) { return false; @@ -258,9 +271,9 @@ export class FsTree implements Tree { export function flushChanges(root: string, fileChanges: FileChange[]) { fileChanges.forEach((f) => { - const fpath = path.join(root, f.path); + const fpath = join(root, f.path); if (f.type === 'CREATE') { - mkdirpSync(path.dirname(fpath)); + mkdirpSync(dirname(fpath)); writeFileSync(fpath, f.content); } else if (f.type === 'UPDATE') { writeFileSync(fpath, f.content); diff --git a/packages/workspace/collection.json b/packages/workspace/collection.json index 8e3333e83e..52dc7ac2e9 100644 --- a/packages/workspace/collection.json +++ b/packages/workspace/collection.json @@ -3,7 +3,7 @@ "version": "0.1", "schematics": { "workspace": { - "factory": "./src/schematics/workspace/workspace", + "factory": "./src/schematics/workspace/workspace#workspaceSchematic", "schema": "./src/schematics/workspace/schema.json", "description": "Create an empty workspace", "hidden": true @@ -38,7 +38,71 @@ }, "new": { - "factory": "./src/schematics/new/new", + "factory": "./src/schematics/new/new#newSchematic", + "schema": "./src/schematics/new/schema.json", + "description": "Create a workspace", + "hidden": true + }, + + "library": { + "factory": "./src/schematics/library/library", + "schema": "./src/schematics/library/schema.json", + "aliases": ["lib"], + "description": "Create a library" + }, + + "workspace-generator": { + "factory": "./src/schematics/workspace-generator/workspace-generator", + "schema": "./src/schematics/workspace-generator/schema.json", + "aliases": ["workspace-schematic"], + "description": "Generates a workspace generator" + }, + + "run-commands": { + "factory": "./src/schematics/run-commands/run-commands", + "schema": "./src/schematics/run-commands/schema.json", + "aliases": ["run-command", "target"], + "description": "Generates a target to run any command in the terminal" + } + }, + "generators": { + "workspace": { + "factory": "./src/schematics/workspace/workspace#workspaceGenerator", + "schema": "./src/schematics/workspace/schema.json", + "description": "Create an empty workspace", + "hidden": true + }, + + "ng-add": { + "factory": "./src/schematics/init/init", + "schema": "./src/schematics/init/schema.json", + "description": "Convert an existing CLI project into an Nx Workspace", + "hidden": true + }, + + "preset": { + "factory": "./src/schematics/preset/preset", + "schema": "./src/schematics/preset/schema.json", + "description": "Create application in an empty workspace", + "hidden": true + }, + + "move": { + "factory": "./src/schematics/move/move", + "schema": "./src/schematics/move/schema.json", + "aliases": ["mv"], + "description": "Move an application or library to another folder" + }, + + "remove": { + "factory": "./src/schematics/remove/remove", + "schema": "./src/schematics/remove/schema.json", + "aliases": ["rm"], + "description": "Remove an application or library" + }, + + "new": { + "factory": "./src/schematics/new/new#newGenerator", "schema": "./src/schematics/new/schema.json", "description": "Create a workspace", "hidden": true diff --git a/packages/workspace/src/schematics/new/__snapshots__/new.spec.ts.snap b/packages/workspace/src/schematics/new/__snapshots__/new.spec.ts.snap new file mode 100644 index 0000000000..fec55d232e --- /dev/null +++ b/packages/workspace/src/schematics/new/__snapshots__/new.spec.ts.snap @@ -0,0 +1,383 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`new --preset angular should generate an empty nx.json 1`] = ` +Object { + "affected": Object { + "defaultBase": "master", + }, + "implicitDependencies": Object { + ".eslintrc.json": "*", + "nx.json": "*", + "package.json": Object { + "dependencies": "*", + "devDependencies": "*", + }, + "tsconfig.base.json": "*", + "tslint.json": "*", + "workspace.json": "*", + }, + "npmScope": "npmScope", + "projects": Object {}, + "tasksRunnerOptions": Object { + "default": Object { + "options": Object { + "cacheableOperations": Array [ + "build", + "lint", + "test", + "e2e", + ], + }, + "runner": "@nrwl/workspace/tasks-runners/default", + }, + }, +} +`; + +exports[`new --preset angular should generate an empty workspace.json 1`] = ` +Object { + "cli": Object { + "defaultCollection": "@nrwl/workspace", + }, + "generators": Object { + "@nrwl/angular": Object { + "application": Object { + "linter": "eslint", + }, + "library": Object { + "linter": "eslint", + }, + "storybook-configuration": Object { + "linter": "eslint", + }, + }, + }, + "projects": Object {}, + "version": 2, +} +`; + +exports[`new --preset angular should generate necessary npm dependencies 1`] = ` +Object { + "dependencies": Object { + "@nrwl/angular": "*", + }, + "devDependencies": Object { + "@nrwl/cli": "*", + "@nrwl/tao": "*", + "@nrwl/workspace": "*", + "@types/node": "12.12.38", + "dotenv": "6.2.0", + "eslint": "7.10.0", + "prettier": "2.1.2", + "ts-node": "~9.1.1", + "tslint": "~6.1.0", + "typescript": "~4.0.3", + }, + "license": "MIT", + "name": "my-workspace", + "private": true, + "scripts": Object { + "affected": "nx affected", + "affected:apps": "nx affected:apps", + "affected:build": "nx affected:build", + "affected:dep-graph": "nx affected:dep-graph", + "affected:e2e": "nx affected:e2e", + "affected:libs": "nx affected:libs", + "affected:lint": "nx affected:lint", + "affected:test": "nx affected:test", + "build": "nx build", + "dep-graph": "nx dep-graph", + "e2e": "nx e2e", + "format": "nx format:write", + "format:check": "nx format:check", + "format:write": "nx format:write", + "help": "nx help", + "lint": "nx workspace-lint && nx lint", + "nx": "nx", + "start": "nx serve", + "test": "nx test", + "update": "nx migrate latest", + "workspace-generator": "nx workspace-generator", + }, + "version": "0.0.0", +} +`; + +exports[`new --preset empty should generate an empty nx.json 1`] = ` +Object { + "affected": Object { + "defaultBase": "master", + }, + "implicitDependencies": Object { + ".eslintrc.json": "*", + "nx.json": "*", + "package.json": Object { + "dependencies": "*", + "devDependencies": "*", + }, + "tsconfig.base.json": "*", + "tslint.json": "*", + "workspace.json": "*", + }, + "npmScope": "npmScope", + "projects": Object {}, + "tasksRunnerOptions": Object { + "default": Object { + "options": Object { + "cacheableOperations": Array [ + "build", + "lint", + "test", + "e2e", + ], + }, + "runner": "@nrwl/workspace/tasks-runners/default", + }, + }, +} +`; + +exports[`new --preset empty should generate an empty workspace.json 1`] = ` +Object { + "cli": Object { + "defaultCollection": "@nrwl/workspace", + }, + "projects": Object {}, + "version": 2, +} +`; + +exports[`new --preset empty should generate an necessary npm dependencies 1`] = ` +Object { + "dependencies": Object {}, + "devDependencies": Object { + "@nrwl/cli": "*", + "@nrwl/tao": "*", + "@nrwl/workspace": "*", + "@types/node": "12.12.38", + "dotenv": "6.2.0", + "eslint": "7.10.0", + "prettier": "2.1.2", + "ts-node": "~9.1.1", + "tslint": "~6.1.0", + "typescript": "~4.0.3", + }, + "license": "MIT", + "name": "my-workspace", + "private": true, + "scripts": Object { + "affected": "nx affected", + "affected:apps": "nx affected:apps", + "affected:build": "nx affected:build", + "affected:dep-graph": "nx affected:dep-graph", + "affected:e2e": "nx affected:e2e", + "affected:libs": "nx affected:libs", + "affected:lint": "nx affected:lint", + "affected:test": "nx affected:test", + "build": "nx build", + "dep-graph": "nx dep-graph", + "e2e": "nx e2e", + "format": "nx format:write", + "format:check": "nx format:check", + "format:write": "nx format:write", + "help": "nx help", + "lint": "nx workspace-lint && nx lint", + "nx": "nx", + "start": "nx serve", + "test": "nx test", + "update": "nx migrate latest", + "workspace-generator": "nx workspace-generator", + }, + "version": "0.0.0", +} +`; + +exports[`new --preset empty should generate necessary npm dependencies 1`] = ` +Object { + "dependencies": Object {}, + "devDependencies": Object { + "@nrwl/cli": "*", + "@nrwl/tao": "*", + "@nrwl/workspace": "*", + "@types/node": "12.12.38", + "dotenv": "6.2.0", + "eslint": "7.10.0", + "prettier": "2.1.2", + "ts-node": "~9.1.1", + "tslint": "~6.1.0", + "typescript": "~4.0.3", + }, + "license": "MIT", + "name": "my-workspace", + "private": true, + "scripts": Object { + "affected": "nx affected", + "affected:apps": "nx affected:apps", + "affected:build": "nx affected:build", + "affected:dep-graph": "nx affected:dep-graph", + "affected:e2e": "nx affected:e2e", + "affected:libs": "nx affected:libs", + "affected:lint": "nx affected:lint", + "affected:test": "nx affected:test", + "build": "nx build", + "dep-graph": "nx dep-graph", + "e2e": "nx e2e", + "format": "nx format:write", + "format:check": "nx format:check", + "format:write": "nx format:write", + "help": "nx help", + "lint": "nx workspace-lint && nx lint", + "nx": "nx", + "start": "nx serve", + "test": "nx test", + "update": "nx migrate latest", + "workspace-generator": "nx workspace-generator", + }, + "version": "0.0.0", +} +`; + +exports[`new --preset react should generate an empty nx.json 1`] = ` +Object { + "affected": Object { + "defaultBase": "master", + }, + "implicitDependencies": Object { + ".eslintrc.json": "*", + "nx.json": "*", + "package.json": Object { + "dependencies": "*", + "devDependencies": "*", + }, + "tsconfig.base.json": "*", + "tslint.json": "*", + "workspace.json": "*", + }, + "npmScope": "npmScope", + "projects": Object {}, + "tasksRunnerOptions": Object { + "default": Object { + "options": Object { + "cacheableOperations": Array [ + "build", + "lint", + "test", + "e2e", + ], + }, + "runner": "@nrwl/workspace/tasks-runners/default", + }, + }, +} +`; + +exports[`new --preset react should generate an empty workspace.json 1`] = ` +Object { + "cli": Object { + "defaultCollection": "@nrwl/workspace", + }, + "projects": Object {}, + "version": 2, +} +`; + +exports[`new --preset react should generate necessary npm dependencies 1`] = ` +Object { + "dependencies": Object {}, + "devDependencies": Object { + "@nrwl/cli": "*", + "@nrwl/react": "*", + "@nrwl/tao": "*", + "@nrwl/workspace": "*", + "@types/node": "12.12.38", + "dotenv": "6.2.0", + "eslint": "7.10.0", + "prettier": "2.1.2", + "ts-node": "~9.1.1", + "tslint": "~6.1.0", + "typescript": "~4.0.3", + }, + "license": "MIT", + "name": "my-workspace", + "private": true, + "scripts": Object { + "affected": "nx affected", + "affected:apps": "nx affected:apps", + "affected:build": "nx affected:build", + "affected:dep-graph": "nx affected:dep-graph", + "affected:e2e": "nx affected:e2e", + "affected:libs": "nx affected:libs", + "affected:lint": "nx affected:lint", + "affected:test": "nx affected:test", + "build": "nx build", + "dep-graph": "nx dep-graph", + "e2e": "nx e2e", + "format": "nx format:write", + "format:check": "nx format:check", + "format:write": "nx format:write", + "help": "nx help", + "lint": "nx workspace-lint && nx lint", + "nx": "nx", + "start": "nx serve", + "test": "nx test", + "update": "nx migrate latest", + "workspace-generator": "nx workspace-generator", + }, + "version": "0.0.0", +} +`; + +exports[`new should 1`] = ` +Object { + "cli": Object { + "defaultCollection": "@nrwl/workspace", + }, + "projects": Object {}, + "version": 2, +} +`; + +exports[`new should generate an empty nx.json 1`] = ` +Object { + "affected": Object { + "defaultBase": "master", + }, + "implicitDependencies": Object { + ".eslintrc.json": "*", + "nx.json": "*", + "package.json": Object { + "dependencies": "*", + "devDependencies": "*", + }, + "tsconfig.base.json": "*", + "tslint.json": "*", + "workspace.json": "*", + }, + "npmScope": "npmScope", + "projects": Object {}, + "tasksRunnerOptions": Object { + "default": Object { + "options": Object { + "cacheableOperations": Array [ + "build", + "lint", + "test", + "e2e", + ], + }, + "runner": "@nrwl/workspace/tasks-runners/default", + }, + }, +} +`; + +exports[`new should generate an empty workspace.json 1`] = ` +Object { + "cli": Object { + "defaultCollection": "@nrwl/workspace", + }, + "projects": Object {}, + "version": 2, +} +`; diff --git a/packages/workspace/src/schematics/new/new.spec.ts b/packages/workspace/src/schematics/new/new.spec.ts new file mode 100644 index 0000000000..b4c72fe5d8 --- /dev/null +++ b/packages/workspace/src/schematics/new/new.spec.ts @@ -0,0 +1,65 @@ +import { createTree } from '@nrwl/devkit/testing'; +import { readJson, Tree } from '@nrwl/devkit'; +import { newGenerator, Preset, Schema } from './new'; +import { Linter } from '../../utils/lint'; + +const defaultOptions: Omit = { + cli: 'nx', + preset: Preset.Empty, + skipInstall: false, + skipGit: false, + linter: Linter.EsLint, + defaultBase: 'master', +}; + +describe('new', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTree(); + }); + + it('should generate an empty workspace.json', async () => { + await newGenerator(tree, { + ...defaultOptions, + name: 'my-workspace', + directory: 'my-workspace', + npmScope: 'npmScope', + appName: 'app', + }); + expect(readJson(tree, 'my-workspace/workspace.json')).toMatchSnapshot(); + }); + + it('should generate an empty nx.json', async () => { + await newGenerator(tree, { + ...defaultOptions, + name: 'my-workspace', + directory: 'my-workspace', + npmScope: 'npmScope', + appName: 'app', + }); + expect(readJson(tree, 'my-workspace/nx.json')).toMatchSnapshot(); + }); + + describe('--preset', () => { + describe.each([[Preset.Empty], [Preset.Angular], [Preset.React]])( + '%s', + (preset) => { + beforeEach(async () => { + await newGenerator(tree, { + ...defaultOptions, + name: 'my-workspace', + directory: 'my-workspace', + npmScope: 'npmScope', + appName: 'app', + preset, + }); + }); + + it('should generate necessary npm dependencies', () => { + expect(readJson(tree, 'my-workspace/package.json')).toMatchSnapshot(); + }); + } + ); + }); +}); diff --git a/packages/workspace/src/schematics/new/new.ts b/packages/workspace/src/schematics/new/new.ts index 8c1158350d..1ec52644ae 100644 --- a/packages/workspace/src/schematics/new/new.ts +++ b/packages/workspace/src/schematics/new/new.ts @@ -1,31 +1,21 @@ import { - chain, - move, - noop, - Rule, - schematic, - SchematicContext, Tree, -} from '@angular-devkit/schematics'; -import { - NodePackageInstallTask, - RepositoryInitializerTask, -} from '@angular-devkit/schematics/tasks'; + formatFiles, + updateJson, + addDependenciesToPackageJson, + installPackagesTask, + getWorkspacePath, + convertNxGenerator, + names, + getPackageManagerCommand, +} from '@nrwl/devkit'; -import { - addDepsToPackageJson, - updateWorkspaceInTree, -} from '../../utils/ast-utils'; - -import { formatFiles } from '../../utils/rules/format-files'; - -import { nxVersion } from '../../utils/versions'; -import * as path from 'path'; -import { Observable } from 'rxjs'; -import { spawn } from 'child_process'; -import { getPackageManagerCommand } from '@nrwl/tao/src/shared/package-manager'; +import { join } from 'path'; import * as yargsParser from 'yargs-parser'; -import { names } from '@nrwl/devkit'; +import { spawn, SpawnOptions } from 'child_process'; + +import { workspaceGenerator } from '../workspace/workspace'; +import { nxVersion } from '../../utils/versions'; export enum Preset { Empty = 'empty', @@ -51,67 +41,114 @@ export interface Schema { nxCloud?: boolean; preset: Preset; commit?: { name: string; email: string; message?: string }; - defaultBase?: string; - nxWorkspaceRoot?: string; + defaultBase: string; linter: 'tslint' | 'eslint'; packageManager?: string; } -class RunPresetTask { - toConfiguration() { - return { - name: 'RunPreset', - }; - } -} - -function createPresetTaskExecutor(opts: Schema) { +function generatePreset(host: Tree, opts: Schema) { const cliCommand = opts.cli === 'angular' ? 'ng' : 'nx'; const parsedArgs = yargsParser(process.argv, { boolean: ['interactive'], }); - - return { - name: 'RunPreset', - create: () => { - return Promise.resolve(() => { - const spawnOptions = { - stdio: [process.stdin, process.stdout, process.stderr], - shell: true, - cwd: path.join(opts.nxWorkspaceRoot || process.cwd(), opts.directory), - }; - const pmc = getPackageManagerCommand(); - const executable = `${pmc.exec} ${cliCommand}`; - const args = [ - `g`, - `@nrwl/workspace:preset`, - `--name=${opts.appName}`, - opts.style ? `--style=${opts.style}` : null, - opts.linter ? `--linter=${opts.linter}` : null, - opts.npmScope - ? `--npmScope=${opts.npmScope}` - : `--npmScope=${opts.name}`, - opts.preset ? `--preset=${opts.preset}` : null, - `--cli=${cliCommand}`, - parsedArgs.interactive ? '--interactive=true' : '--interactive=false', - ].filter((e) => !!e); - return new Observable((obs) => { - spawn(executable, args, spawnOptions).on('close', (code: number) => { - if (code === 0) { - obs.next(); - obs.complete(); - } else { - const message = 'Workspace creation failed, see above.'; - obs.error(new Error(message)); - } - }); - }); - }); - }, + const spawnOptions = { + stdio: [process.stdin, process.stdout, process.stderr], + shell: true, + cwd: join(host.root, opts.directory), }; + const pmc = getPackageManagerCommand(); + const executable = `${pmc.exec} ${cliCommand}`; + const args = [ + `g`, + `@nrwl/workspace:preset`, + `--name=${opts.appName}`, + opts.style ? `--style=${opts.style}` : null, + opts.linter ? `--linter=${opts.linter}` : null, + opts.npmScope ? `--npmScope=${opts.npmScope}` : `--npmScope=${opts.name}`, + opts.preset ? `--preset=${opts.preset}` : null, + `--cli=${cliCommand}`, + parsedArgs.interactive ? '--interactive=true' : '--interactive=false', + ].filter((e) => !!e); + return new Promise((resolve, reject) => { + spawn(executable, args, spawnOptions).on('close', (code: number) => { + if (code === 0) { + resolve(); + } else { + const message = 'Workspace creation failed, see above.'; + reject(new Error(message)); + } + }); + }); } -export default function (options: Schema): Rule { +async function initializeGitRepo( + host: Tree, + rootDirectory: string, + options: Schema +) { + const execute = (args: ReadonlyArray, ignoreErrorStream = false) => { + const outputStream = 'ignore'; + const errorStream = ignoreErrorStream ? 'ignore' : process.stderr; + const spawnOptions: SpawnOptions = { + stdio: [process.stdin, outputStream, errorStream], + shell: true, + cwd: join(host.root, rootDirectory), + env: { + ...process.env, + ...(options.commit.name + ? { + GIT_AUTHOR_NAME: options.commit.name, + GIT_COMMITTER_NAME: options.commit.name, + } + : {}), + ...(options.commit.email + ? { + GIT_AUTHOR_EMAIL: options.commit.email, + GIT_COMMITTER_EMAIL: options.commit.email, + } + : {}), + }, + }; + return new Promise((resolve, reject) => { + spawn('git', args, spawnOptions).on('close', (code) => { + if (code === 0) { + resolve(); + } else { + reject(code); + } + }); + }); + }; + const hasCommand = await execute(['--version']).then( + () => true, + () => false + ); + if (!hasCommand) { + return; + } + const insideRepo = await execute( + ['rev-parse', '--is-inside-work-tree'], + true + ).then( + () => true, + () => false + ); + if (insideRepo) { + console.info( + `Directory is already under version control. Skipping initialization of git.` + ); + return; + } + await execute(['init']); + await execute(['add', '.']); + if (options.commit) { + const message = options.commit.message || 'initial commit'; + await execute(['commit', `-m "${message}"`]); + } + console.info('Successfully initialized git.'); +} + +export async function newGenerator(host: Tree, options: Schema) { if ( options.skipInstall && options.preset !== 'empty' && @@ -125,139 +162,97 @@ export default function (options: Schema): Rule { options = normalizeOptions(options); - const layout = options.preset === 'oss' ? 'packages' : 'apps-and-libs'; + const layout: 'packages' | 'apps-and-libs' = + options.preset === 'oss' ? 'packages' : 'apps-and-libs'; const workspaceOpts = { ...options, layout, preset: undefined, nxCloud: undefined, }; - return (host: Tree, context: SchematicContext) => { - const engineHost = (context.engine.workflow as any).engineHost; - engineHost.registerTaskExecutor(createPresetTaskExecutor(options)); + workspaceGenerator(host, workspaceOpts); - return chain([ - schematic('workspace', workspaceOpts), - options.cli === 'angular' ? setDefaultPackageManager(options) : noop(), - setDefaultLinter(options), - addPresetDependencies(options), - addCloudDependencies(options), - move('/', options.directory), - addTasks(options), - formatFiles({ skipFormat: false }, options.directory), - ])(Tree.empty(), context); + if (options.cli === 'angular') { + setDefaultPackageManager(host, options); + } + setDefaultLinter(host, options); + addPresetDependencies(host, options); + addCloudDependencies(host, options); + + await formatFiles(host); + host.listChanges().forEach((change) => { + if (change.type !== 'DELETE') { + host.rename(change.path, join(options.directory, change.path)); + } + }); + return async () => { + installPackagesTask(host, false, options.directory); + await generatePreset(host, options); + if (!options.skipGit) { + await initializeGitRepo(host, options.directory, options); + } }; } -function addCloudDependencies(options: Schema) { - return options.nxCloud - ? addDepsToPackageJson({}, { '@nrwl/nx-cloud': 'latest' }, false) - : noop(); -} +export default newGenerator; +export const newSchematic = convertNxGenerator(newGenerator); -function addPresetDependencies(options: Schema) { - if (options.preset === 'empty') { - return noop(); - } else if (options.preset === 'web-components') { - return addDepsToPackageJson( +function addCloudDependencies(host: Tree, options: Schema) { + if (options.nxCloud) { + return addDependenciesToPackageJson( + host, {}, - { - '@nrwl/web': nxVersion, - }, - false + { '@nrwl/nx-cloud': 'latest' } ); - } else if (options.preset === 'angular') { - return addDepsToPackageJson( - { - '@nrwl/angular': nxVersion, - }, - {}, - false - ); - } else if (options.preset === 'angular-nest') { - return addDepsToPackageJson( - { - '@nrwl/angular': nxVersion, - }, - { - '@nrwl/nest': nxVersion, - }, - false - ); - } else if (options.preset === 'react') { - return addDepsToPackageJson( - {}, - { - '@nrwl/react': nxVersion, - }, - false - ); - } else if (options.preset === 'react-express') { - return addDepsToPackageJson( - {}, - { - '@nrwl/react': nxVersion, - '@nrwl/express': nxVersion, - }, - false - ); - } else if (options.preset === 'next') { - return addDepsToPackageJson( - {}, - { - '@nrwl/next': nxVersion, - }, - false - ); - } else if (options.preset === 'nest') { - return addDepsToPackageJson( - {}, - { - '@nrwl/nest': nxVersion, - }, - false - ); - } else { - return noop(); } } -function addTasks(options: Schema) { - return (host: Tree, context: SchematicContext) => { - let packageTask; - let presetInstallTask; - if (!options.skipInstall) { - packageTask = context.addTask( - new NodePackageInstallTask(options.directory) - ); - } - if (options.preset !== 'empty') { - const createPresetTask = context.addTask(new RunPresetTask(), [ - packageTask, - ]); +const presetDependencies: Omit< + Record< + Preset, + { dependencies: Record; dev: Record } + >, + Preset.Empty | Preset.OSS +> = { + [Preset.WebComponents]: { dependencies: {}, dev: { '@nrwl/web': nxVersion } }, + [Preset.Angular]: { dependencies: { '@nrwl/angular': nxVersion }, dev: {} }, + [Preset.AngularWithNest]: { + dependencies: { '@nrwl/angular': nxVersion }, + dev: { '@nrwl/nest': nxVersion }, + }, + [Preset.React]: { + dependencies: {}, + dev: { + '@nrwl/react': nxVersion, + }, + }, + [Preset.ReactWithExpress]: { + dependencies: {}, + dev: { + '@nrwl/react': nxVersion, + '@nrwl/express': nxVersion, + }, + }, + [Preset.Nest]: { + dependencies: {}, + dev: { + '@nrwl/nest': nxVersion, + }, + }, + [Preset.NextJs]: { + dependencies: {}, + dev: { + '@nrwl/next': nxVersion, + }, + }, +}; - presetInstallTask = context.addTask( - new NodePackageInstallTask(options.directory), - [createPresetTask] - ); - } - if (!options.skipGit) { - const commit = - typeof options.commit == 'object' - ? options.commit - : !!options.commit - ? {} - : false; - context.addTask( - new RepositoryInitializerTask(options.directory, commit), - presetInstallTask - ? [presetInstallTask] - : packageTask - ? [packageTask] - : [] - ); - } - }; +function addPresetDependencies(host: Tree, options: Schema) { + if (options.preset === Preset.Empty || options.preset === Preset.OSS) { + return; + } + const { dependencies, dev } = presetDependencies[options.preset]; + return addDependenciesToPackageJson(host, dependencies, dev); } function normalizeOptions(options: Schema): Schema { @@ -269,30 +264,29 @@ function normalizeOptions(options: Schema): Schema { return options; } -function setDefaultLinter({ linter, preset }: Schema): Rule { +function setDefaultLinter(host: Tree, { linter, preset }: Schema) { // Don't do anything if someone doesn't pick angular - if (preset === 'angular' || preset === 'angular-nest') { - switch (linter) { - case 'eslint': { - return setESLintDefault(); - } - case 'tslint': { - return setTSLintDefault(); - } - default: { - return noop(); - } + if (preset !== 'angular' && preset !== 'angular-nest') { + return; + } + + switch (linter) { + case 'eslint': { + setESLintDefault(host); + break; + } + case 'tslint': { + setTSLintDefault(host); + break; } - } else { - return noop(); } } /** * This sets ESLint as the default for any schematics that default to TSLint */ -function setESLintDefault() { - return updateWorkspaceInTree((json) => { +function setESLintDefault(host: Tree) { + updateJson(host, getWorkspacePath(host), (json) => { setDefault(json, '@nrwl/angular', 'application', 'linter', 'eslint'); setDefault(json, '@nrwl/angular', 'library', 'linter', 'eslint'); setDefault( @@ -309,8 +303,8 @@ function setESLintDefault() { /** * This sets TSLint as the default for any schematics that default to ESLint */ -function setTSLintDefault() { - return updateWorkspaceInTree((json) => { +function setTSLintDefault(host: Tree) { + updateJson(host, getWorkspacePath(host), (json) => { setDefault(json, '@nrwl/workspace', 'library', 'linter', 'tslint'); setDefault(json, '@nrwl/cypress', 'cypress-project', 'linter', 'tslint'); setDefault(json, '@nrwl/cypress', 'cypress-project', 'linter', 'tslint'); @@ -325,12 +319,12 @@ function setTSLintDefault() { }); } -function setDefaultPackageManager({ packageManager }: Schema) { +function setDefaultPackageManager(host: Tree, { packageManager }: Schema) { if (!packageManager) { - return noop(); + return; } - return updateWorkspaceInTree((json) => { + updateJson(host, getWorkspacePath(host), (json) => { if (!json.cli) { json.cli = {}; } diff --git a/packages/workspace/src/schematics/new/schema.json b/packages/workspace/src/schematics/new/schema.json index 56ba55e17c..0d3cb93c23 100644 --- a/packages/workspace/src/schematics/new/schema.json +++ b/packages/workspace/src/schematics/new/schema.json @@ -3,6 +3,7 @@ "id": "NxWorkspaceNew", "title": "Create an empty workspace", "type": "object", + "cli": "nx", "properties": { "name": { "description": "The name of the workspace.", @@ -13,17 +14,17 @@ }, "x-prompt": "What name would you like to use for the workspace?" }, + "cli": { + "description": "CLI used for generating code and running tasks", + "type": "string", + "enum": ["nx", "angular"], + "default": "nx" + }, "style": { "description": "The file extension to be used for style files.", "type": "string", "default": "css" }, - "directory": { - "type": "string", - "format": "path", - "description": "The directory name to create the workspace in.", - "default": "" - }, "npmScope": { "type": "string", "description": "Npm scope for importing libs." @@ -82,11 +83,6 @@ "type": "boolean", "default": false }, - "nxWorkspaceRoot": { - "type": "string", - "description": "Root directory.", - "hidden": true - }, "linter": { "description": "The tool to use for running lint checks.", "type": "string", diff --git a/packages/workspace/src/schematics/workspace/__snapshots__/workspace.spec.ts.snap b/packages/workspace/src/schematics/workspace/__snapshots__/workspace.spec.ts.snap new file mode 100644 index 0000000000..e0bfac511b --- /dev/null +++ b/packages/workspace/src/schematics/workspace/__snapshots__/workspace.spec.ts.snap @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`workspace should create a prettierrc file 1`] = ` +"{ + \\"singleQuote\\": true +}" +`; diff --git a/packages/workspace/src/schematics/workspace/files/README.md__tmpl__ b/packages/workspace/src/schematics/workspace/files/README.md__tmpl__ index fbae411e4a..9a66d5be2d 100644 --- a/packages/workspace/src/schematics/workspace/files/README.md__tmpl__ +++ b/packages/workspace/src/schematics/workspace/files/README.md__tmpl__ @@ -1,6 +1,6 @@ <% if(cli === 'angular') { %> -# <%= utils.classify(name) %> +# <%= formattedNames.className %> This project was generated using [Nx](https://nx.dev). @@ -92,7 +92,7 @@ Visit the [Nx Documentation](https://nx.dev/angular) to learn more. <% } else { %> -# <%= utils.classify(name) %> +# <%= formattedNames.className %> This project was generated using [Nx](https://nx.dev). diff --git a/packages/workspace/src/schematics/workspace/files/__dot__prettierrc b/packages/workspace/src/schematics/workspace/files/__dot__prettierrc deleted file mode 100644 index 69909e172a..0000000000 --- a/packages/workspace/src/schematics/workspace/files/__dot__prettierrc +++ /dev/null @@ -1 +0,0 @@ -<%= defaultNrwlPrettierConfig %> \ No newline at end of file diff --git a/packages/workspace/src/schematics/workspace/files/package.json__tmpl__ b/packages/workspace/src/schematics/workspace/files/package.json__tmpl__ index c6b59ecbb5..8af4e77956 100644 --- a/packages/workspace/src/schematics/workspace/files/package.json__tmpl__ +++ b/packages/workspace/src/schematics/workspace/files/package.json__tmpl__ @@ -1,5 +1,5 @@ { - "name": "<%= utils.dasherize(name) %>", + "name": "<%= formattedNames.fileName %>", "version": "0.0.0", "license": "MIT", "scripts": { diff --git a/packages/workspace/src/schematics/workspace/schema.d.ts b/packages/workspace/src/schematics/workspace/schema.d.ts index 1874578dfa..dcd2f76deb 100644 --- a/packages/workspace/src/schematics/workspace/schema.d.ts +++ b/packages/workspace/src/schematics/workspace/schema.d.ts @@ -1,5 +1,4 @@ export interface Schema { - directory: string; name: string; npmScope?: string; skipInstall?: boolean; @@ -8,5 +7,5 @@ export interface Schema { commit?: { name: string; email: string; message?: string }; cli: 'nx' | 'angular'; layout: 'apps-and-libs' | 'packages'; - defaultBase?: string; + defaultBase: string; } diff --git a/packages/workspace/src/schematics/workspace/schema.json b/packages/workspace/src/schematics/workspace/schema.json index ad3b3c4b9b..c1f7f15ccc 100644 --- a/packages/workspace/src/schematics/workspace/schema.json +++ b/packages/workspace/src/schematics/workspace/schema.json @@ -1,6 +1,7 @@ { "$schema": "http://json-schema.org/schema", "id": "SchematicsNxNgNew", + "cli": "nx", "title": "Create an empty workspace", "type": "object", "properties": { @@ -41,6 +42,7 @@ "type": "string", "format": "path", "description": "The directory name to create the workspace in.", + "x-deprecated": "This option is no longer used.", "default": "" }, "layout": { diff --git a/packages/workspace/src/schematics/workspace/workspace.spec.ts b/packages/workspace/src/schematics/workspace/workspace.spec.ts index 372cea6e55..6de59d1341 100644 --- a/packages/workspace/src/schematics/workspace/workspace.spec.ts +++ b/packages/workspace/src/schematics/workspace/workspace.spec.ts @@ -1,16 +1,22 @@ -import { Tree } from '@angular-devkit/schematics'; -import { NxJson, readJsonInTree } from '@nrwl/workspace'; -import { runSchematic } from '../../utils/testing'; +import { readJson, Tree } from '@nrwl/devkit'; +import { workspaceGenerator } from './workspace'; +import { createTree } from '@nrwl/devkit/testing'; +import { NxJson } from '../../core/shared-interfaces'; -describe('workspace', () => { - let projectTree: Tree; +describe('@nrwl/workspace:workspace', () => { + let tree: Tree; beforeEach(() => { - projectTree = Tree.empty(); + tree = createTree(); }); it('should create files', async () => { - const tree = await runSchematic('workspace', { name: 'proj' }, projectTree); + workspaceGenerator(tree, { + name: 'proj', + cli: 'nx', + layout: 'apps-and-libs', + defaultBase: 'main', + }); expect(tree.exists('/nx.json')).toBe(true); expect(tree.exists('/workspace.json')).toBe(true); expect(tree.exists('/.prettierrc')).toBe(true); @@ -18,8 +24,13 @@ describe('workspace', () => { }); it('should create nx.json', async () => { - const tree = await runSchematic('workspace', { name: 'proj' }, projectTree); - const nxJson = readJsonInTree(tree, '/nx.json'); + workspaceGenerator(tree, { + name: 'proj', + cli: 'nx', + layout: 'apps-and-libs', + defaultBase: 'master', + }); + const nxJson = readJson(tree, '/nx.json'); expect(nxJson).toEqual({ npmScope: 'proj', affected: { @@ -48,9 +59,24 @@ describe('workspace', () => { }); }); + it('should create a prettierrc file', async () => { + workspaceGenerator(tree, { + name: 'proj', + cli: 'nx', + layout: 'apps-and-libs', + defaultBase: 'main', + }); + expect(tree.read('.prettierrc').toString()).toMatchSnapshot(); + }); + it('should recommend vscode extensions', async () => { - const tree = await runSchematic('workspace', { name: 'proj' }, projectTree); - const recommendations = readJsonInTree<{ recommendations: string[] }>( + workspaceGenerator(tree, { + name: 'proj', + cli: 'nx', + layout: 'apps-and-libs', + defaultBase: 'main', + }); + const recommendations = readJson<{ recommendations: string[] }>( tree, '/.vscode/extensions.json' ).recommendations; @@ -62,12 +88,13 @@ describe('workspace', () => { }); it('should recommend vscode extensions (angular)', async () => { - const tree = await runSchematic( - 'workspace', - { name: 'proj', cli: 'angular' }, - projectTree - ); - const recommendations = readJsonInTree<{ recommendations: string[] }>( + workspaceGenerator(tree, { + name: 'proj', + cli: 'angular', + layout: 'apps-and-libs', + defaultBase: 'main', + }); + const recommendations = readJson<{ recommendations: string[] }>( tree, '/.vscode/extensions.json' ).recommendations; @@ -81,39 +108,42 @@ describe('workspace', () => { }); it('should add decorate-angular-cli when used with angular cli', async () => { - const tree = await runSchematic( - 'workspace', - { name: 'proj', cli: 'angular' }, - projectTree - ); + workspaceGenerator(tree, { + name: 'proj', + cli: 'angular', + layout: 'apps-and-libs', + defaultBase: 'main', + }); expect(tree.exists('/decorate-angular-cli.js')).toBe(true); - const packageJson = readJsonInTree(tree, '/package.json'); + const packageJson = readJson(tree, '/package.json'); expect(packageJson.scripts.postinstall).toEqual( 'node ./decorate-angular-cli.js' ); }); it('should not add decorate-angular-cli when used with nx cli', async () => { - const tree = await runSchematic( - 'workspace', - { name: 'proj', cli: 'nx' }, - projectTree - ); + workspaceGenerator(tree, { + name: 'proj', + cli: 'nx', + layout: 'apps-and-libs', + defaultBase: 'main', + }); expect(tree.exists('/decorate-angular-cli.js')).toBe(false); - const packageJson = readJsonInTree(tree, '/package.json'); + const packageJson = readJson(tree, '/package.json'); expect(packageJson.scripts.postinstall).toBeUndefined(); }); it('should create a workspace using package layout', async () => { - const tree = await runSchematic( - 'workspace', - { name: 'proj', cli: 'nx', layout: 'packages' }, - projectTree - ); + workspaceGenerator(tree, { + name: 'proj', + cli: 'nx', + layout: 'packages', + defaultBase: 'main', + }); expect(tree.exists('/packages/.gitkeep')).toBe(true); expect(tree.exists('/apps/.gitkeep')).toBe(false); expect(tree.exists('/libs/.gitkeep')).toBe(false); - const nx = readJsonInTree(tree, '/nx.json'); + const nx = readJson(tree, '/nx.json'); expect(nx.workspaceLayout).toEqual({ appsDir: 'packages', libsDir: 'packages', diff --git a/packages/workspace/src/schematics/workspace/workspace.ts b/packages/workspace/src/schematics/workspace/workspace.ts index 2250643725..64ce4ae03a 100644 --- a/packages/workspace/src/schematics/workspace/workspace.ts +++ b/packages/workspace/src/schematics/workspace/workspace.ts @@ -1,17 +1,12 @@ import { - apply, - branchAndMerge, - chain, - mergeWith, - noop, - Rule, - SchematicContext, - template, + generateFiles, Tree, - url, -} from '@angular-devkit/schematics'; + updateJson, + convertNxGenerator, + names, + writeJson, +} from '@nrwl/devkit'; import { Schema } from './schema'; -import { join, strings } from '@angular-devkit/core'; import { angularCliVersion, eslintVersion, @@ -21,7 +16,6 @@ import { } from '../../utils/versions'; import { readFileSync } from 'fs'; import { join as pathJoin } from 'path'; -import { updateJsonInTree } from '@nrwl/workspace'; export const DEFAULT_NRWL_PRETTIER_CONFIG = { singleQuote: true, @@ -31,11 +25,11 @@ const decorateAngularClI = (host: Tree) => { const decorateCli = readFileSync( pathJoin(__dirname as any, '..', 'utils', 'decorate-angular-cli.js__tmpl__') ).toString(); - host.create('decorate-angular-cli.js', decorateCli); + host.write('decorate-angular-cli.js', decorateCli); }; -function setWorkspaceLayoutProperties(options: Schema) { - return updateJsonInTree('nx.json', (json) => { +function setWorkspaceLayoutProperties(tree: Tree, options: Schema) { + updateJson(tree, 'nx.json', (json) => { if (options.layout === 'packages') { json.workspaceLayout = { appsDir: 'packages', @@ -46,56 +40,51 @@ function setWorkspaceLayoutProperties(options: Schema) { }); } -function createAppsAndLibsFolders(options: Schema) { - return (host: Tree) => { - if (options.layout === 'packages') { - host.create('packages/.gitkeep', ''); - } else { - host.create('apps/.gitkeep', ''); - host.create('libs/.gitkeep', ''); - } - }; +function createAppsAndLibsFolders(host: Tree, options: Schema) { + if (options.layout === 'packages') { + host.write('packages/.gitkeep', ''); + } else { + host.write('apps/.gitkeep', ''); + host.write('libs/.gitkeep', ''); + } } -export default function (options: Schema): Rule { +function createFiles(host: Tree, options: Schema) { + const npmScope = options.npmScope ? options.npmScope : options.name; + const formattedNames = names(options.name); + generateFiles(host, pathJoin(__dirname, './files'), '', { + formattedNames, + dot: '.', + tmpl: '', + workspaceFile: options.cli === 'angular' ? 'angular' : 'workspace', + cliCommand: options.cli === 'angular' ? 'ng' : 'nx', + nxCli: false, + typescriptVersion, + prettierVersion, + eslintVersion, + // angular cli is used only when workspace schematics is added to angular cli + angularCliVersion, + ...(options as object), + nxVersion, + npmScope, + }); +} + +function createPrettierrc(host: Tree) { + writeJson(host, '.prettierrc', DEFAULT_NRWL_PRETTIER_CONFIG); +} + +export function workspaceGenerator(host: Tree, options: Schema) { 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: '', - workspaceFile: options.cli === 'angular' ? 'angular' : 'workspace', - cliCommand: options.cli === 'angular' ? 'ng' : 'nx', - nxCli: false, - typescriptVersion, - prettierVersion, - eslintVersion, - // angular cli is used only when workspace schematics is added to angular cli - angularCliVersion, - ...(options as object), - nxVersion, - npmScope, - defaultNrwlPrettierConfig: JSON.stringify( - DEFAULT_NRWL_PRETTIER_CONFIG, - null, - 2 - ), - }), - ]); - return chain([ - branchAndMerge( - chain([ - mergeWith(templateSource), - options.cli === 'angular' ? decorateAngularClI : noop(), - setWorkspaceLayoutProperties(options), - createAppsAndLibsFolders(options), - ]) - ), - ])(host, context); - }; + createFiles(host, options); + createPrettierrc(host); + if (options.cli === 'angular') { + decorateAngularClI(host); + } + setWorkspaceLayoutProperties(host, options); + createAppsAndLibsFolders(host, options); } + +export const workspaceSchematic = convertNxGenerator(workspaceGenerator);