From 4df3b661528656d81e5deaf603af2dbc25b9dce9 Mon Sep 17 00:00:00 2001 From: Jason Jean Date: Thu, 11 Mar 2021 14:18:22 -0500 Subject: [PATCH] feat(node): migrate @nrwl/node schematics to devkit (#4958) --- nx.json | 2 +- .../devkit/src/generators/generate-files.ts | 2 +- .../application/application.spec.ts | 4 +- .../application/application.spec.ts | 4 +- packages/node/collection.json | 35 +- packages/node/index.ts | 4 +- packages/node/package.json | 1 + .../application/application.spec.ts | 227 ++++++------ .../src/generators/application/application.ts | 240 ++++++++++++ .../application/files/app/src/app/.gitkeep | 0 .../application/files/app/src/assets/.gitkeep | 0 .../environments/environment.prod.ts__tmpl__ | 0 .../src/environments/environment.ts__tmpl__ | 0 .../application/files/app/src/main.ts__tmpl__ | 0 .../application/files/app/tsconfig.app.json | 0 .../application/files/app/tsconfig.json | 0 .../src/generators/application/schema.d.ts | 15 + .../application/schema.json | 1 + .../node/src/generators/init/init.spec.ts | 49 +++ packages/node/src/generators/init/init.ts | 53 +++ packages/node/src/generators/init/schema.d.ts | 4 + .../init/schema.json | 1 + .../library/files/lib/package.json__tmpl__ | 0 .../lib/src/lib/__fileName__.spec.ts__tmpl__ | 0 .../files/lib/src/lib/__fileName__.ts__tmpl__ | 0 .../library/files/lib/tsconfig.lib.json | 0 .../library/library.spec.ts | 341 +++++++----------- .../node/src/generators/library/library.ts | 140 +++++++ .../library/schema.d.ts | 16 +- .../library/schema.json | 1 + .../src/schematics/application/application.ts | 247 ------------- .../src/schematics/application/schema.d.ts | 15 - .../node/src/schematics/init/init.spec.ts | 52 --- packages/node/src/schematics/init/init.ts | 28 -- packages/node/src/schematics/init/schema.d.ts | 4 - .../node/src/schematics/library/library.ts | 148 -------- 36 files changed, 790 insertions(+), 844 deletions(-) rename packages/node/src/{schematics => generators}/application/application.spec.ts (67%) create mode 100644 packages/node/src/generators/application/application.ts rename packages/node/src/{schematics => generators}/application/files/app/src/app/.gitkeep (100%) rename packages/node/src/{schematics => generators}/application/files/app/src/assets/.gitkeep (100%) rename packages/node/src/{schematics => generators}/application/files/app/src/environments/environment.prod.ts__tmpl__ (100%) rename packages/node/src/{schematics => generators}/application/files/app/src/environments/environment.ts__tmpl__ (100%) rename packages/node/src/{schematics => generators}/application/files/app/src/main.ts__tmpl__ (100%) rename packages/node/src/{schematics => generators}/application/files/app/tsconfig.app.json (100%) rename packages/node/src/{schematics => generators}/application/files/app/tsconfig.json (100%) create mode 100644 packages/node/src/generators/application/schema.d.ts rename packages/node/src/{schematics => generators}/application/schema.json (99%) create mode 100644 packages/node/src/generators/init/init.spec.ts create mode 100644 packages/node/src/generators/init/init.ts create mode 100644 packages/node/src/generators/init/schema.d.ts rename packages/node/src/{schematics => generators}/init/schema.json (96%) rename packages/node/src/{schematics => generators}/library/files/lib/package.json__tmpl__ (100%) rename packages/node/src/{schematics => generators}/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ (100%) rename packages/node/src/{schematics => generators}/library/files/lib/src/lib/__fileName__.ts__tmpl__ (100%) rename packages/node/src/{schematics => generators}/library/files/lib/tsconfig.lib.json (100%) rename packages/node/src/{schematics => generators}/library/library.spec.ts (61%) create mode 100644 packages/node/src/generators/library/library.ts rename packages/node/src/{schematics => generators}/library/schema.d.ts (53%) rename packages/node/src/{schematics => generators}/library/schema.json (99%) delete mode 100644 packages/node/src/schematics/application/application.ts delete mode 100644 packages/node/src/schematics/application/schema.d.ts delete mode 100644 packages/node/src/schematics/init/init.spec.ts delete mode 100644 packages/node/src/schematics/init/init.ts delete mode 100644 packages/node/src/schematics/init/schema.d.ts delete mode 100644 packages/node/src/schematics/library/library.ts diff --git a/nx.json b/nx.json index 0fb0592dad..da08e6762e 100644 --- a/nx.json +++ b/nx.json @@ -71,7 +71,7 @@ }, "node": { "tags": [], - "implicitDependencies": ["workspace", "jest"] + "implicitDependencies": ["workspace", "jest", "linter"] }, "next": { "tags": [], diff --git a/packages/devkit/src/generators/generate-files.ts b/packages/devkit/src/generators/generate-files.ts index db0d34eb7c..c4f68e7736 100644 --- a/packages/devkit/src/generators/generate-files.ts +++ b/packages/devkit/src/generators/generate-files.ts @@ -75,7 +75,7 @@ export function generateFiles( newContent = fs.readFileSync(filePath); } else { const template = fs.readFileSync(filePath).toString(); - newContent = ejs.render(template, substitutions); + newContent = ejs.render(template, substitutions, {}); } host.write(computedPath, newContent); diff --git a/packages/express/src/schematics/application/application.spec.ts b/packages/express/src/schematics/application/application.spec.ts index 4aea80840c..ea152ff977 100644 --- a/packages/express/src/schematics/application/application.spec.ts +++ b/packages/express/src/schematics/application/application.spec.ts @@ -46,7 +46,9 @@ describe('app', () => { ); expect(eslintrcJson).toMatchInlineSnapshot(` Object { - "extends": "../../.eslintrc.json", + "extends": Array [ + "../../.eslintrc.json", + ], "ignorePatterns": Array [ "!**/*", ], diff --git a/packages/nest/src/schematics/application/application.spec.ts b/packages/nest/src/schematics/application/application.spec.ts index ea45466a3e..d1fda51638 100644 --- a/packages/nest/src/schematics/application/application.spec.ts +++ b/packages/nest/src/schematics/application/application.spec.ts @@ -24,7 +24,9 @@ describe('app', () => { ); expect(eslintrcJson).toMatchInlineSnapshot(` Object { - "extends": "../../.eslintrc.json", + "extends": Array [ + "../../.eslintrc.json", + ], "ignorePatterns": Array [ "!**/*", ], diff --git a/packages/node/collection.json b/packages/node/collection.json index c029fecaec..1956ffd690 100644 --- a/packages/node/collection.json +++ b/packages/node/collection.json @@ -2,23 +2,44 @@ "name": "nx/node", "version": "0.1", "extends": ["@nrwl/workspace"], - "schematics": { + "generators": { "init": { - "factory": "./src/schematics/init/init", - "schema": "./src/schematics/init/schema.json", + "factory": "./src/generators/init/init", + "schema": "./src/generators/init/schema.json", "description": "Initialize the @nrwl/node plugin", "aliases": ["ng-add"], "hidden": true }, "application": { - "factory": "./src/schematics/application/application", - "schema": "./src/schematics/application/schema.json", + "factory": "./src/generators/application/application", + "schema": "./src/generators/application/schema.json", "aliases": ["app"], "description": "Create a node application" }, "library": { - "factory": "./src/schematics/library/library", - "schema": "./src/schematics/library/schema.json", + "factory": "./src/generators/library/library", + "schema": "./src/generators/library/schema.json", + "aliases": ["lib"], + "description": "Create a library" + } + }, + "schematics": { + "init": { + "factory": "./src/generators/init/init#initSchematic", + "schema": "./src/generators/init/schema.json", + "description": "Initialize the @nrwl/node plugin", + "aliases": ["ng-add"], + "hidden": true + }, + "application": { + "factory": "./src/generators/application/application#applicationSchematic", + "schema": "./src/generators/application/schema.json", + "aliases": ["app"], + "description": "Create a node application" + }, + "library": { + "factory": "./src/generators/library/library#librarySchematic", + "schema": "./src/generators/library/schema.json", "aliases": ["lib"], "description": "Create a library" } diff --git a/packages/node/index.ts b/packages/node/index.ts index 6c2b36750b..2c89d6a04f 100644 --- a/packages/node/index.ts +++ b/packages/node/index.ts @@ -1,2 +1,2 @@ -export { applicationGenerator } from './src/schematics/application/application'; -export { libraryGenerator } from './src/schematics/library/library'; +export { applicationGenerator } from './src/generators/application/application'; +export { libraryGenerator } from './src/generators/library/library'; diff --git a/packages/node/package.json b/packages/node/package.json index d62f31121d..d35d584d72 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -29,6 +29,7 @@ "migrations": "./migrations.json" }, "dependencies": { + "@nrwl/workspace": "*", "@nrwl/devkit": "*", "@nrwl/jest": "*", "@nrwl/linter": "*", diff --git a/packages/node/src/schematics/application/application.spec.ts b/packages/node/src/generators/application/application.spec.ts similarity index 67% rename from packages/node/src/schematics/application/application.spec.ts rename to packages/node/src/generators/application/application.spec.ts index fa47a7eb7a..3476a35dc3 100644 --- a/packages/node/src/schematics/application/application.spec.ts +++ b/packages/node/src/generators/application/application.spec.ts @@ -1,25 +1,37 @@ -import { Tree } from '@angular-devkit/schematics'; -import { createEmptyWorkspace } from '@nrwl/workspace/testing'; -import { runSchematic } from '../../utils/testing'; -import { NxJson, readJsonInTree } from '@nrwl/workspace'; -// to break the dependency -const createApp = require('../../../../angular/' + 'src/utils/testing') - .createApp; +import { NxJsonConfiguration, readJson, Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { applicationGenerator as angularApplicationGenerator } from '@nrwl/angular/generators'; import { Schema } from './schema'; +import { applicationGenerator } from './application'; +import { overrideCollectionResolutionForTesting } from '@nrwl/devkit/ngcli-adapter'; +import { join } from 'path'; describe('app', () => { - let appTree: Tree; + let tree: Tree; beforeEach(() => { - appTree = Tree.empty(); - appTree = createEmptyWorkspace(appTree); + tree = createTreeWithEmptyWorkspace(); + + overrideCollectionResolutionForTesting({ + '@nrwl/cypress': join(__dirname, '../../../../cypress/collection.json'), + '@nrwl/jest': join(__dirname, '../../../../jest/collection.json'), + '@nrwl/workspace': join( + __dirname, + '../../../../workspace/collection.json' + ), + '@nrwl/angular': join(__dirname, '../../../../angular/collection.json'), + }); + }); + + afterEach(() => { + overrideCollectionResolutionForTesting(null); }); describe('not nested', () => { it('should update workspace.json', async () => { - const tree = await runSchematic('app', { name: 'myNodeApp' }, appTree); - const workspaceJson = readJsonInTree(tree, '/workspace.json'); + await applicationGenerator(tree, { name: 'myNodeApp' }); + const workspaceJson = readJson(tree, '/workspace.json'); const project = workspaceJson.projects['my-node-app']; expect(project.root).toEqual('apps/my-node-app'); expect(project.architect).toEqual( @@ -67,12 +79,8 @@ describe('app', () => { }); it('should update nx.json', async () => { - const tree = await runSchematic( - 'app', - { name: 'myNodeApp', tags: 'one,two' }, - appTree - ); - const nxJson = readJsonInTree(tree, '/nx.json'); + await applicationGenerator(tree, { name: 'myNodeApp', tags: 'one,two' }); + const nxJson = readJson(tree, '/nx.json'); expect(nxJson.projects).toEqual({ 'my-node-app': { tags: ['one', 'two'], @@ -81,11 +89,11 @@ describe('app', () => { }); it('should generate files', async () => { - const tree = await runSchematic('app', { name: 'myNodeApp' }, appTree); + await applicationGenerator(tree, { name: 'myNodeApp' }); expect(tree.exists(`apps/my-node-app/jest.config.js`)).toBeTruthy(); expect(tree.exists('apps/my-node-app/src/main.ts')).toBeTruthy(); - const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); + const tsconfig = readJson(tree, 'apps/my-node-app/tsconfig.json'); expect(tsconfig).toMatchInlineSnapshot(` Object { "extends": "../../tsconfig.base.json", @@ -102,17 +110,16 @@ describe('app', () => { } `); - const tsconfigApp = readJsonInTree( - tree, - 'apps/my-node-app/tsconfig.app.json' - ); + const tsconfigApp = readJson(tree, 'apps/my-node-app/tsconfig.app.json'); expect(tsconfigApp.compilerOptions.outDir).toEqual('../../dist/out-tsc'); expect(tsconfigApp.extends).toEqual('./tsconfig.json'); - const eslintrc = readJsonInTree(tree, 'apps/my-node-app/.eslintrc.json'); + const eslintrc = readJson(tree, 'apps/my-node-app/.eslintrc.json'); expect(eslintrc).toMatchInlineSnapshot(` Object { - "extends": "../../.eslintrc.json", + "extends": Array [ + "../../.eslintrc.json", + ], "ignorePatterns": Array [ "!**/*", ], @@ -153,12 +160,11 @@ describe('app', () => { describe('nested', () => { it('should update workspace.json', async () => { - const tree = await runSchematic( - 'app', - { name: 'myNodeApp', directory: 'myDir' }, - appTree - ); - const workspaceJson = readJsonInTree(tree, '/workspace.json'); + await applicationGenerator(tree, { + name: 'myNodeApp', + directory: 'myDir', + }); + const workspaceJson = readJson(tree, '/workspace.json'); expect(workspaceJson.projects['my-dir-my-node-app'].root).toEqual( 'apps/my-dir/my-node-app' @@ -178,12 +184,12 @@ describe('app', () => { }); it('should update nx.json', async () => { - const tree = await runSchematic( - 'app', - { name: 'myNodeApp', directory: 'myDir', tags: 'one,two' }, - appTree - ); - const nxJson = readJsonInTree(tree, '/nx.json'); + await applicationGenerator(tree, { + name: 'myNodeApp', + directory: 'myDir', + tags: 'one,two', + }); + const nxJson = readJson(tree, '/nx.json'); expect(nxJson.projects).toEqual({ 'my-dir-my-node-app': { tags: ['one', 'two'], @@ -193,15 +199,14 @@ describe('app', () => { it('should generate files', async () => { const hasJsonValue = ({ path, expectedValue, lookupFn }) => { - const config = readJsonInTree(tree, path); + const config = readJson(tree, path); expect(lookupFn(config)).toEqual(expectedValue); }; - const tree = await runSchematic( - 'app', - { name: 'myNodeApp', directory: 'myDir' }, - appTree - ); + await applicationGenerator(tree, { + name: 'myNodeApp', + directory: 'myDir', + }); // Make sure these exist [ @@ -226,7 +231,7 @@ describe('app', () => { { path: 'apps/my-dir/my-node-app/.eslintrc.json', lookupFn: (json) => json.extends, - expectedValue: '../../../.eslintrc.json', + expectedValue: ['../../../.eslintrc.json'], }, ].forEach(hasJsonValue); }); @@ -234,17 +239,16 @@ describe('app', () => { describe('--unit-test-runner none', () => { it('should not generate test configuration', async () => { - const tree = await runSchematic( - 'app', - { name: 'myNodeApp', unitTestRunner: 'none' }, - appTree - ); + await applicationGenerator(tree, { + name: 'myNodeApp', + unitTestRunner: 'none', + }); expect(tree.exists('jest.config.js')).toBeFalsy(); expect(tree.exists('apps/my-node-app/src/test-setup.ts')).toBeFalsy(); expect(tree.exists('apps/my-node-app/src/test.ts')).toBeFalsy(); expect(tree.exists('apps/my-node-app/tsconfig.spec.json')).toBeFalsy(); expect(tree.exists('apps/my-node-app/jest.config.js')).toBeFalsy(); - const workspaceJson = readJsonInTree(tree, 'workspace.json'); + const workspaceJson = readJson(tree, 'workspace.json'); expect( workspaceJson.projects['my-node-app'].architect.test ).toBeUndefined(); @@ -264,59 +268,53 @@ describe('app', () => { describe('--frontendProject', () => { it('should configure proxy', async () => { - appTree = createApp(appTree, 'my-frontend'); + await angularApplicationGenerator(tree, { name: 'my-frontend' }); - const tree = await runSchematic( - 'app', - { name: 'myNodeApp', frontendProject: 'my-frontend' }, - appTree - ); + await applicationGenerator(tree, { + name: 'myNodeApp', + frontendProject: 'my-frontend', + }); expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); - const serve = readJsonInTree(tree, 'workspace.json').projects[ - 'my-frontend' - ].architect.serve; + const serve = readJson(tree, 'workspace.json').projects['my-frontend'] + .architect.serve; expect(serve.options.proxyConfig).toEqual( 'apps/my-frontend/proxy.conf.json' ); }); it('should configure proxies for multiple node projects with the same frontend app', async () => { - appTree = createApp(appTree, 'my-frontend'); + await angularApplicationGenerator(tree, { name: 'my-frontend' }); - appTree = await runSchematic( - 'app', - { name: 'cart', frontendProject: 'my-frontend' }, - appTree - ); + await applicationGenerator(tree, { + name: 'cart', + frontendProject: 'my-frontend', + }); - const tree = await runSchematic( - 'app', - { name: 'billing', frontendProject: 'my-frontend' }, - appTree - ); + await applicationGenerator(tree, { + name: 'billing', + frontendProject: 'my-frontend', + }); expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); - expect(readJsonInTree(tree, 'apps/my-frontend/proxy.conf.json')).toEqual({ + expect(readJson(tree, 'apps/my-frontend/proxy.conf.json')).toEqual({ '/api': { target: 'http://localhost:3333', secure: false }, '/billing-api': { target: 'http://localhost:3333', secure: false }, }); }); it('should work with unnormalized project names', async () => { - appTree = createApp(appTree, 'myFrontend'); + await angularApplicationGenerator(tree, { name: 'myFrontend' }); - const tree = await runSchematic( - 'app', - { name: 'myNodeApp', frontendProject: 'myFrontend' }, - appTree - ); + await applicationGenerator(tree, { + name: 'myNodeApp', + frontendProject: 'myFrontend', + }); expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); - const serve = readJsonInTree(tree, 'workspace.json').projects[ - 'my-frontend' - ].architect.serve; + const serve = readJson(tree, 'workspace.json').projects['my-frontend'] + .architect.serve; expect(serve.options.proxyConfig).toEqual( 'apps/my-frontend/proxy.conf.json' ); @@ -325,22 +323,22 @@ describe('app', () => { describe('--babelJest', () => { it('should use babel for jest', async () => { - const tree = await runSchematic( - 'app', - { name: 'myNodeApp', tags: 'one,two', babelJest: true } as Schema, - appTree - ); + await applicationGenerator(tree, { + name: 'myNodeApp', + tags: 'one,two', + babelJest: true, + } as Schema); - expect(tree.readContent(`apps/my-node-app/jest.config.js`)) + expect(tree.read(`apps/my-node-app/jest.config.js`).toString()) .toMatchInlineSnapshot(` "module.exports = { displayName: 'my-node-app', preset: '../../jest.preset.js', transform: { - '^.+\\\\\\\\.[tj]s$': 'babel-jest', + '^.+\\\\\\\\.[tj]s$': 'babel-jest' }, - moduleFileExtensions: ['ts', 'js', 'html'], - coverageDirectory: '../../coverage/apps/my-node-app', + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/apps/my-node-app' }; " `); @@ -348,38 +346,30 @@ describe('app', () => { }); describe('--js flag', () => { it('should generate js files instead of ts files', async () => { - const tree = await runSchematic( - 'app', - { - name: 'myNodeApp', - js: true, - } as Schema, - appTree - ); + await applicationGenerator(tree, { + name: 'myNodeApp', + js: true, + } as Schema); expect(tree.exists(`apps/my-node-app/jest.config.js`)).toBeTruthy(); expect(tree.exists('apps/my-node-app/src/main.js')).toBeTruthy(); - const tsConfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); + const tsConfig = readJson(tree, 'apps/my-node-app/tsconfig.json'); expect(tsConfig.compilerOptions).toEqual({ allowJs: true, }); - const tsConfigApp = readJsonInTree( - tree, - 'apps/my-node-app/tsconfig.app.json' - ); + const tsConfigApp = readJson(tree, 'apps/my-node-app/tsconfig.app.json'); expect(tsConfigApp.include).toEqual(['**/*.ts', '**/*.js']); expect(tsConfigApp.exclude).toEqual(['**/*.spec.ts', '**/*.spec.js']); }); it('should update workspace.json', async () => { - const tree = await runSchematic( - 'app', - { name: 'myNodeApp', js: true } as Schema, - appTree - ); - const workspaceJson = readJsonInTree(tree, '/workspace.json'); + await applicationGenerator(tree, { + name: 'myNodeApp', + js: true, + } as Schema); + const workspaceJson = readJson(tree, '/workspace.json'); const project = workspaceJson.projects['my-node-app']; const buildTarget = project.architect.build; @@ -393,11 +383,11 @@ describe('app', () => { }); it('should generate js files for nested libs as well', async () => { - const tree = await runSchematic( - 'app', - { name: 'myNodeApp', directory: 'myDir', js: true } as Schema, - appTree - ); + await applicationGenerator(tree, { + name: 'myNodeApp', + directory: 'myDir', + js: true, + } as Schema); expect( tree.exists(`apps/my-dir/my-node-app/jest.config.js`) ).toBeTruthy(); @@ -407,11 +397,10 @@ describe('app', () => { describe('--pascalCaseFiles', () => { it(`should notify that this flag doesn't do anything`, async () => { - const tree = await runSchematic( - 'app', - { name: 'myNodeApp', pascalCaseFiles: true } as Schema, - appTree - ); + await applicationGenerator(tree, { + name: 'myNodeApp', + pascalCaseFiles: true, + } as Schema); // @TODO how to spy on context ? // expect(contextLoggerSpy).toHaveBeenCalledWith('NOTE: --pascalCaseFiles is a noop') diff --git a/packages/node/src/generators/application/application.ts b/packages/node/src/generators/application/application.ts new file mode 100644 index 0000000000..14e354266d --- /dev/null +++ b/packages/node/src/generators/application/application.ts @@ -0,0 +1,240 @@ +import { + addProjectConfiguration, + convertNxGenerator, + formatFiles, + generateFiles, + GeneratorCallback, + getWorkspaceLayout, + joinPathFragments, + logger, + names, + NxJsonProjectConfiguration, + offsetFromRoot, + ProjectConfiguration, + readProjectConfiguration, + readWorkspaceConfiguration, + TargetConfiguration, + toJS, + Tree, + updateProjectConfiguration, + updateTsConfigsToJs, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; + +import { join } from 'path'; + +import { Linter, lintProjectGenerator } from '@nrwl/linter'; +import { jestProjectGenerator } from '@nrwl/jest'; +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; + +import { Schema } from './schema'; +import { initGenerator } from '../init/init'; + +interface NormalizedSchema extends Schema { + appProjectRoot: string; + parsedTags: string[]; +} + +function getBuildConfig( + project: ProjectConfiguration, + options: NormalizedSchema +): TargetConfiguration { + return { + executor: '@nrwl/node:build', + outputs: ['{options.outputPath}'], + options: { + outputPath: joinPathFragments('dist', options.appProjectRoot), + main: joinPathFragments( + project.sourceRoot, + 'main' + (options.js ? '.js' : '.ts') + ), + tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), + assets: [joinPathFragments(project.sourceRoot, 'assets')], + }, + configurations: { + production: { + optimization: true, + extractLicenses: true, + inspect: false, + fileReplacements: [ + { + replace: joinPathFragments( + project.sourceRoot, + 'environments/environment' + (options.js ? '.js' : '.ts') + ), + with: joinPathFragments( + project.sourceRoot, + 'environments/environment.prod' + (options.js ? '.js' : '.ts') + ), + }, + ], + }, + }, + }; +} + +function getServeConfig(options: NormalizedSchema): TargetConfiguration { + return { + executor: '@nrwl/node:execute', + options: { + buildTarget: `${options.name}:build`, + }, + }; +} + +function addProject(tree: Tree, options: NormalizedSchema) { + const project: ProjectConfiguration & NxJsonProjectConfiguration = { + root: options.appProjectRoot, + sourceRoot: join(options.appProjectRoot, 'src'), + projectType: 'application', + targets: {}, + tags: options.parsedTags, + }; + project.targets.build = getBuildConfig(project, options); + project.targets.serve = getServeConfig(options); + + addProjectConfiguration(tree, options.name, project); + + const workspace = readWorkspaceConfiguration(tree); + + if (!workspace.defaultProject) { + workspace.defaultProject = options.name; + updateWorkspaceConfiguration(tree, workspace); + } +} + +function addAppFiles(tree: Tree, options: NormalizedSchema) { + generateFiles(tree, join(__dirname, './files/app'), options.appProjectRoot, { + tmpl: '', + name: options.name, + root: options.appProjectRoot, + offset: offsetFromRoot(options.appProjectRoot), + }); + if (options.js) { + toJS(tree); + } + if (options.pascalCaseFiles) { + logger.warn('NOTE: --pascalCaseFiles is a noop'); + } +} + +function addProxy(tree: Tree, options: NormalizedSchema) { + const projectConfig = readProjectConfiguration(tree, options.frontendProject); + if (projectConfig.targets && projectConfig.targets.serve) { + const pathToProxyFile = `${projectConfig.root}/proxy.conf.json`; + projectConfig.targets.serve.options.proxyConfig = pathToProxyFile; + + if (!tree.exists(pathToProxyFile)) { + tree.write( + pathToProxyFile, + JSON.stringify( + { + '/api': { + target: 'http://localhost:3333', + secure: false, + }, + }, + null, + 2 + ) + ); + } else { + //add new entry to existing config + const proxyFileContent = tree.read(pathToProxyFile).toString(); + + const proxyModified = { + ...JSON.parse(proxyFileContent), + [`/${options.name}-api`]: { + target: 'http://localhost:3333', + secure: false, + }, + }; + + tree.write(pathToProxyFile, JSON.stringify(proxyModified, null, 2)); + } + + updateProjectConfiguration(tree, options.frontendProject, projectConfig); + } +} + +export async function applicationGenerator(tree: Tree, schema: Schema) { + const options = normalizeOptions(tree, schema); + + const tasks: GeneratorCallback[] = []; + const initTask = await initGenerator(tree, { + ...options, + skipFormat: true, + }); + tasks.push(initTask); + + addAppFiles(tree, options); + addProject(tree, options); + + if (options.linter !== Linter.None) { + const lintTask = await lintProjectGenerator(tree, { + linter: options.linter, + project: options.name, + tsConfigPaths: [ + joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), + ], + eslintFilePatterns: [`${options.appProjectRoot}/**/*.ts`], + skipFormat: true, + }); + tasks.push(lintTask); + } + + if (options.unitTestRunner === 'jest') { + const jestTask = await jestProjectGenerator(tree, { + project: options.name, + setupFile: 'none', + skipSerializers: true, + supportTsx: options.js, + babelJest: options.babelJest, + }); + tasks.push(jestTask); + } + if (options.js) { + updateTsConfigsToJs(tree, { projectRoot: options.appProjectRoot }); + } + + if (options.frontendProject) { + addProxy(tree, options); + } + + if (!options.skipFormat) { + await formatFiles(tree); + } + + return runTasksInSerial(...tasks); +} + +function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { + const { appsDir } = getWorkspaceLayout(host); + + const appDirectory = options.directory + ? `${names(options.directory).fileName}/${names(options.name).fileName}` + : names(options.name).fileName; + + const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-'); + + const appProjectRoot = joinPathFragments(appsDir, appDirectory); + + const parsedTags = options.tags + ? options.tags.split(',').map((s) => s.trim()) + : []; + + return { + ...options, + name: names(appProjectName).fileName, + frontendProject: options.frontendProject + ? names(options.frontendProject).fileName + : undefined, + appProjectRoot, + parsedTags, + linter: options.linter ?? Linter.EsLint, + unitTestRunner: options.unitTestRunner ?? 'jest', + }; +} + +export default applicationGenerator; +export const applicationSchematic = convertNxGenerator(applicationGenerator); diff --git a/packages/node/src/schematics/application/files/app/src/app/.gitkeep b/packages/node/src/generators/application/files/app/src/app/.gitkeep similarity index 100% rename from packages/node/src/schematics/application/files/app/src/app/.gitkeep rename to packages/node/src/generators/application/files/app/src/app/.gitkeep diff --git a/packages/node/src/schematics/application/files/app/src/assets/.gitkeep b/packages/node/src/generators/application/files/app/src/assets/.gitkeep similarity index 100% rename from packages/node/src/schematics/application/files/app/src/assets/.gitkeep rename to packages/node/src/generators/application/files/app/src/assets/.gitkeep diff --git a/packages/node/src/schematics/application/files/app/src/environments/environment.prod.ts__tmpl__ b/packages/node/src/generators/application/files/app/src/environments/environment.prod.ts__tmpl__ similarity index 100% rename from packages/node/src/schematics/application/files/app/src/environments/environment.prod.ts__tmpl__ rename to packages/node/src/generators/application/files/app/src/environments/environment.prod.ts__tmpl__ diff --git a/packages/node/src/schematics/application/files/app/src/environments/environment.ts__tmpl__ b/packages/node/src/generators/application/files/app/src/environments/environment.ts__tmpl__ similarity index 100% rename from packages/node/src/schematics/application/files/app/src/environments/environment.ts__tmpl__ rename to packages/node/src/generators/application/files/app/src/environments/environment.ts__tmpl__ diff --git a/packages/node/src/schematics/application/files/app/src/main.ts__tmpl__ b/packages/node/src/generators/application/files/app/src/main.ts__tmpl__ similarity index 100% rename from packages/node/src/schematics/application/files/app/src/main.ts__tmpl__ rename to packages/node/src/generators/application/files/app/src/main.ts__tmpl__ diff --git a/packages/node/src/schematics/application/files/app/tsconfig.app.json b/packages/node/src/generators/application/files/app/tsconfig.app.json similarity index 100% rename from packages/node/src/schematics/application/files/app/tsconfig.app.json rename to packages/node/src/generators/application/files/app/tsconfig.app.json diff --git a/packages/node/src/schematics/application/files/app/tsconfig.json b/packages/node/src/generators/application/files/app/tsconfig.json similarity index 100% rename from packages/node/src/schematics/application/files/app/tsconfig.json rename to packages/node/src/generators/application/files/app/tsconfig.json diff --git a/packages/node/src/generators/application/schema.d.ts b/packages/node/src/generators/application/schema.d.ts new file mode 100644 index 0000000000..d05927ab6b --- /dev/null +++ b/packages/node/src/generators/application/schema.d.ts @@ -0,0 +1,15 @@ +import { Linter } from '@nrwl/linter'; + +export interface Schema { + name: string; + skipFormat?: boolean; + skipPackageJson?: boolean; + directory?: string; + unitTestRunner?: 'jest' | 'none'; + linter?: Linter; + tags?: string; + frontendProject?: string; + babelJest?: boolean; + js?: boolean; + pascalCaseFiles?: boolean; +} diff --git a/packages/node/src/schematics/application/schema.json b/packages/node/src/generators/application/schema.json similarity index 99% rename from packages/node/src/schematics/application/schema.json rename to packages/node/src/generators/application/schema.json index f77fdc014c..0de85eb01a 100644 --- a/packages/node/src/schematics/application/schema.json +++ b/packages/node/src/generators/application/schema.json @@ -1,5 +1,6 @@ { "$schema": "http://json-schema.org/schema", + "cli": "nx", "id": "SchematicsNxNodeApp", "title": "Nx Application Options Schema", "type": "object", diff --git a/packages/node/src/generators/init/init.spec.ts b/packages/node/src/generators/init/init.spec.ts new file mode 100644 index 0000000000..e8a8efcc6f --- /dev/null +++ b/packages/node/src/generators/init/init.spec.ts @@ -0,0 +1,49 @@ +import { addDependenciesToPackageJson, readJson, Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; + +import { nxVersion } from '../../utils/versions'; +import { initGenerator } from './init'; + +describe('init', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add dependencies', async () => { + const existing = 'existing'; + const existingVersion = '1.0.0'; + + addDependenciesToPackageJson( + tree, + { + '@nrwl/node': nxVersion, + [existing]: existingVersion, + }, + { + [existing]: existingVersion, + } + ); + await initGenerator(tree, {}); + + const packageJson = readJson(tree, 'package.json'); + expect(packageJson.dependencies['@nrwl/node']).toBeUndefined(); + expect(packageJson.dependencies[existing]).toBeDefined(); + expect(packageJson.devDependencies['@nrwl/node']).toBeDefined(); + expect(packageJson.devDependencies[existing]).toBeDefined(); + }); + + describe('defaultCollection', () => { + it('should be set if none was set before', async () => { + await initGenerator(tree, {}); + const workspaceJson = readJson(tree, 'workspace.json'); + expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/node'); + }); + }); + + it('should not add jest config if unitTestRunner is none', async () => { + await initGenerator(tree, { unitTestRunner: 'none' }); + expect(tree.exists('jest.config.js')).toEqual(false); + }); +}); diff --git a/packages/node/src/generators/init/init.ts b/packages/node/src/generators/init/init.ts new file mode 100644 index 0000000000..ab38c3be30 --- /dev/null +++ b/packages/node/src/generators/init/init.ts @@ -0,0 +1,53 @@ +import { + addDependenciesToPackageJson, + convertNxGenerator, + formatFiles, + GeneratorCallback, + Tree, + updateJson, +} from '@nrwl/devkit'; +import { nxVersion } from '../../utils/versions'; +import { Schema } from './schema'; +import { setDefaultCollection } from '@nrwl/workspace/src/utilities/set-default-collection'; +import { jestInitGenerator } from '@nrwl/jest'; + +function updateDependencies(tree: Tree) { + updateJson(tree, 'package.json', (json) => { + delete json.dependencies['@nrwl/node']; + return json; + }); + + return addDependenciesToPackageJson(tree, {}, { '@nrwl/node': nxVersion }); +} + +function normalizeOptions(schema: Schema) { + return { + ...schema, + unitTestRunner: schema.unitTestRunner ?? 'jest', + }; +} + +export async function initGenerator(tree: Tree, schema: Schema) { + const options = normalizeOptions(schema); + + setDefaultCollection(tree, '@nrwl/node'); + + let jestInstall: GeneratorCallback; + if (options.unitTestRunner === 'jest') { + jestInstall = await jestInitGenerator(tree, {}); + } + const installTask = await updateDependencies(tree); + if (!options.skipFormat) { + await formatFiles(tree); + } + + return async () => { + if (jestInstall) { + await jestInstall(); + } + await installTask(); + }; +} + +export default initGenerator; +export const initSchematic = convertNxGenerator(initGenerator); diff --git a/packages/node/src/generators/init/schema.d.ts b/packages/node/src/generators/init/schema.d.ts new file mode 100644 index 0000000000..dde6ab2877 --- /dev/null +++ b/packages/node/src/generators/init/schema.d.ts @@ -0,0 +1,4 @@ +export interface Schema { + unitTestRunner?: 'jest' | 'none'; + skipFormat?: boolean; +} diff --git a/packages/node/src/schematics/init/schema.json b/packages/node/src/generators/init/schema.json similarity index 96% rename from packages/node/src/schematics/init/schema.json rename to packages/node/src/generators/init/schema.json index d50dd69ae5..e63e0eefba 100644 --- a/packages/node/src/schematics/init/schema.json +++ b/packages/node/src/generators/init/schema.json @@ -1,5 +1,6 @@ { "$schema": "http://json-schema.org/schema", + "cli": "nx", "id": "NxNodeInit", "title": "Init Node Plugin", "type": "object", diff --git a/packages/node/src/schematics/library/files/lib/package.json__tmpl__ b/packages/node/src/generators/library/files/lib/package.json__tmpl__ similarity index 100% rename from packages/node/src/schematics/library/files/lib/package.json__tmpl__ rename to packages/node/src/generators/library/files/lib/package.json__tmpl__ diff --git a/packages/node/src/schematics/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ b/packages/node/src/generators/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ similarity index 100% rename from packages/node/src/schematics/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ rename to packages/node/src/generators/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ diff --git a/packages/node/src/schematics/library/files/lib/src/lib/__fileName__.ts__tmpl__ b/packages/node/src/generators/library/files/lib/src/lib/__fileName__.ts__tmpl__ similarity index 100% rename from packages/node/src/schematics/library/files/lib/src/lib/__fileName__.ts__tmpl__ rename to packages/node/src/generators/library/files/lib/src/lib/__fileName__.ts__tmpl__ diff --git a/packages/node/src/schematics/library/files/lib/tsconfig.lib.json b/packages/node/src/generators/library/files/lib/tsconfig.lib.json similarity index 100% rename from packages/node/src/schematics/library/files/lib/tsconfig.lib.json rename to packages/node/src/generators/library/files/lib/tsconfig.lib.json diff --git a/packages/node/src/schematics/library/library.spec.ts b/packages/node/src/generators/library/library.spec.ts similarity index 61% rename from packages/node/src/schematics/library/library.spec.ts rename to packages/node/src/generators/library/library.spec.ts index 593d328a60..320549cfcb 100644 --- a/packages/node/src/schematics/library/library.spec.ts +++ b/packages/node/src/generators/library/library.spec.ts @@ -1,21 +1,20 @@ -import { Tree } from '@angular-devkit/schematics'; -import { NxJson, readJsonInTree } from '@nrwl/workspace'; -import { createEmptyWorkspace } from '@nrwl/workspace/testing'; -import { runSchematic } from '../../utils/testing'; +import { NxJsonConfiguration, readJson, Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; + import { Schema } from './schema.d'; +import { libraryGenerator } from './library'; describe('lib', () => { - let appTree: Tree; + let tree: Tree; beforeEach(() => { - appTree = Tree.empty(); - appTree = createEmptyWorkspace(appTree); + tree = createTreeWithEmptyWorkspace(); }); describe('not nested', () => { it('should update workspace.json', async () => { - const tree = await runSchematic('lib', { name: 'myLib' }, appTree); - const workspaceJson = readJsonInTree(tree, '/workspace.json'); + await libraryGenerator(tree, { name: 'myLib' }); + const workspaceJson = readJson(tree, '/workspace.json'); expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); expect(workspaceJson.projects['my-lib'].architect.build).toBeUndefined(); expect(workspaceJson.projects['my-lib'].architect.lint).toEqual({ @@ -35,12 +34,12 @@ describe('lib', () => { }); it('adds srcRootForCompilationRoot in workspace.json', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', rootDir: './src', buildable: true }, - appTree - ); - const workspaceJson = readJsonInTree(tree, '/workspace.json'); + await libraryGenerator(tree, { + name: 'myLib', + rootDir: './src', + buildable: true, + }); + const workspaceJson = readJson(tree, '/workspace.json'); expect( workspaceJson.projects['my-lib'].architect.build.options .srcRootForCompilationRoot @@ -48,12 +47,8 @@ describe('lib', () => { }); it('should update nx.json', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', tags: 'one,two' }, - appTree - ); - const nxJson = readJsonInTree(tree, '/nx.json'); + await libraryGenerator(tree, { name: 'myLib', tags: 'one,two' }); + const nxJson = readJson(tree, '/nx.json'); expect(nxJson.projects).toEqual({ 'my-lib': { tags: ['one', 'two'], @@ -62,16 +57,16 @@ describe('lib', () => { }); it('should update root tsconfig.base.json', async () => { - const tree = await runSchematic('lib', { name: 'myLib' }, appTree); - const tsconfigJson = readJsonInTree(tree, '/tsconfig.base.json'); + await libraryGenerator(tree, { name: 'myLib' }); + const tsconfigJson = readJson(tree, '/tsconfig.base.json'); expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([ 'libs/my-lib/src/index.ts', ]); }); it('should create a local tsconfig.json', async () => { - const tree = await runSchematic('lib', { name: 'myLib' }, appTree); - const tsconfigJson = readJsonInTree(tree, 'libs/my-lib/tsconfig.json'); + await libraryGenerator(tree, { name: 'myLib' }); + const tsconfigJson = readJson(tree, 'libs/my-lib/tsconfig.json'); expect(tsconfigJson).toMatchInlineSnapshot(` Object { "extends": "../../tsconfig.base.json", @@ -90,30 +85,24 @@ describe('lib', () => { }); it('should extend the local tsconfig.json with tsconfig.spec.json', async () => { - const tree = await runSchematic('lib', { name: 'myLib' }, appTree); - const tsconfigJson = readJsonInTree( - tree, - 'libs/my-lib/tsconfig.spec.json' - ); + await libraryGenerator(tree, { name: 'myLib' }); + const tsconfigJson = readJson(tree, 'libs/my-lib/tsconfig.spec.json'); expect(tsconfigJson.extends).toEqual('./tsconfig.json'); }); it('should extend the local tsconfig.json with tsconfig.lib.json', async () => { - const tree = await runSchematic('lib', { name: 'myLib' }, appTree); - const tsconfigJson = readJsonInTree( - tree, - 'libs/my-lib/tsconfig.lib.json' - ); + await libraryGenerator(tree, { name: 'myLib' }); + const tsconfigJson = readJson(tree, 'libs/my-lib/tsconfig.lib.json'); expect(tsconfigJson.compilerOptions.types).toContain('node'); expect(tsconfigJson.extends).toEqual('./tsconfig.json'); }); it('should generate files', async () => { - const tree = await runSchematic('lib', { name: 'myLib' }, appTree); + await libraryGenerator(tree, { name: 'myLib' }); expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy(); expect(tree.exists('libs/my-lib/src/index.ts')).toBeTruthy(); - const eslintrc = readJsonInTree(tree, 'libs/my-lib/.eslintrc.json'); + const eslintrc = readJson(tree, 'libs/my-lib/.eslintrc.json'); expect(eslintrc).toMatchInlineSnapshot(` Object { "extends": Array [ @@ -159,32 +148,24 @@ describe('lib', () => { describe('nested', () => { it('should update nx.json', async () => { - const tree = await runSchematic( - 'lib', - { - name: 'myLib', - directory: 'myDir', - tags: 'one', - }, - appTree - ); - const nxJson = readJsonInTree(tree, '/nx.json'); + await libraryGenerator(tree, { + name: 'myLib', + directory: 'myDir', + tags: 'one', + }); + const nxJson = readJson(tree, '/nx.json'); expect(nxJson.projects).toEqual({ 'my-dir-my-lib': { tags: ['one'], }, }); - const tree2 = await runSchematic( - 'lib', - { - name: 'myLib2', - directory: 'myDir', - tags: 'one,two', - }, - tree - ); - const nxJson2 = readJsonInTree(tree2, '/nx.json'); + await libraryGenerator(tree, { + name: 'myLib2', + directory: 'myDir', + tags: 'one,two', + }); + const nxJson2 = readJson(tree, '/nx.json'); expect(nxJson2.projects).toEqual({ 'my-dir-my-lib': { tags: ['one'], @@ -196,22 +177,14 @@ describe('lib', () => { }); it('should generate files', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', directory: 'myDir' }, - appTree - ); + await libraryGenerator(tree, { name: 'myLib', directory: 'myDir' }); expect(tree.exists(`libs/my-dir/my-lib/jest.config.js`)).toBeTruthy(); expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy(); }); it('should update workspace.json', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', directory: 'myDir' }, - appTree - ); - const workspaceJson = readJsonInTree(tree, '/workspace.json'); + await libraryGenerator(tree, { name: 'myLib', directory: 'myDir' }); + const workspaceJson = readJson(tree, '/workspace.json'); expect(workspaceJson.projects['my-dir-my-lib'].root).toEqual( 'libs/my-dir/my-lib' @@ -225,12 +198,8 @@ describe('lib', () => { }); it('should update tsconfig.json', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', directory: 'myDir' }, - appTree - ); - const tsconfigJson = readJsonInTree(tree, '/tsconfig.base.json'); + await libraryGenerator(tree, { name: 'myLib', directory: 'myDir' }); + const tsconfigJson = readJson(tree, '/tsconfig.base.json'); expect( tsconfigJson.compilerOptions.paths['@proj/my-dir/my-lib'] ).toEqual(['libs/my-dir/my-lib/src/index.ts']); @@ -243,15 +212,11 @@ describe('lib', () => { expect.assertions(1); try { - const tree = await runSchematic( - 'lib', - { - name: 'myLib', - directory: 'myDir', - publishable: true, - }, - appTree - ); + await libraryGenerator(tree, { + name: 'myLib', + directory: 'myDir', + publishable: true, + }); } catch (e) { expect(e.message).toContain( 'For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)' @@ -260,16 +225,9 @@ describe('lib', () => { }); it('should create a local tsconfig.json', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', directory: 'myDir' }, - appTree - ); + await libraryGenerator(tree, { name: 'myLib', directory: 'myDir' }); - const tsconfigJson = readJsonInTree( - tree, - 'libs/my-dir/my-lib/tsconfig.json' - ); + const tsconfigJson = readJson(tree, 'libs/my-dir/my-lib/tsconfig.json'); expect(tsconfigJson.extends).toEqual('../../../tsconfig.base.json'); expect(tsconfigJson.references).toEqual([ { @@ -284,20 +242,13 @@ describe('lib', () => { describe('--unit-test-runner none', () => { it('should not generate test configuration', async () => { - const resultTree = await runSchematic( - 'lib', - { name: 'myLib', unitTestRunner: 'none' }, - appTree - ); - expect(resultTree.exists('libs/my-lib/tsconfig.spec.json')).toBeFalsy(); - expect(resultTree.exists('libs/my-lib/jest.config.js')).toBeFalsy(); - expect(resultTree.exists('libs/my-lib/lib/my-lib.spec.ts')).toBeFalsy(); - const workspaceJson = readJsonInTree(resultTree, 'workspace.json'); + await libraryGenerator(tree, { name: 'myLib', unitTestRunner: 'none' }); + expect(tree.exists('libs/my-lib/tsconfig.spec.json')).toBeFalsy(); + expect(tree.exists('libs/my-lib/jest.config.js')).toBeFalsy(); + expect(tree.exists('libs/my-lib/lib/my-lib.spec.ts')).toBeFalsy(); + const workspaceJson = readJson(tree, 'workspace.json'); expect(workspaceJson.projects['my-lib'].architect.test).toBeUndefined(); - const tsconfigJson = readJsonInTree( - resultTree, - 'libs/my-lib/tsconfig.json' - ); + const tsconfigJson = readJson(tree, 'libs/my-lib/tsconfig.json'); expect(tsconfigJson.extends).toEqual('../../tsconfig.base.json'); expect(tsconfigJson.references).toEqual([ { @@ -320,12 +271,8 @@ describe('lib', () => { describe('buildable package', () => { it('should have a builder defined', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', buildable: true }, - appTree - ); - const workspaceJson = readJsonInTree(tree, '/workspace.json'); + await libraryGenerator(tree, { name: 'myLib', buildable: true }); + const workspaceJson = readJson(tree, '/workspace.json'); expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); @@ -352,12 +299,12 @@ describe('lib', () => { describe('publishable package', () => { it('should have a builder defined', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', publishable: true, importPath: '@proj/mylib' }, - appTree - ); - const workspaceJson = readJsonInTree(tree, '/workspace.json'); + await libraryGenerator(tree, { + name: 'myLib', + publishable: true, + importPath: '@proj/mylib', + }); + const workspaceJson = readJson(tree, '/workspace.json'); expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); @@ -365,16 +312,13 @@ describe('lib', () => { }); it('should update package.json', async () => { - const publishableTree = await runSchematic( - 'lib', - { name: 'mylib', publishable: true, importPath: '@proj/mylib' }, - appTree - ); + await libraryGenerator(tree, { + name: 'mylib', + publishable: true, + importPath: '@proj/mylib', + }); - let packageJsonContent = readJsonInTree( - publishableTree, - 'libs/mylib/package.json' - ); + let packageJsonContent = readJson(tree, 'libs/mylib/package.json'); expect(packageJsonContent.name).toEqual('@proj/mylib'); }); @@ -382,21 +326,14 @@ describe('lib', () => { describe('--importPath', () => { it('should update the package.json & tsconfig with the given import path', async () => { - const tree = await runSchematic( - 'lib', - { - name: 'myLib', - publishable: true, - directory: 'myDir', - importPath: '@myorg/lib', - }, - appTree - ); - const packageJson = readJsonInTree( - tree, - 'libs/my-dir/my-lib/package.json' - ); - const tsconfigJson = readJsonInTree(tree, '/tsconfig.base.json'); + await libraryGenerator(tree, { + name: 'myLib', + publishable: true, + directory: 'myDir', + importPath: '@myorg/lib', + }); + const packageJson = readJson(tree, 'libs/my-dir/my-lib/package.json'); + const tsconfigJson = readJson(tree, '/tsconfig.base.json'); expect(packageJson.name).toBe('@myorg/lib'); expect( @@ -405,26 +342,18 @@ describe('lib', () => { }); it('should fail if the same importPath has already been used', async () => { - const tree1 = await runSchematic( - 'lib', - { - name: 'myLib1', - publishable: true, - importPath: '@myorg/lib', - }, - appTree - ); + await libraryGenerator(tree, { + name: 'myLib1', + publishable: true, + importPath: '@myorg/lib', + }); try { - await runSchematic( - 'lib', - { - name: 'myLib2', - publishable: true, - importPath: '@myorg/lib', - }, - tree1 - ); + await libraryGenerator(tree, { + name: 'myLib2', + publishable: true, + importPath: '@myorg/lib', + }); } catch (e) { expect(e.message).toContain( 'You already have a library using the import path' @@ -437,22 +366,21 @@ describe('lib', () => { describe(`--babelJest`, () => { it('should use babel for jest', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', babelJest: true } as Schema, - appTree - ); + await libraryGenerator(tree, { + name: 'myLib', + babelJest: true, + } as Schema); - expect(tree.readContent(`libs/my-lib/jest.config.js`)) + expect(tree.read(`libs/my-lib/jest.config.js`).toString()) .toMatchInlineSnapshot(` "module.exports = { displayName: 'my-lib', preset: '../../jest.preset.js', transform: { - '^.+\\\\\\\\.[tj]sx?$': 'babel-jest', + '^.+\\\\\\\\.[tj]sx?$': 'babel-jest' }, - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], - coverageDirectory: '../../coverage/libs/my-lib', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/my-lib' }; " `); @@ -460,14 +388,10 @@ describe('lib', () => { }); describe('--js flag', () => { it('should generate js files instead of ts files', async () => { - const tree = await runSchematic( - 'lib', - { - name: 'myLib', - js: true, - } as Schema, - appTree - ); + await libraryGenerator(tree, { + name: 'myLib', + js: true, + } as Schema); expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy(); expect(tree.exists('libs/my-lib/src/index.js')).toBeTruthy(); @@ -475,37 +399,35 @@ describe('lib', () => { expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.js')).toBeTruthy(); expect( - readJsonInTree(tree, 'libs/my-lib/tsconfig.json').compilerOptions + readJson(tree, 'libs/my-lib/tsconfig.json').compilerOptions ).toEqual({ allowJs: true, }); - expect( - readJsonInTree(tree, 'libs/my-lib/tsconfig.lib.json').include - ).toEqual(['**/*.ts', '**/*.js']); - expect( - readJsonInTree(tree, 'libs/my-lib/tsconfig.lib.json').exclude - ).toEqual(['**/*.spec.ts', '**/*.spec.js']); + expect(readJson(tree, 'libs/my-lib/tsconfig.lib.json').include).toEqual([ + '**/*.ts', + '**/*.js', + ]); + expect(readJson(tree, 'libs/my-lib/tsconfig.lib.json').exclude).toEqual([ + '**/*.spec.ts', + '**/*.spec.js', + ]); }); it('should update root tsconfig.json with a js file path', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', js: true } as Schema, - appTree - ); - const tsconfigJson = readJsonInTree(tree, '/tsconfig.base.json'); + await libraryGenerator(tree, { name: 'myLib', js: true } as Schema); + const tsconfigJson = readJson(tree, '/tsconfig.base.json'); expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([ 'libs/my-lib/src/index.js', ]); }); it('should update architect builder when --buildable', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', buildable: true, js: true } as Schema, - appTree - ); - const workspaceJson = readJsonInTree(tree, '/workspace.json'); + await libraryGenerator(tree, { + name: 'myLib', + buildable: true, + js: true, + } as Schema); + const workspaceJson = readJson(tree, '/workspace.json'); expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); @@ -515,11 +437,11 @@ describe('lib', () => { }); it('should generate js files for nested libs as well', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', directory: 'myDir', js: true } as Schema, - appTree - ); + await libraryGenerator(tree, { + name: 'myLib', + directory: 'myDir', + js: true, + } as Schema); expect(tree.exists(`libs/my-dir/my-lib/jest.config.js`)).toBeTruthy(); expect(tree.exists('libs/my-dir/my-lib/src/index.js')).toBeTruthy(); expect( @@ -533,21 +455,20 @@ describe('lib', () => { describe('--pascalCaseFiles', () => { it('should generate files with upper case names', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', pascalCaseFiles: true } as Schema, - appTree - ); + await libraryGenerator(tree, { + name: 'myLib', + pascalCaseFiles: true, + } as Schema); expect(tree.exists('libs/my-lib/src/lib/MyLib.ts')).toBeTruthy(); expect(tree.exists('libs/my-lib/src/lib/MyLib.spec.ts')).toBeTruthy(); }); it('should generate files with upper case names for nested libs as well', async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', directory: 'myDir', pascalCaseFiles: true } as Schema, - appTree - ); + await libraryGenerator(tree, { + name: 'myLib', + directory: 'myDir', + pascalCaseFiles: true, + } as Schema); expect( tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.ts') ).toBeTruthy(); diff --git a/packages/node/src/generators/library/library.ts b/packages/node/src/generators/library/library.ts new file mode 100644 index 0000000000..697a5d42b7 --- /dev/null +++ b/packages/node/src/generators/library/library.ts @@ -0,0 +1,140 @@ +import { + convertNxGenerator, + formatFiles, + generateFiles, + getWorkspaceLayout, + joinPathFragments, + names, + offsetFromRoot, + readProjectConfiguration, + toJS, + Tree, + updateProjectConfiguration, + updateTsConfigsToJs, +} from '@nrwl/devkit'; + +import { Schema } from './schema'; +import { libraryGenerator as workspaceLibraryGenerator } from '@nrwl/workspace/generators'; +import { join } from 'path'; + +export interface NormalizedSchema extends Schema { + name: string; + prefix: string; + fileName: string; + projectRoot: string; + projectDirectory: string; + parsedTags: string[]; +} + +export async function libraryGenerator(tree: Tree, schema: Schema) { + const options = normalizeOptions(tree, schema); + + if (options.publishable === true && !schema.importPath) { + throw new Error( + `For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)` + ); + } + + const libraryInstall = await workspaceLibraryGenerator(tree, { + ...schema, + importPath: options.importPath, + }); + createFiles(tree, options); + + if (options.js) { + updateTsConfigsToJs(tree, options); + } + updateProject(tree, options); + await formatFiles(tree); + + return libraryInstall; +} + +export default libraryGenerator; +export const librarySchematic = convertNxGenerator(libraryGenerator); + +function normalizeOptions(tree: Tree, options: Schema): NormalizedSchema { + const { npmScope, libsDir } = getWorkspaceLayout(tree); + const defaultPrefix = npmScope; + const name = names(options.name).fileName; + const projectDirectory = options.directory + ? `${names(options.directory).fileName}/${name}` + : name; + + const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); + const fileName = projectName; + const projectRoot = joinPathFragments(libsDir, projectDirectory); + + const parsedTags = options.tags + ? options.tags.split(',').map((s) => s.trim()) + : []; + + const importPath = + options.importPath || `@${defaultPrefix}/${projectDirectory}`; + + return { + ...options, + prefix: defaultPrefix, // we could also allow customizing this + fileName, + name: projectName, + projectRoot, + projectDirectory, + parsedTags, + importPath, + }; +} + +function createFiles(tree: Tree, options: NormalizedSchema) { + const nameFormats = names(options.name); + return generateFiles( + tree, + join(__dirname, './files/lib'), + options.projectRoot, + { + ...options, + ...nameFormats, + tmpl: '', + offsetFromRoot: offsetFromRoot(options.projectRoot), + } + ); + + if (options.unitTestRunner === 'none') { + tree.delete( + join(options.projectRoot, `./src/lib/${nameFormats.fileName}.spec.ts`) + ); + } + if (!options.publishable && !options.buildable) { + tree.delete(join(options.projectRoot, 'package.json')); + } + if (options.js) { + toJS(tree); + } +} + +function updateProject(tree: Tree, options: NormalizedSchema) { + if (!options.publishable && !options.buildable) { + return; + } + + const project = readProjectConfiguration(tree, options.name); + const { libsDir } = getWorkspaceLayout(tree); + + project.targets = project.targets || {}; + project.targets.build = { + executor: '@nrwl/node:package', + outputs: ['{options.outputPath}'], + options: { + outputPath: `dist/${libsDir}/${options.projectDirectory}`, + tsConfig: `${options.projectRoot}/tsconfig.lib.json`, + packageJson: `${options.projectRoot}/package.json`, + main: `${options.projectRoot}/src/index` + (options.js ? '.js' : '.ts'), + assets: [`${options.projectRoot}/*.md`], + }, + }; + + if (options.rootDir) { + project.targets.build.options.srcRootForCompilationRoot = options.rootDir; + } + + updateProjectConfiguration(tree, options.name, project); +} diff --git a/packages/node/src/schematics/library/schema.d.ts b/packages/node/src/generators/library/schema.d.ts similarity index 53% rename from packages/node/src/schematics/library/schema.d.ts rename to packages/node/src/generators/library/schema.d.ts index bbc6c9ec2d..e452a9b8a9 100644 --- a/packages/node/src/schematics/library/schema.d.ts +++ b/packages/node/src/generators/library/schema.d.ts @@ -3,18 +3,18 @@ import { Linter } from '@nrwl/workspace'; export interface Schema { name: string; directory?: string; - skipTsConfig: boolean; - skipFormat: boolean; + skipTsConfig?: boolean; + skipFormat?: boolean; tags?: string; - unitTestRunner: 'jest' | 'none'; - linter: Linter; + unitTestRunner?: 'jest' | 'none'; + linter?: Linter; buildable?: boolean; publishable?: boolean; importPath?: string; - testEnvironment: 'jsdom' | 'node'; + testEnvironment?: 'jsdom' | 'node'; rootDir?: string; babelJest?: boolean; - js: boolean; - pascalCaseFiles: boolean; - strict: boolean; + js?: boolean; + pascalCaseFiles?: boolean; + strict?: boolean; } diff --git a/packages/node/src/schematics/library/schema.json b/packages/node/src/generators/library/schema.json similarity index 99% rename from packages/node/src/schematics/library/schema.json rename to packages/node/src/generators/library/schema.json index acdfc0a5d6..db93adf12a 100644 --- a/packages/node/src/schematics/library/schema.json +++ b/packages/node/src/generators/library/schema.json @@ -1,5 +1,6 @@ { "$schema": "http://json-schema.org/schema", + "cli": "nx", "id": "NxNodeLibrary", "title": "Create a Node Library for Nx", "type": "object", diff --git a/packages/node/src/schematics/application/application.ts b/packages/node/src/schematics/application/application.ts deleted file mode 100644 index eeb0dfc79b..0000000000 --- a/packages/node/src/schematics/application/application.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { - apply, - chain, - externalSchematic, - mergeWith, - move, - noop, - Rule, - SchematicContext, - template, - Tree, - url, -} from '@angular-devkit/schematics'; -import { join, normalize, Path } from '@angular-devkit/core'; -import { Schema } from './schema'; -import { - updateJsonInTree, - updateWorkspaceInTree, - generateProjectLint, - addLintFiles, - formatFiles, -} from '@nrwl/workspace'; -import { getProjectConfig } from '@nrwl/workspace'; -import init from '../init/init'; -import { appsDir } from '@nrwl/workspace/src/utils/ast-utils'; -import { - toJS, - updateTsConfigsToJs, - maybeJs, -} from '@nrwl/workspace/src/utils/rules/to-js'; -import { names, offsetFromRoot } from '@nrwl/devkit'; -import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter'; - -interface NormalizedSchema extends Schema { - appProjectRoot: Path; - parsedTags: string[]; -} - -function updateNxJson(options: NormalizedSchema): Rule { - return updateJsonInTree(`/nx.json`, (json) => { - return { - ...json, - projects: { - ...json.projects, - [options.name]: { tags: options.parsedTags }, - }, - }; - }); -} - -function getBuildConfig(project: any, options: NormalizedSchema) { - return { - builder: '@nrwl/node:build', - outputs: ['{options.outputPath}'], - options: { - outputPath: join(normalize('dist'), options.appProjectRoot), - main: maybeJs(options, join(project.sourceRoot, 'main.ts')), - tsConfig: join(options.appProjectRoot, 'tsconfig.app.json'), - assets: [join(project.sourceRoot, 'assets')], - }, - configurations: { - production: { - optimization: true, - extractLicenses: true, - inspect: false, - fileReplacements: [ - { - replace: maybeJs( - options, - join(project.sourceRoot, 'environments/environment.ts') - ), - with: maybeJs( - options, - join(project.sourceRoot, 'environments/environment.prod.ts') - ), - }, - ], - }, - }, - }; -} - -function getServeConfig(options: NormalizedSchema) { - return { - builder: '@nrwl/node:execute', - options: { - buildTarget: `${options.name}:build`, - }, - }; -} - -function updateWorkspaceJson(options: NormalizedSchema): Rule { - return updateWorkspaceInTree((workspaceJson) => { - const project = { - root: options.appProjectRoot, - sourceRoot: join(options.appProjectRoot, 'src'), - projectType: 'application', - prefix: options.name, - architect: {}, - }; - - project.architect.build = getBuildConfig(project, options); - project.architect.serve = getServeConfig(options); - project.architect.lint = generateProjectLint( - normalize(project.root), - join(normalize(project.root), 'tsconfig.app.json'), - options.linter, - [`${options.appProjectRoot}/**/*.${options.js ? 'js' : 'ts'}`] - ); - - workspaceJson.projects[options.name] = project; - - workspaceJson.defaultProject = workspaceJson.defaultProject || options.name; - - return workspaceJson; - }); -} - -function addAppFiles(options: NormalizedSchema): Rule { - return chain([ - mergeWith( - apply(url(`./files/app`), [ - template({ - tmpl: '', - name: options.name, - root: options.appProjectRoot, - offset: offsetFromRoot(options.appProjectRoot), - }), - move(options.appProjectRoot), - options.js ? toJS() : noop(), - ]) - ), - options.pascalCaseFiles - ? (tree, context) => { - context.logger.warn('NOTE: --pascalCaseFiles is a noop'); - return tree; - } - : noop(), - ]); -} - -function addProxy(options: NormalizedSchema): Rule { - return (host: Tree, context: SchematicContext) => { - const projectConfig = getProjectConfig(host, options.frontendProject); - if (projectConfig.architect && projectConfig.architect.serve) { - const pathToProxyFile = `${projectConfig.root}/proxy.conf.json`; - - if (!host.exists(pathToProxyFile)) { - host.create( - pathToProxyFile, - JSON.stringify( - { - '/api': { - target: 'http://localhost:3333', - secure: false, - }, - }, - null, - 2 - ) - ); - } else { - //add new entry to existing config - const proxyFileContent = host.get(pathToProxyFile).content.toString(); - - const proxyModified = { - ...JSON.parse(proxyFileContent), - [`/${options.name}-api`]: { - target: 'http://localhost:3333', - secure: false, - }, - }; - - host.overwrite(pathToProxyFile, JSON.stringify(proxyModified, null, 2)); - } - - updateWorkspaceInTree((json) => { - projectConfig.architect.serve.options.proxyConfig = pathToProxyFile; - json.projects[options.frontendProject] = projectConfig; - return json; - })(host, context); - } - }; -} - -function addJest(options: NormalizedSchema) { - return options.unitTestRunner === 'jest' - ? externalSchematic('@nrwl/jest', 'jest-project', { - project: options.name, - setupFile: 'none', - skipSerializers: true, - supportTsx: options.js, - babelJest: options.babelJest, - }) - : noop(); -} - -export default function (schema: Schema): Rule { - return (host: Tree, context: SchematicContext) => { - const options = normalizeOptions(host, schema); - return chain([ - init({ - ...options, - skipFormat: true, - }), - addLintFiles(options.appProjectRoot, options.linter), - addAppFiles(options), - options.js - ? updateTsConfigsToJs({ projectRoot: options.appProjectRoot }) - : noop, - updateWorkspaceJson(options), - updateNxJson(options), - addJest(options), - options.frontendProject ? addProxy(options) : noop(), - formatFiles(options), - ])(host, context); - }; -} - -function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { - const appDirectory = options.directory - ? `${names(options.directory).fileName}/${names(options.name).fileName}` - : names(options.name).fileName; - - const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-'); - - const appProjectRoot = join(normalize(appsDir(host)), appDirectory); - - const parsedTags = options.tags - ? options.tags.split(',').map((s) => s.trim()) - : []; - - return { - ...options, - name: names(appProjectName).fileName, - frontendProject: options.frontendProject - ? names(options.frontendProject).fileName - : undefined, - appProjectRoot, - parsedTags, - }; -} - -export const applicationGenerator = wrapAngularDevkitSchematic( - '@nrwl/node', - 'application' -); diff --git a/packages/node/src/schematics/application/schema.d.ts b/packages/node/src/schematics/application/schema.d.ts deleted file mode 100644 index 75a21542c2..0000000000 --- a/packages/node/src/schematics/application/schema.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Linter } from '@nrwl/workspace'; - -export interface Schema { - name: string; - skipFormat: boolean; - skipPackageJson: boolean; - directory?: string; - unitTestRunner: 'jest' | 'none'; - linter: Linter; - tags?: string; - frontendProject?: string; - babelJest?: boolean; - js: boolean; - pascalCaseFiles: boolean; -} diff --git a/packages/node/src/schematics/init/init.spec.ts b/packages/node/src/schematics/init/init.spec.ts deleted file mode 100644 index caa390e863..0000000000 --- a/packages/node/src/schematics/init/init.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Tree } from '@angular-devkit/schematics'; -import { addDepsToPackageJson, readJsonInTree } from '@nrwl/workspace'; -import { createEmptyWorkspace } from '@nrwl/workspace/testing'; -import { callRule, runSchematic } from '../../utils/testing'; -import { nxVersion } from '../../utils/versions'; - -describe('init', () => { - let tree: Tree; - - beforeEach(() => { - tree = Tree.empty(); - tree = createEmptyWorkspace(tree); - }); - - it('should add dependencies', async () => { - const existing = 'existing'; - const existingVersion = '1.0.0'; - await callRule( - addDepsToPackageJson( - { '@nrwl/node': nxVersion, [existing]: existingVersion }, - { [existing]: existingVersion }, - false - ), - tree - ); - const result = await runSchematic('init', {}, tree); - const packageJson = readJsonInTree(result, 'package.json'); - expect(packageJson.dependencies['@nrwl/node']).toBeUndefined(); - expect(packageJson.dependencies[existing]).toBeDefined(); - expect(packageJson.devDependencies['@nrwl/node']).toBeDefined(); - expect(packageJson.devDependencies[existing]).toBeDefined(); - }); - - describe('defaultCollection', () => { - it('should be set if none was set before', async () => { - const result = await runSchematic('init', {}, tree); - const workspaceJson = readJsonInTree(result, 'workspace.json'); - expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/node'); - }); - }); - - it('should not add jest config if unitTestRunner is none', async () => { - const result = await runSchematic( - 'init', - { - unitTestRunner: 'none', - }, - tree - ); - expect(result.exists('jest.config.js')).toEqual(false); - }); -}); diff --git a/packages/node/src/schematics/init/init.ts b/packages/node/src/schematics/init/init.ts deleted file mode 100644 index b7b67d3873..0000000000 --- a/packages/node/src/schematics/init/init.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { chain, noop, Rule } from '@angular-devkit/schematics'; -import { - addPackageWithInit, - formatFiles, - setDefaultCollection, - updateJsonInTree, -} from '@nrwl/workspace'; -import { nxVersion } from '../../utils/versions'; -import { Schema } from './schema'; - -function updateDependencies(): Rule { - return updateJsonInTree('package.json', (json) => { - delete json.dependencies['@nrwl/node']; - json.devDependencies['@nrwl/node'] = nxVersion; - return json; - }); -} - -export default function (schema: Schema) { - return chain([ - setDefaultCollection('@nrwl/node'), - schema.unitTestRunner === 'jest' - ? addPackageWithInit('@nrwl/jest') - : noop(), - updateDependencies(), - formatFiles(schema), - ]); -} diff --git a/packages/node/src/schematics/init/schema.d.ts b/packages/node/src/schematics/init/schema.d.ts deleted file mode 100644 index 52ac13000a..0000000000 --- a/packages/node/src/schematics/init/schema.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Schema { - unitTestRunner: 'jest' | 'none'; - skipFormat: boolean; -} diff --git a/packages/node/src/schematics/library/library.ts b/packages/node/src/schematics/library/library.ts deleted file mode 100644 index 9194bcebba..0000000000 --- a/packages/node/src/schematics/library/library.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { normalize, Path } from '@angular-devkit/core'; -import { - apply, - chain, - externalSchematic, - filter, - MergeStrategy, - mergeWith, - move, - noop, - Rule, - SchematicContext, - SchematicsException, - template, - Tree, - url, -} from '@angular-devkit/schematics'; -import { - formatFiles, - getNpmScope, - updateWorkspaceInTree, -} from '@nrwl/workspace'; -import { Schema } from './schema'; -import { libsDir } from '@nrwl/workspace/src/utils/ast-utils'; -import { - maybeJs, - toJS, - updateTsConfigsToJs, -} from '@nrwl/workspace/src/utils/rules/to-js'; -import { names, offsetFromRoot } from '@nrwl/devkit'; -import { wrapAngularDevkitSchematic } from '@nrwl/devkit/ngcli-adapter'; - -export interface NormalizedSchema extends Schema { - name: string; - prefix: string; - fileName: string; - projectRoot: Path; - projectDirectory: string; - parsedTags: string[]; -} - -export default function (schema: NormalizedSchema): Rule { - return (host: Tree, context: SchematicContext) => { - const options = normalizeOptions(host, schema); - - if (options.publishable === true && !schema.importPath) { - throw new SchematicsException( - `For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)` - ); - } - - return chain([ - externalSchematic('@nrwl/workspace', 'lib', { - ...schema, - importPath: options.importPath, - }), - createFiles(options), - options.js ? updateTsConfigsToJs(options) : noop(), - addProject(options), - formatFiles(options), - ]); - }; -} - -function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { - const defaultPrefix = getNpmScope(host); - const name = names(options.name).fileName; - const projectDirectory = options.directory - ? `${names(options.directory).fileName}/${name}` - : name; - - const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); - const fileName = projectName; - const projectRoot = normalize(`${libsDir(host)}/${projectDirectory}`); - - const parsedTags = options.tags - ? options.tags.split(',').map((s) => s.trim()) - : []; - - const importPath = - options.importPath || `@${defaultPrefix}/${projectDirectory}`; - - return { - ...options, - prefix: defaultPrefix, // we could also allow customizing this - fileName, - name: projectName, - projectRoot, - projectDirectory, - parsedTags, - importPath, - }; -} - -function createFiles(options: NormalizedSchema): Rule { - return mergeWith( - apply(url(`./files/lib`), [ - template({ - ...options, - ...names(options.name), - tmpl: '', - offsetFromRoot: offsetFromRoot(options.projectRoot), - }), - move(options.projectRoot), - options.unitTestRunner === 'none' - ? filter((file) => !file.endsWith('spec.ts')) - : noop(), - options.publishable || options.buildable - ? noop() - : filter((file) => !file.endsWith('package.json')), - options.js ? toJS() : noop(), - ]), - MergeStrategy.Overwrite - ); -} - -function addProject(options: NormalizedSchema): Rule { - if (!options.publishable && !options.buildable) { - return noop(); - } - - return updateWorkspaceInTree((json, context, host) => { - const { architect } = json.projects[options.name]; - if (architect) { - architect.build = { - builder: '@nrwl/node:package', - outputs: ['{options.outputPath}'], - options: { - outputPath: `dist/${libsDir(host)}/${options.projectDirectory}`, - tsConfig: `${options.projectRoot}/tsconfig.lib.json`, - packageJson: `${options.projectRoot}/package.json`, - main: maybeJs(options, `${options.projectRoot}/src/index.ts`), - assets: [`${options.projectRoot}/*.md`], - }, - }; - - if (options.rootDir) { - architect.build.options.srcRootForCompilationRoot = options.rootDir; - } - } - return json; - }); -} - -export const libraryGenerator = wrapAngularDevkitSchematic( - '@nrwl/node', - 'library' -);