From 2f34441e12a5f24ad2e86901c24c1565e6c5e86e Mon Sep 17 00:00:00 2001 From: Juri Date: Fri, 5 Jun 2020 15:39:29 +0200 Subject: [PATCH 1/5] fix(angular): require importPath for publishable libs & adjust buildable ISSUES CLOSED: #2794 --- .../angular/api-angular/schematics/library.md | 18 ++- docs/angular/api-react/schematics/library.md | 6 + docs/react/api-angular/schematics/library.md | 18 ++- e2e/angular/src/angular-package.test.ts | 6 +- e2e/workspace/src/workspace.test.ts | 16 +- .../library/lib/normalize-options.ts | 8 +- .../lib/update-lib-package-npm-scope.ts | 12 +- .../library/lib/update-ng-package.ts | 2 +- .../schematics/library/lib/update-project.ts | 4 +- .../schematics/library/lib/update-tsconfig.ts | 17 ++- .../src/schematics/library/library.spec.ts | 139 +++++++++++++++--- .../angular/src/schematics/library/library.ts | 13 +- .../src/schematics/library/schema.d.ts | 2 + .../src/schematics/library/schema.json | 12 +- packages/workspace/package.json | 3 +- .../src/schematics/library/library.spec.ts | 45 ++++++ .../src/schematics/library/library.ts | 1 + 17 files changed, 260 insertions(+), 62 deletions(-) diff --git a/docs/angular/api-angular/schematics/library.md b/docs/angular/api-angular/schematics/library.md index 2b5aa0917c4c1..167157e740516 100644 --- a/docs/angular/api-angular/schematics/library.md +++ b/docs/angular/api-angular/schematics/library.md @@ -36,12 +36,26 @@ Type: `boolean` Add a module spec file. +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + ### directory Type: `string` A directory where the lib is placed +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. + ### lazy Default: `false` @@ -72,13 +86,11 @@ The prefix to apply to generated selectors. ### publishable -Alias(es): buildable - Default: `false` Type: `boolean` -Generate a buildable library. +Generate a publishable library. ### routing diff --git a/docs/angular/api-react/schematics/library.md b/docs/angular/api-react/schematics/library.md index 8eb6c788ac24e..b0ba08c25ce25 100644 --- a/docs/angular/api-react/schematics/library.md +++ b/docs/angular/api-react/schematics/library.md @@ -66,6 +66,12 @@ Type: `string` A directory where the lib is placed. +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib + ### js Default: `false` diff --git a/docs/react/api-angular/schematics/library.md b/docs/react/api-angular/schematics/library.md index 0e42d88a65d72..313e538276381 100644 --- a/docs/react/api-angular/schematics/library.md +++ b/docs/react/api-angular/schematics/library.md @@ -36,12 +36,26 @@ Type: `boolean` Add a module spec file. +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + ### directory Type: `string` A directory where the lib is placed +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. + ### lazy Default: `false` @@ -72,13 +86,11 @@ The prefix to apply to generated selectors. ### publishable -Alias(es): buildable - Default: `false` Type: `boolean` -Generate a buildable library. +Generate a publishable library. ### routing diff --git a/e2e/angular/src/angular-package.test.ts b/e2e/angular/src/angular-package.test.ts index b07279352895f..27e10550a1e11 100644 --- a/e2e/angular/src/angular-package.test.ts +++ b/e2e/angular/src/angular-package.test.ts @@ -33,13 +33,13 @@ forEachCli('angular', (cli) => { newProject(); runCLI( - `generate @nrwl/angular:library ${parentLib} --publishable=true --no-interactive` + `generate @nrwl/angular:library ${parentLib} --publishable=true --importPath=@proj/${parentLib} --no-interactive` ); runCLI( - `generate @nrwl/angular:library ${childLib} --publishable=true --no-interactive` + `generate @nrwl/angular:library ${childLib} --publishable=true --importPath=@proj/${childLib} --no-interactive` ); runCLI( - `generate @nrwl/angular:library ${childLib2} --publishable=true --no-interactive` + `generate @nrwl/angular:library ${childLib2} --publishable=true --importPath=@proj/${childLib2} --no-interactive` ); // create secondary entrypoint diff --git a/e2e/workspace/src/workspace.test.ts b/e2e/workspace/src/workspace.test.ts index 2cb5302000069..bb1ee2da45453 100644 --- a/e2e/workspace/src/workspace.test.ts +++ b/e2e/workspace/src/workspace.test.ts @@ -38,8 +38,8 @@ forEachCli((cliName) => { const mylib1 = uniq('mylib1'); const mylib2 = uniq('mylib1'); runCLI(`generate @nrwl/react:app ${myapp}`); - runCLI(`generate @nrwl/react:lib ${mylib1} --publishable`); - runCLI(`generate @nrwl/react:lib ${mylib2} --publishable`); + runCLI(`generate @nrwl/react:lib ${mylib1} --buildable`); + runCLI(`generate @nrwl/react:lib ${mylib2} --buildable`); updateFile( `apps/${myapp}/src/main.ts`, @@ -80,9 +80,9 @@ forEachCli((cliName) => { const libD = uniq('libd-rand'); runCLI(`generate @nrwl/angular:app ${appA}`); - runCLI(`generate @nrwl/angular:lib ${libA} --publishable --defaults`); - runCLI(`generate @nrwl/angular:lib ${libB} --publishable --defaults`); - runCLI(`generate @nrwl/angular:lib ${libC} --publishable --defaults`); + runCLI(`generate @nrwl/angular:lib ${libA} --buildable --defaults`); + runCLI(`generate @nrwl/angular:lib ${libB} --buildable --defaults`); + runCLI(`generate @nrwl/angular:lib ${libC} --buildable --defaults`); runCLI(`generate @nrwl/angular:lib ${libD} --defaults`); // libA depends on libC @@ -206,7 +206,9 @@ forEachCli((cliName) => { runCLI(`generate @nrwl/angular:app ${myapp2}`); runCLI(`generate @nrwl/angular:lib ${mylib}`); runCLI(`generate @nrwl/angular:lib ${mylib2}`); - runCLI(`generate @nrwl/angular:lib ${mypublishablelib} --publishable`); + runCLI( + `generate @nrwl/angular:lib ${mypublishablelib} --publishable --importPath=@proj/${mypublishablelib}` + ); updateFile( `apps/${myapp}/src/app/app.component.spec.ts`, @@ -434,7 +436,7 @@ forEachCli((cliName) => { runCLI(`generate @nrwl/react:app ${myapp2}`); runCLI(`generate @nrwl/react:lib ${mylib}`); runCLI(`generate @nrwl/react:lib ${mylib2}`); - runCLI(`generate @nrwl/react:lib ${mypublishablelib} --publishable`); + runCLI(`generate @nrwl/react:lib ${mypublishablelib} --buildable`); updateFile( `apps/${myapp}/src/main.tsx`, diff --git a/packages/angular/src/schematics/library/lib/normalize-options.ts b/packages/angular/src/schematics/library/lib/normalize-options.ts index 12ad9d797b9fd..d652a9fdf90ce 100644 --- a/packages/angular/src/schematics/library/lib/normalize-options.ts +++ b/packages/angular/src/schematics/library/lib/normalize-options.ts @@ -1,6 +1,6 @@ import { Tree } from '@angular-devkit/schematics'; -import { getNpmScope, toClassName, toFileName } from '@nrwl/workspace'; -import { libsDir } from '@nrwl/workspace/src/utils/ast-utils'; +import { getNpmScope, toClassName, toFileName, NxJson } from '@nrwl/workspace'; +import { libsDir, readJsonInTree } from '@nrwl/workspace/src/utils/ast-utils'; import { Schema } from '../schema'; import { NormalizedSchema } from './normalized-schema'; @@ -24,6 +24,9 @@ export function normalizeOptions( const modulePath = `${projectRoot}/src/lib/${fileName}.module.ts`; const defaultPrefix = getNpmScope(host); + const importPath = + options.importPath || `@${defaultPrefix}/${projectDirectory}`; + return { ...options, prefix: options.prefix ? options.prefix : defaultPrefix, @@ -35,5 +38,6 @@ export function normalizeOptions( modulePath, parsedTags, fileName, + importPath, }; } diff --git a/packages/angular/src/schematics/library/lib/update-lib-package-npm-scope.ts b/packages/angular/src/schematics/library/lib/update-lib-package-npm-scope.ts index d08195cd1f72b..9e0c6184b73b0 100644 --- a/packages/angular/src/schematics/library/lib/update-lib-package-npm-scope.ts +++ b/packages/angular/src/schematics/library/lib/update-lib-package-npm-scope.ts @@ -1,12 +1,10 @@ import { Rule, Tree } from '@angular-devkit/schematics'; -import { getNpmScope, updateJsonInTree } from '@nrwl/workspace'; +import { updateJsonInTree } from '@nrwl/workspace'; import { NormalizedSchema } from './normalized-schema'; export function updateLibPackageNpmScope(options: NormalizedSchema): Rule { - return (host: Tree) => { - return updateJsonInTree(`${options.projectRoot}/package.json`, (json) => { - json.name = `@${getNpmScope(host)}/${options.name}`; - return json; - }); - }; + return updateJsonInTree(`${options.projectRoot}/package.json`, (json) => { + json.name = options.importPath; + return json; + }); } diff --git a/packages/angular/src/schematics/library/lib/update-ng-package.ts b/packages/angular/src/schematics/library/lib/update-ng-package.ts index e4b5d527b5dea..8c53e17f8a5e5 100644 --- a/packages/angular/src/schematics/library/lib/update-ng-package.ts +++ b/packages/angular/src/schematics/library/lib/update-ng-package.ts @@ -4,7 +4,7 @@ import { libsDir } from '@nrwl/workspace/src/utils/ast-utils'; import { NormalizedSchema } from './normalized-schema'; export function updateNgPackage(host: Tree, options: NormalizedSchema): Rule { - if (!options.publishable) { + if (!(options.publishable || options.buildable)) { return noop(); } const dest = `${offsetFromRoot(options.projectRoot)}dist/${libsDir(host)}/${ diff --git a/packages/angular/src/schematics/library/lib/update-project.ts b/packages/angular/src/schematics/library/lib/update-project.ts index 993c1078a6e7d..bf0c05526be83 100644 --- a/packages/angular/src/schematics/library/lib/update-project.ts +++ b/packages/angular/src/schematics/library/lib/update-project.ts @@ -47,7 +47,7 @@ export function updateProject(options: NormalizedSchema): Rule { host.delete(path.join(libRoot, `${options.name}.component.spec.ts`)); } - if (!options.publishable) { + if (!options.publishable && !options.buildable) { host.delete(path.join(options.projectRoot, 'ng-package.json')); host.delete(path.join(options.projectRoot, 'package.json')); host.delete(path.join(options.projectRoot, 'tsconfig.lib.prod.json')); @@ -138,7 +138,7 @@ export function updateProject(options: NormalizedSchema): Rule { }; } - if (!options.publishable) { + if (!options.publishable && !options.buildable) { delete fixedProject.architect.build; } else { // adjust the builder path to our custom one diff --git a/packages/angular/src/schematics/library/lib/update-tsconfig.ts b/packages/angular/src/schematics/library/lib/update-tsconfig.ts index 2f1f166d1a0d5..7742a32cdb90c 100644 --- a/packages/angular/src/schematics/library/lib/update-tsconfig.ts +++ b/packages/angular/src/schematics/library/lib/update-tsconfig.ts @@ -2,23 +2,30 @@ import { chain, Rule, SchematicContext, + SchematicsException, Tree, } from '@angular-devkit/schematics'; -import { NxJson, readJsonInTree, updateJsonInTree } from '@nrwl/workspace'; -import { libsDir } from '@nrwl/workspace/src/utils/ast-utils'; +import { updateJsonInTree } from '@nrwl/workspace'; import { NormalizedSchema } from './normalized-schema'; export function updateTsConfig(options: NormalizedSchema): Rule { return chain([ (host: Tree, context: SchematicContext) => { - const nxJson = readJsonInTree(host, 'nx.json'); return updateJsonInTree('tsconfig.base.json', (json) => { const c = json.compilerOptions; c.paths = c.paths || {}; delete c.paths[options.name]; - c.paths[`@${nxJson.npmScope}/${options.projectDirectory}`] = [ - `${libsDir(host)}/${options.projectDirectory}/src/index.ts`, + + if (c.paths[options.importPath]) { + throw new SchematicsException( + `You already have a library using the import path "${options.importPath}". Make sure to specify a unique one.` + ); + } + + c.paths[options.importPath] = [ + `libs/${options.projectDirectory}/src/index.ts`, ]; + return json; })(host, context); }, diff --git a/packages/angular/src/schematics/library/library.spec.ts b/packages/angular/src/schematics/library/library.spec.ts index 39e4bfbf8ed22..cf858727e97af 100644 --- a/packages/angular/src/schematics/library/library.spec.ts +++ b/packages/angular/src/schematics/library/library.spec.ts @@ -18,7 +18,12 @@ describe('lib', () => { it('should update ng-package.json', async () => { const publishableTree = await runSchematic( 'lib', - { name: 'myLib', framework: 'angular', publishable: true }, + { + name: 'myLib', + framework: 'angular', + publishable: true, + importPath: '@myorg/lib', + }, appTree ); let ngPackage = readJsonInTree( @@ -31,7 +36,12 @@ describe('lib', () => { it('should update ng-package.json $schema to the correct folder', async () => { const publishableTree = await runSchematic( 'lib', - { name: 'myLib', framework: 'angular', publishable: true }, + { + name: 'myLib', + framework: 'angular', + publishable: true, + importPath: '@myorg/lib', + }, appTree ); let ngPackage = readJsonInTree( @@ -53,37 +63,27 @@ describe('lib', () => { it('should update package.json when publishable', async () => { const tree = await runSchematic( 'lib', - { name: 'myLib', framework: 'angular', publishable: true }, + { + name: 'myLib', + framework: 'angular', + publishable: true, + importPath: '@myorg/lib', + }, appTree ); const packageJson = readJsonInTree(tree, '/package.json'); expect(packageJson.devDependencies['ng-packagr']).toBeDefined(); }); - it("should update npmScope of lib's package.json when publishable", async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', framework: 'angular', publishable: true }, - appTree - ); - const packageJson = readJsonInTree(tree, '/libs/my-lib/package.json'); - expect(packageJson.name).toEqual('@proj/my-lib'); - }); - - it("should update npmScope of lib's package.json when publishable", async () => { - const tree = await runSchematic( - 'lib', - { name: 'myLib', publishable: true, prefix: 'lib' }, - appTree - ); - const packageJson = readJsonInTree(tree, '/libs/my-lib/package.json'); - expect(packageJson.name).toEqual('@proj/my-lib'); - }); - it('should update workspace.json', async () => { const tree = await runSchematic( 'lib', - { name: 'myLib', framework: 'angular', publishable: true }, + { + name: 'myLib', + framework: 'angular', + publishable: true, + importPath: '@myorg/lib', + }, appTree ); const workspaceJson = readJsonInTree(tree, '/workspace.json'); @@ -115,6 +115,18 @@ describe('lib', () => { ).not.toBeDefined(); }); + it('should have a "build" target when a library is buildable', async () => { + const tree = await runSchematic( + 'lib', + { name: 'myLib', publishable: false, buildable: true }, + appTree + ); + const workspaceJson = readJsonInTree(tree, '/workspace.json'); + + expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); + expect(workspaceJson.projects['my-lib'].architect.build).toBeDefined(); + }); + it('should remove tsconfib.lib.prod.json when library is not publishable', async () => { const tree = await runSchematic( 'lib', @@ -454,6 +466,7 @@ describe('lib', () => { directory: 'myDir', framework: 'angular', publishable: true, + importPath: '@myorg/lib', }, appTree ); @@ -503,6 +516,26 @@ describe('lib', () => { ).toBeUndefined(); }); + it('should throw an exception when not passing importPath when using --publishable', async () => { + expect.assertions(1); + + try { + const tree = await runSchematic( + 'lib', + { + name: 'myLib', + directory: 'myDir', + publishable: true, + }, + appTree + ); + } 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)' + ); + } + }); + it('should update tsconfig.json (no existing path mappings)', async () => { const updatedTree: any = updateJsonInTree( 'tsconfig.base.json', @@ -1049,4 +1082,62 @@ describe('lib', () => { ).toEqual(['libs/my-lib/tsconfig.lib.json']); }); }); + + describe('--importPath', () => { + it('should update the package.json & tsconfig with the given import path', async () => { + const tree = await runSchematic( + 'lib', + { + name: 'myLib', + framework: 'angular', + 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'); + + expect(packageJson.name).toBe('@myorg/lib'); + expect( + tsconfigJson.compilerOptions.paths[packageJson.name] + ).toBeDefined(); + }); + + it('should fail if the same importPath has already been used', async () => { + const tree1 = await runSchematic( + 'lib', + { + name: 'myLib1', + framework: 'angular', + publishable: true, + importPath: '@myorg/lib', + }, + appTree + ); + + try { + await runSchematic( + 'lib', + { + name: 'myLib2', + framework: 'angular', + publishable: true, + importPath: '@myorg/lib', + }, + tree1 + ); + } catch (e) { + expect(e.message).toContain( + 'You already have a library using the import path' + ); + } + + expect.assertions(1); + }); + }); }); diff --git a/packages/angular/src/schematics/library/library.ts b/packages/angular/src/schematics/library/library.ts index 73311b8aadf42..660947b9604f9 100644 --- a/packages/angular/src/schematics/library/library.ts +++ b/packages/angular/src/schematics/library/library.ts @@ -5,6 +5,7 @@ import { noop, Rule, schematic, + SchematicsException, Tree, } from '@angular-devkit/schematics'; import { @@ -28,6 +29,12 @@ export default function (schema: Schema): Rule { throw new Error(`routing must be set`); } + 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([ addLintFiles(options.projectRoot, Linter.TsLint, { onlyGlobal: true }), addUnitTestRunner(options), @@ -42,7 +49,7 @@ export default function (schema: Schema): Rule { prefix: options.prefix, style: options.style, entryFile: 'index', - skipPackageJson: !options.publishable, + skipPackageJson: !(options.publishable || options.buildable), skipTsConfig: true, }), // TODO: Remove this after Angular 10.1.0 @@ -66,7 +73,9 @@ export default function (schema: Schema): Rule { project: options.name, }) : noop(), - options.publishable ? updateLibPackageNpmScope(options) : noop(), + options.publishable || options.buildable + ? updateLibPackageNpmScope(options) + : noop(), addModule(options), formatFiles(options), ]); diff --git a/packages/angular/src/schematics/library/schema.d.ts b/packages/angular/src/schematics/library/schema.d.ts index 11b939b4dd35c..00a795f4d3d72 100644 --- a/packages/angular/src/schematics/library/schema.d.ts +++ b/packages/angular/src/schematics/library/schema.d.ts @@ -7,7 +7,9 @@ export interface Schema { addModuleSpec?: boolean; directory?: string; sourceDir?: string; + buildable: boolean; publishable: boolean; + importPath?: string; spec?: boolean; flat?: boolean; diff --git a/packages/angular/src/schematics/library/schema.json b/packages/angular/src/schematics/library/schema.json index 6140a94f5204e..ffdf5cda78e68 100644 --- a/packages/angular/src/schematics/library/schema.json +++ b/packages/angular/src/schematics/library/schema.json @@ -20,8 +20,12 @@ "publishable": { "type": "boolean", "default": false, - "description": "Generate a buildable library.", - "alias": "buildable" + "description": "Generate a publishable library." + }, + "buildable": { + "type": "boolean", + "default": false, + "description": "Generate a buildable library." }, "prefix": { "type": "string", @@ -104,6 +108,10 @@ "enum": ["karma", "jest", "none"], "description": "Test runner to use for unit tests", "default": "jest" + }, + "importPath": { + "type": "string", + "description": "The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name." } }, "required": [] diff --git a/packages/workspace/package.json b/packages/workspace/package.json index 0437f349a2204..843e0a88f1dd3 100644 --- a/packages/workspace/package.json +++ b/packages/workspace/package.json @@ -68,6 +68,7 @@ "yargs": "^11.0.0", "chalk": "2.4.2", "@nrwl/cli": "*", - "axios": "0.19.2" + "axios": "0.19.2", + "speakingurl": "14.0.1" } } diff --git a/packages/workspace/src/schematics/library/library.spec.ts b/packages/workspace/src/schematics/library/library.spec.ts index d0d71ecabf74d..62e74fd176707 100644 --- a/packages/workspace/src/schematics/library/library.spec.ts +++ b/packages/workspace/src/schematics/library/library.spec.ts @@ -255,4 +255,49 @@ describe('lib', () => { ).toEqual(['libs/my-lib/tsconfig.lib.json']); }); }); + + describe('--importPath', () => { + it('should update the tsconfig with the given import path', async () => { + const tree = await runSchematic( + 'lib', + { + name: 'myLib', + directory: 'myDir', + importPath: '@myorg/lib', + }, + appTree + ); + const tsconfigJson = readJsonInTree(tree, '/tsconfig.base.json'); + + expect(tsconfigJson.compilerOptions.paths['@myorg/lib']).toBeDefined(); + }); + + it('should fail if the same importPath has already been used', async () => { + const tree1 = await runSchematic( + 'lib', + { + name: 'myLib1', + importPath: '@myorg/lib', + }, + appTree + ); + + try { + await runSchematic( + 'lib', + { + name: 'myLib2', + importPath: '@myorg/lib', + }, + tree1 + ); + } catch (e) { + expect(e.message).toContain( + 'You already have a library using the import path' + ); + } + + expect.assertions(1); + }); + }); }); diff --git a/packages/workspace/src/schematics/library/library.ts b/packages/workspace/src/schematics/library/library.ts index 8041b169dad80..5a3aeadf11a13 100644 --- a/packages/workspace/src/schematics/library/library.ts +++ b/packages/workspace/src/schematics/library/library.ts @@ -28,6 +28,7 @@ export interface NormalizedSchema extends Schema { projectRoot: string; projectDirectory: string; parsedTags: string[]; + importPath?: string; } function addProject(options: NormalizedSchema): Rule { From 28d84fc32e905f6b66826b4ab8b2e45e73dde4ab Mon Sep 17 00:00:00 2001 From: Juri Date: Thu, 11 Jun 2020 23:36:46 +0200 Subject: [PATCH 2/5] fix(react): require importPath for publishable libs & adjust buildable ISSUES CLOSED: #2794 --- docs/angular/api-react/schematics/library.md | 12 ++- docs/react/api-react/schematics/library.md | 18 +++- e2e/react/src/react-package.test.ts | 6 +- e2e/react/src/react.test.ts | 4 +- .../src/schematics/library/library.spec.ts | 95 +++++++++++++++++++ .../react/src/schematics/library/library.ts | 35 +++++-- .../react/src/schematics/library/schema.d.ts | 2 + .../react/src/schematics/library/schema.json | 12 ++- 8 files changed, 162 insertions(+), 22 deletions(-) diff --git a/docs/angular/api-react/schematics/library.md b/docs/angular/api-react/schematics/library.md index b0ba08c25ce25..ef207e9690262 100644 --- a/docs/angular/api-react/schematics/library.md +++ b/docs/angular/api-react/schematics/library.md @@ -50,6 +50,14 @@ Type: `string` The application project to add the library route to. +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + ### component Default: `true` @@ -108,11 +116,9 @@ Use pascal case component file name (e.g. App.tsx). ### publishable -Alias(es): buildable - Type: `boolean` -Create a buildable library. +Create a publishable library. ### routing diff --git a/docs/react/api-react/schematics/library.md b/docs/react/api-react/schematics/library.md index b56e22d15b8f9..3b89dfcc340a7 100644 --- a/docs/react/api-react/schematics/library.md +++ b/docs/react/api-react/schematics/library.md @@ -50,6 +50,14 @@ Type: `string` The application project to add the library route to. +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + ### component Default: `true` @@ -66,6 +74,12 @@ Type: `string` A directory where the lib is placed. +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib + ### js Default: `false` @@ -102,11 +116,9 @@ Use pascal case component file name (e.g. App.tsx). ### publishable -Alias(es): buildable - Type: `boolean` -Create a buildable library. +Create a publishable library. ### routing diff --git a/e2e/react/src/react-package.test.ts b/e2e/react/src/react-package.test.ts index cff874d69def0..dfcaa8d3e6b32 100644 --- a/e2e/react/src/react-package.test.ts +++ b/e2e/react/src/react-package.test.ts @@ -37,13 +37,13 @@ forEachCli('nx', (cli) => { runCLI(`generate @nrwl/react:app ${app}`); runCLI( - `generate @nrwl/react:library ${parentLib} --buildable --no-interactive` + `generate @nrwl/react:library ${parentLib} --publishable --importPath=@proj/${parentLib} --no-interactive` ); runCLI( - `generate @nrwl/react:library ${childLib} --buildable --no-interactive` + `generate @nrwl/react:library ${childLib} --publishable --importPath=@proj/${childLib} --no-interactive` ); runCLI( - `generate @nrwl/react:library ${childLib2} --buildable --no-interactive` + `generate @nrwl/react:library ${childLib2} --publishable --importPath=@proj/${childLib2} --no-interactive` ); // create dependencies by importing diff --git a/e2e/react/src/react.test.ts b/e2e/react/src/react.test.ts index 95f74fc9f5355..21347a786651e 100644 --- a/e2e/react/src/react.test.ts +++ b/e2e/react/src/react.test.ts @@ -52,7 +52,7 @@ forEachCli('nx', () => { const libName = uniq('lib'); runCLI( - `generate @nrwl/react:lib ${libName} --publishable --no-interactive` + `generate @nrwl/react:lib ${libName} --publishable --importPath=@proj/${libName} --no-interactive` ); const libTestResults = await runCLIAsync( @@ -113,7 +113,7 @@ forEachCli('nx', () => { const libName = uniq('lib'); runCLI( - `generate @nrwl/react:lib ${libName} --publishable --no-interactive` + `generate @nrwl/react:lib ${libName} --publishable --importPath=@proj/${libName} --no-interactive` ); const mainPath = `libs/${libName}/src/lib/${libName}.tsx`; diff --git a/packages/react/src/schematics/library/library.spec.ts b/packages/react/src/schematics/library/library.spec.ts index c6b5e5c3977fb..ab284ef6d169f 100644 --- a/packages/react/src/schematics/library/library.spec.ts +++ b/packages/react/src/schematics/library/library.spec.ts @@ -357,6 +357,23 @@ describe('lib', () => { }); }); + describe('--buildable', () => { + it('should have a builder defined', async () => { + const tree = await runSchematic( + 'lib', + { + name: 'myLib', + buildable: true, + }, + appTree + ); + + const workspaceJson = readJsonInTree(tree, '/workspace.json'); + + expect(workspaceJson.projects['my-lib'].architect.build).toBeDefined(); + }); + }); + describe('--publishable', () => { it('should add build architect', async () => { const tree = await runSchematic( @@ -364,6 +381,7 @@ describe('lib', () => { { name: 'myLib', publishable: true, + importPath: '@proj/my-lib', }, appTree ); @@ -384,12 +402,29 @@ describe('lib', () => { }); }); + it('should fail if no importPath is provided with publishable', async () => { + expect.assertions(1); + + try { + const tree = await runSchematic( + 'lib', + { name: 'myLib', directory: 'myDir', publishable: true }, + appTree + ); + } 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)' + ); + } + }); + it('should support styled-components', async () => { const tree = await runSchematic( 'lib', { name: 'myLib', publishable: true, + importPath: '@proj/my-lib', style: 'styled-components', }, appTree @@ -410,6 +445,7 @@ describe('lib', () => { { name: 'myLib', publishable: true, + importPath: '@proj/my-lib', style: '@emotion/styled', }, appTree @@ -430,6 +466,7 @@ describe('lib', () => { { name: 'myLib', publishable: true, + importPath: '@proj/my-lib', style: 'styled-jsx', }, appTree @@ -450,6 +487,7 @@ describe('lib', () => { { name: 'myLib', publishable: true, + importPath: '@proj/my-lib', style: 'none', }, appTree @@ -470,6 +508,7 @@ describe('lib', () => { { name: 'myLib', publishable: true, + importPath: '@proj/my-lib', }, appTree ); @@ -494,4 +533,60 @@ describe('lib', () => { expect(tree.exists('/libs/my-lib/src/index.js')).toBe(true); }); }); + + 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'); + + expect(packageJson.name).toBe('@myorg/lib'); + expect( + tsconfigJson.compilerOptions.paths[packageJson.name] + ).toBeDefined(); + }); + + 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 + ); + + try { + await runSchematic( + 'lib', + { + name: 'myLib2', + framework: 'angular', + publishable: true, + importPath: '@myorg/lib', + }, + tree1 + ); + } catch (e) { + expect(e.message).toContain( + 'You already have a library using the import path' + ); + } + + expect.assertions(1); + }); + }); }); diff --git a/packages/react/src/schematics/library/library.ts b/packages/react/src/schematics/library/library.ts index b04308e48372c..06ddfc1056ae9 100644 --- a/packages/react/src/schematics/library/library.ts +++ b/packages/react/src/schematics/library/library.ts @@ -9,6 +9,7 @@ import { noop, Rule, SchematicContext, + SchematicsException, template, Tree, url, @@ -66,6 +67,12 @@ export default function (schema: Schema): 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)` + ); + } + if (!options.component) { options.style = 'none'; } @@ -125,7 +132,7 @@ function addProject(options: NormalizedSchema): Rule { options.linter ); - if (options.publishable) { + if (options.publishable || options.buildable) { const external = ['react', 'react-dom']; // Also exclude CSS-in-JS packages from build if ( @@ -179,12 +186,20 @@ function updateTsConfig(options: NormalizedSchema): Rule { const c = json.compilerOptions; c.paths = c.paths || {}; delete c.paths[options.name]; - c.paths[`@${nxJson.npmScope}/${options.projectDirectory}`] = [ + + if (c.paths[options.importPath]) { + throw new SchematicsException( + `You already have a library using the import path "${options.importPath}". Make sure to specify a unique one.` + ); + } + + c.paths[options.importPath] = [ maybeJs( options, `${libsDir(host)}/${options.projectDirectory}/src/index.ts` ), ]; + return json; })(host, context); }, @@ -201,7 +216,7 @@ function createFiles(options: NormalizedSchema): Rule { offsetFromRoot: offsetFromRoot(options.projectRoot), }), move(options.projectRoot), - options.publishable + options.publishable || options.buildable ? noop() : filter((file) => !file.endsWith('package.json')), options.js ? toJS() : noop(), @@ -324,6 +339,9 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { ? options.tags.split(',').map((s) => s.trim()) : []; + const importPath = + options.importPath || `@${getNpmScope(host)}/${projectDirectory}`; + const normalized: NormalizedSchema = { ...options, fileName, @@ -332,6 +350,7 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { projectRoot, projectDirectory, parsedTags, + importPath, }; if (options.appProject) { @@ -359,12 +378,10 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { } function updateLibPackageNpmScope(options: NormalizedSchema): Rule { - return (host: Tree) => { - return updateJsonInTree(`${options.projectRoot}/package.json`, (json) => { - json.name = `@${getNpmScope(host)}/${options.name}`; - return json; - }); - }; + return updateJsonInTree(`${options.projectRoot}/package.json`, (json) => { + json.name = options.importPath; + return json; + }); } function maybeJs(options: NormalizedSchema, path: string): string { diff --git a/packages/react/src/schematics/library/schema.d.ts b/packages/react/src/schematics/library/schema.d.ts index 463c8451b507e..df93fe74177ed 100644 --- a/packages/react/src/schematics/library/schema.d.ts +++ b/packages/react/src/schematics/library/schema.d.ts @@ -16,5 +16,7 @@ export interface Schema { linter: Linter; component?: boolean; publishable?: boolean; + buildable?: boolean; + importPath?: string; js?: boolean; } diff --git a/packages/react/src/schematics/library/schema.json b/packages/react/src/schematics/library/schema.json index 0406fd49e4227..7799af36e2316 100644 --- a/packages/react/src/schematics/library/schema.json +++ b/packages/react/src/schematics/library/schema.json @@ -113,8 +113,16 @@ }, "publishable": { "type": "boolean", - "description": "Create a buildable library.", - "alias": "buildable" + "description": "Create a publishable library." + }, + "buildable": { + "type": "boolean", + "default": false, + "description": "Generate a buildable library." + }, + "importPath": { + "type": "string", + "description": "The library name used to import it, like @myorg/my-awesome-lib" }, "component": { "type": "boolean", From 7cb5ae4f224558129d56263484bc68a39098c09e Mon Sep 17 00:00:00 2001 From: Juri Date: Fri, 12 Jun 2020 14:17:08 +0200 Subject: [PATCH 3/5] fix(node): require importPath for publishable libs & adjust buildable ISSUES CLOSED: #2794 --- docs/angular/api-node/schematics/library.md | 16 ++- .../api-workspace/schematics/library.md | 6 + docs/react/api-node/schematics/library.md | 16 ++- .../react/api-workspace/schematics/library.md | 6 + e2e/node/src/node.test.ts | 18 ++- .../library/files/lib/package.json__tmpl__ | 2 +- .../src/schematics/library/library.spec.ts | 105 +++++++++++++++++- .../node/src/schematics/library/library.ts | 26 +++-- .../node/src/schematics/library/schema.d.ts | 2 + .../node/src/schematics/library/schema.json | 12 +- .../src/schematics/library/library.ts | 39 ++++--- .../src/schematics/library/schema.d.ts | 1 + .../src/schematics/library/schema.json | 4 + 13 files changed, 217 insertions(+), 36 deletions(-) diff --git a/docs/angular/api-node/schematics/library.md b/docs/angular/api-node/schematics/library.md index 9d77a1864ea54..90518b9b91fc2 100644 --- a/docs/angular/api-node/schematics/library.md +++ b/docs/angular/api-node/schematics/library.md @@ -36,6 +36,14 @@ nx g lib mylib --directory=myapp ## Options +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + ### directory Alias(es): d @@ -44,6 +52,12 @@ Type: `string` A directory where the lib is placed +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. + ### linter Default: `tslint` @@ -62,8 +76,6 @@ Library name ### publishable -Alias(es): buildable - Type: `boolean` Create a publishable library. diff --git a/docs/angular/api-workspace/schematics/library.md b/docs/angular/api-workspace/schematics/library.md index b48c556176043..42590485164b4 100644 --- a/docs/angular/api-workspace/schematics/library.md +++ b/docs/angular/api-workspace/schematics/library.md @@ -42,6 +42,12 @@ Type: `string` A directory where the lib is placed +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib + ### linter Default: `tslint` diff --git a/docs/react/api-node/schematics/library.md b/docs/react/api-node/schematics/library.md index c6e7f085f72da..2d37bbfe88d61 100644 --- a/docs/react/api-node/schematics/library.md +++ b/docs/react/api-node/schematics/library.md @@ -36,6 +36,14 @@ nx g lib mylib --directory=myapp ## Options +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + ### directory Alias(es): d @@ -44,6 +52,12 @@ Type: `string` A directory where the lib is placed +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. + ### linter Default: `tslint` @@ -62,8 +76,6 @@ Library name ### publishable -Alias(es): buildable - Type: `boolean` Create a publishable library. diff --git a/docs/react/api-workspace/schematics/library.md b/docs/react/api-workspace/schematics/library.md index dba68a2e9c6d5..7dac3b181c389 100644 --- a/docs/react/api-workspace/schematics/library.md +++ b/docs/react/api-workspace/schematics/library.md @@ -42,6 +42,12 @@ Type: `string` A directory where the lib is placed +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib + ### linter Default: `tslint` diff --git a/e2e/node/src/node.test.ts b/e2e/node/src/node.test.ts index 59d3dc6fb8ab8..90420923c86f0 100644 --- a/e2e/node/src/node.test.ts +++ b/e2e/node/src/node.test.ts @@ -266,7 +266,9 @@ forEachCli((currentCLIName) => { ensureProject(); const nodeLib = uniq('nodelib'); - runCLI(`generate @nrwl/node:lib ${nodeLib} --publishable`); + runCLI( + `generate @nrwl/node:lib ${nodeLib} --publishable --importPath=@proj/${nodeLib}` + ); checkFilesExist(`libs/${nodeLib}/package.json`); const tslibConfig = readJson(`libs/${nodeLib}/tsconfig.lib.json`); expect(tslibConfig).toEqual({ @@ -303,12 +305,16 @@ forEachCli((currentCLIName) => { const nglib = uniq('nglib'); // Generating two libraries just to have a lot of files to copy - runCLI(`generate @nrwl/node:lib ${nodelib} --publishable`); + runCLI( + `generate @nrwl/node:lib ${nodelib} --publishable --importPath=@proj/${nodelib}` + ); /** * The angular lib contains a lot sub directories that would fail without * `nodir: true` in the package.impl.ts */ - runCLI(`generate @nrwl/angular:lib ${nglib} --publishable`); + runCLI( + `generate @nrwl/angular:lib ${nglib} --publishable --importPath=@proj/${nglib}` + ); const workspace = readJson(workspaceConfigName()); workspace.projects[nodelib].architect.build.options.assets.push({ input: `./dist/libs/${nglib}`, @@ -428,9 +434,9 @@ forEachCli((currentCLIName) => { ensureProject(); runCLI(`generate @nrwl/express:app ${app}`); - runCLI(`generate @nrwl/node:lib ${parentLib} --publishable=true`); - runCLI(`generate @nrwl/node:lib ${childLib} --publishable=true`); - runCLI(`generate @nrwl/node:lib ${childLib2} --publishable=true`); + runCLI(`generate @nrwl/node:lib ${parentLib} --buildable=true`); + runCLI(`generate @nrwl/node:lib ${childLib} --buildable=true`); + runCLI(`generate @nrwl/node:lib ${childLib2} --buildable=true`); // create dependencies by importing const createDep = (parent, children: string[]) => { diff --git a/packages/node/src/schematics/library/files/lib/package.json__tmpl__ b/packages/node/src/schematics/library/files/lib/package.json__tmpl__ index 9ebd4abc6585a..e3a3ad83c46eb 100644 --- a/packages/node/src/schematics/library/files/lib/package.json__tmpl__ +++ b/packages/node/src/schematics/library/files/lib/package.json__tmpl__ @@ -1,4 +1,4 @@ { - "name": "@<%= prefix %>/<%= name %>", + "name": "<%= importPath %>", "version": "0.0.1" } diff --git a/packages/node/src/schematics/library/library.spec.ts b/packages/node/src/schematics/library/library.spec.ts index de5bcec5aac6e..d3ea61a74b383 100644 --- a/packages/node/src/schematics/library/library.spec.ts +++ b/packages/node/src/schematics/library/library.spec.ts @@ -185,6 +185,26 @@ describe('lib', () => { ).toBeUndefined(); }); + it('should throw an exception when not passing importPath when using --publishable', async () => { + expect.assertions(1); + + try { + const tree = await runSchematic( + 'lib', + { + name: 'myLib', + directory: 'myDir', + publishable: true, + }, + appTree + ); + } 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)' + ); + } + }); + it('should create a local tsconfig.json', async () => { const tree = await runSchematic( 'lib', @@ -236,11 +256,39 @@ 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'); + + expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); + + expect(workspaceJson.projects['my-lib'].architect.build).toBeDefined(); + }); + }); + 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'); + + expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); + + expect(workspaceJson.projects['my-lib'].architect.build).toBeDefined(); + }); + it('should update package.json', async () => { const publishableTree = await runSchematic( 'lib', - { name: 'mylib', publishable: true }, + { name: 'mylib', publishable: true, importPath: '@proj/mylib' }, appTree ); @@ -252,4 +300,59 @@ describe('lib', () => { expect(packageJsonContent.name).toEqual('@proj/mylib'); }); }); + + 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'); + + expect(packageJson.name).toBe('@myorg/lib'); + expect( + tsconfigJson.compilerOptions.paths[packageJson.name] + ).toBeDefined(); + }); + + 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 + ); + + try { + await runSchematic( + 'lib', + { + name: 'myLib2', + publishable: true, + importPath: '@myorg/lib', + }, + tree1 + ); + } catch (e) { + expect(e.message).toContain( + 'You already have a library using the import path' + ); + } + + expect.assertions(1); + }); + }); }); diff --git a/packages/node/src/schematics/library/library.ts b/packages/node/src/schematics/library/library.ts index 14c069cf3372d..6650698833deb 100644 --- a/packages/node/src/schematics/library/library.ts +++ b/packages/node/src/schematics/library/library.ts @@ -10,17 +10,18 @@ import { noop, Rule, SchematicContext, + SchematicsException, template, Tree, url, } from '@angular-devkit/schematics'; import { formatFiles, + getNpmScope, names, offsetFromRoot, toFileName, updateWorkspaceInTree, - getNpmScope, } from '@nrwl/workspace'; import { Schema } from './schema'; import { libsDir } from '@nrwl/workspace/src/utils/ast-utils'; @@ -38,8 +39,17 @@ 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), + externalSchematic('@nrwl/workspace', 'lib', { + ...schema, + importPath: options.importPath, + }), createFiles(options), addProject(options), formatFiles(options), @@ -62,7 +72,10 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { ? options.tags.split(',').map((s) => s.trim()) : []; - const normalized: NormalizedSchema = { + const importPath = + options.importPath || `@${defaultPrefix}/${projectDirectory}`; + + return { ...options, prefix: defaultPrefix, // we could also allow customizing this fileName, @@ -70,9 +83,8 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { projectRoot, projectDirectory, parsedTags, + importPath, }; - - return normalized; } function createFiles(options: NormalizedSchema): Rule { @@ -88,7 +100,7 @@ function createFiles(options: NormalizedSchema): Rule { options.unitTestRunner === 'none' ? filter((file) => !file.endsWith('spec.ts')) : noop(), - options.publishable + options.publishable || options.buildable ? noop() : filter((file) => !file.endsWith('package.json')), ]), @@ -97,7 +109,7 @@ function createFiles(options: NormalizedSchema): Rule { } function addProject(options: NormalizedSchema): Rule { - if (!options.publishable) { + if (!options.publishable && !options.buildable) { return noop(); } diff --git a/packages/node/src/schematics/library/schema.d.ts b/packages/node/src/schematics/library/schema.d.ts index 12b6d7761d234..c8531f9aa21d3 100644 --- a/packages/node/src/schematics/library/schema.d.ts +++ b/packages/node/src/schematics/library/schema.d.ts @@ -8,6 +8,8 @@ export interface Schema { tags?: string; unitTestRunner: 'jest' | 'none'; linter: Linter; + buildable?: boolean; publishable?: boolean; + importPath?: string; testEnvironment: 'jsdom' | 'node'; } diff --git a/packages/node/src/schematics/library/schema.json b/packages/node/src/schematics/library/schema.json index dc6126cab94fa..69a43ddde123a 100644 --- a/packages/node/src/schematics/library/schema.json +++ b/packages/node/src/schematics/library/schema.json @@ -53,8 +53,16 @@ }, "publishable": { "type": "boolean", - "description": "Create a publishable library.", - "alias": "buildable" + "description": "Create a publishable library." + }, + "buildable": { + "type": "boolean", + "default": false, + "description": "Generate a buildable library." + }, + "importPath": { + "type": "string", + "description": "The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name." }, "testEnvironment": { "type": "string", diff --git a/packages/workspace/src/schematics/library/library.ts b/packages/workspace/src/schematics/library/library.ts index 5a3aeadf11a13..cf86bebcf9a57 100644 --- a/packages/workspace/src/schematics/library/library.ts +++ b/packages/workspace/src/schematics/library/library.ts @@ -10,11 +10,12 @@ import { template, move, noop, + SchematicsException, } from '@angular-devkit/schematics'; import { join, normalize } from '@angular-devkit/core'; import { Schema } from './schema'; -import { NxJson, updateWorkspaceInTree } from '@nrwl/workspace'; +import { NxJson, updateWorkspaceInTree, getNpmScope } from '@nrwl/workspace'; import { updateJsonInTree, readJsonInTree } from '@nrwl/workspace'; import { toFileName, names } from '@nrwl/workspace'; import { formatFiles } from '@nrwl/workspace'; @@ -53,20 +54,23 @@ function addProject(options: NormalizedSchema): Rule { } function updateTsConfig(options: NormalizedSchema): Rule { - return chain([ - (host: Tree, context: SchematicContext) => { - const nxJson = readJsonInTree(host, 'nx.json'); - return updateJsonInTree('tsconfig.base.json', (json) => { - const c = json.compilerOptions; - c.paths = c.paths || {}; - delete c.paths[options.name]; - c.paths[`@${nxJson.npmScope}/${options.projectDirectory}`] = [ - `${libsDir(host)}/${options.projectDirectory}/src/index.ts`, - ]; - return json; - })(host, context); - }, - ]); + return updateJsonInTree('tsconfig.base.json', (json) => { + const c = json.compilerOptions; + c.paths = c.paths || {}; + delete c.paths[options.name]; + + if (c.paths[options.importPath]) { + throw new SchematicsException( + `You already have a library using the import path "${options.importPath}". Make sure to specify a unique one.` + ); + } + + c.paths[options.importPath] = [ + `libs/${options.projectDirectory}/src/index.ts`, + ]; + + return json; + }); } function createFiles(options: NormalizedSchema): Rule { @@ -91,6 +95,7 @@ function updateNxJson(options: NormalizedSchema): Rule { export default function (schema: Schema): Rule { return (host: Tree, context: SchematicContext) => { const options = normalizeOptions(host, schema); + return chain([ addLintFiles(options.projectRoot, options.linter), createFiles(options), @@ -127,6 +132,9 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { ? options.tags.split(',').map((s) => s.trim()) : []; + const defaultImportPath = `@${getNpmScope(host)}/${projectDirectory}`; + const importPath = options.importPath || defaultImportPath; + return { ...options, fileName, @@ -134,5 +142,6 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { projectRoot, projectDirectory, parsedTags, + importPath, }; } diff --git a/packages/workspace/src/schematics/library/schema.d.ts b/packages/workspace/src/schematics/library/schema.d.ts index 876f938e7e03a..0f7ed5de73eaa 100644 --- a/packages/workspace/src/schematics/library/schema.d.ts +++ b/packages/workspace/src/schematics/library/schema.d.ts @@ -10,4 +10,5 @@ export interface Schema { unitTestRunner: 'jest' | 'none'; linter: Linter; testEnvironment: 'jsdom' | 'node'; + importPath?: string; } diff --git a/packages/workspace/src/schematics/library/schema.json b/packages/workspace/src/schematics/library/schema.json index e46958ef8003f..11149c0325661 100644 --- a/packages/workspace/src/schematics/library/schema.json +++ b/packages/workspace/src/schematics/library/schema.json @@ -54,6 +54,10 @@ "enum": ["jsdom", "node"], "description": "The test environment to use if unitTestRunner is set to jest", "default": "jsdom" + }, + "importPath": { + "type": "string", + "description": "The library name used to import it, like @myorg/my-awesome-lib" } }, "required": ["name"] From 20263f19b8a86ba5aa0036bef9b816638401cecf Mon Sep 17 00:00:00 2001 From: Juri Date: Sun, 28 Jun 2020 22:23:44 +0200 Subject: [PATCH 4/5] fix(nest): require importPath for publishable libs & adjust buildable --- docs/angular/api-nest/schematics/library.md | 18 +++++++++++++++--- docs/react/api-nest/schematics/library.md | 18 +++++++++++++++--- .../src/schematics/library/library.spec.ts | 2 +- .../nest/src/schematics/library/library.ts | 2 +- .../nest/src/schematics/library/schema.d.ts | 1 + .../nest/src/schematics/library/schema.json | 12 ++++++++++-- 6 files changed, 43 insertions(+), 10 deletions(-) diff --git a/docs/angular/api-nest/schematics/library.md b/docs/angular/api-nest/schematics/library.md index 924847ac7e452..fd22d83e6e148 100644 --- a/docs/angular/api-nest/schematics/library.md +++ b/docs/angular/api-nest/schematics/library.md @@ -36,6 +36,14 @@ nx g lib mylib --directory=myapp ## Options +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + ### controller Default: `false` @@ -60,6 +68,12 @@ Type: `boolean` Add the Global decorator to the generated module. +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. + ### linter Default: `tslint` @@ -78,11 +92,9 @@ Library name ### publishable -Alias(es): buildable - Type: `boolean` -Create a buildable library. +Create a publishable library. ### service diff --git a/docs/react/api-nest/schematics/library.md b/docs/react/api-nest/schematics/library.md index 5aa7dc9b70d53..e08bf2bda97fe 100644 --- a/docs/react/api-nest/schematics/library.md +++ b/docs/react/api-nest/schematics/library.md @@ -36,6 +36,14 @@ nx g lib mylib --directory=myapp ## Options +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + ### controller Default: `false` @@ -60,6 +68,12 @@ Type: `boolean` Add the Global decorator to the generated module. +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. + ### linter Default: `tslint` @@ -78,11 +92,9 @@ Library name ### publishable -Alias(es): buildable - Type: `boolean` -Create a buildable library. +Create a publishable library. ### service diff --git a/packages/nest/src/schematics/library/library.spec.ts b/packages/nest/src/schematics/library/library.spec.ts index 8bd0448bac385..aee245c3e733f 100644 --- a/packages/nest/src/schematics/library/library.spec.ts +++ b/packages/nest/src/schematics/library/library.spec.ts @@ -366,7 +366,7 @@ describe('lib', () => { it('should update package.json', async () => { const publishableTree = await runSchematic( 'lib', - { name: 'mylib', publishable: true }, + { name: 'mylib', publishable: true, importPath: '@proj/mylib' }, appTree ); diff --git a/packages/nest/src/schematics/library/library.ts b/packages/nest/src/schematics/library/library.ts index 9ef47afc4c936..74d099205fa05 100644 --- a/packages/nest/src/schematics/library/library.ts +++ b/packages/nest/src/schematics/library/library.ts @@ -175,7 +175,7 @@ function updateTsConfig(options: NormalizedSchema): Rule { } function addProject(options: NormalizedSchema): Rule { - if (!options.publishable) { + if (!options.publishable && !options.buildable) { return noop(); } diff --git a/packages/nest/src/schematics/library/schema.d.ts b/packages/nest/src/schematics/library/schema.d.ts index b3b84628f5582..b88b9be059978 100644 --- a/packages/nest/src/schematics/library/schema.d.ts +++ b/packages/nest/src/schematics/library/schema.d.ts @@ -8,6 +8,7 @@ export interface Schema { tags?: string; unitTestRunner: 'jest' | 'none'; linter: Linter; + buildable: boolean; publishable?: boolean; global?: boolean; service?: boolean; diff --git a/packages/nest/src/schematics/library/schema.json b/packages/nest/src/schematics/library/schema.json index fe4fb359e9c91..ee5672e9dd642 100644 --- a/packages/nest/src/schematics/library/schema.json +++ b/packages/nest/src/schematics/library/schema.json @@ -53,8 +53,16 @@ }, "publishable": { "type": "boolean", - "description": "Create a buildable library.", - "alias": "buildable" + "description": "Create a publishable library." + }, + "buildable": { + "type": "boolean", + "default": false, + "description": "Generate a buildable library." + }, + "importPath": { + "type": "string", + "description": "The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name." }, "global": { "type": "boolean", From 1e4e681cf0e4c0158e5dd1d91e5f0d6873ed7e69 Mon Sep 17 00:00:00 2001 From: Juri Date: Sun, 28 Jun 2020 22:27:45 +0200 Subject: [PATCH 5/5] fix(nx-plugin): require importPath --- .../api-nx-plugin/schematics/plugin.md | 8 ++++++- docs/react/api-nx-plugin/schematics/plugin.md | 8 ++++++- e2e/nx-plugin/src/nx-plugin.test.ts | 24 +++++++++++++------ .../create-nx-plugin/bin/create-nx-plugin.ts | 6 +++-- .../src/schematics/builder/builder.spec.ts | 6 ++++- .../schematics/migration/migration.spec.ts | 6 ++++- .../src/schematics/plugin/plugin.spec.ts | 24 +++++++++++++++---- .../nx-plugin/src/schematics/plugin/plugin.ts | 1 + .../src/schematics/plugin/schema.d.ts | 1 + .../src/schematics/plugin/schema.json | 8 +++++-- .../schematics/schematic/schematic.spec.ts | 6 ++++- 11 files changed, 78 insertions(+), 20 deletions(-) diff --git a/docs/angular/api-nx-plugin/schematics/plugin.md b/docs/angular/api-nx-plugin/schematics/plugin.md index 1cf5c935fdc9e..1cf7610d9ee6d 100644 --- a/docs/angular/api-nx-plugin/schematics/plugin.md +++ b/docs/angular/api-nx-plugin/schematics/plugin.md @@ -27,7 +27,7 @@ nx g plugin ... --dry-run Generate libs/plugins/my-plugin: ```bash -nx g plugin my-plugin --directory=plugins +nx g plugin my-plugin --directory=plugins --importPath=@myorg/my-plugin ``` ## Options @@ -40,6 +40,12 @@ Type: `string` A directory where the plugin is placed +### importPath + +Type: `string` + +How the plugin will be published, like @myorg/my-awesome-plugin. Note this must be a valid npm name + ### linter Default: `tslint` diff --git a/docs/react/api-nx-plugin/schematics/plugin.md b/docs/react/api-nx-plugin/schematics/plugin.md index 83bfe7d7ef521..a108634e9e86f 100644 --- a/docs/react/api-nx-plugin/schematics/plugin.md +++ b/docs/react/api-nx-plugin/schematics/plugin.md @@ -27,7 +27,7 @@ nx g plugin ... --dry-run Generate libs/plugins/my-plugin: ```bash -nx g plugin my-plugin --directory=plugins +nx g plugin my-plugin --directory=plugins --importPath=@myorg/my-plugin ``` ## Options @@ -40,6 +40,12 @@ Type: `string` A directory where the plugin is placed +### importPath + +Type: `string` + +How the plugin will be published, like @myorg/my-awesome-plugin. Note this must be a valid npm name + ### linter Default: `tslint` diff --git a/e2e/nx-plugin/src/nx-plugin.test.ts b/e2e/nx-plugin/src/nx-plugin.test.ts index 48dffa25f4b43..beaad0bf6d638 100644 --- a/e2e/nx-plugin/src/nx-plugin.test.ts +++ b/e2e/nx-plugin/src/nx-plugin.test.ts @@ -19,7 +19,9 @@ forEachCli((currentCLIName) => { ensureProject(); const plugin = uniq('plugin'); - runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter}`); + runCLI( + `generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter} --importPath=@proj/${plugin}` + ); const lintResults = runCLI(`lint ${plugin}`); expect(lintResults).toContain('All files pass linting.'); @@ -62,7 +64,9 @@ forEachCli((currentCLIName) => { it(`should run the plugin's e2e tests`, async (done) => { ensureProject(); const plugin = uniq('plugin'); - runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter}`); + runCLI( + `generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter} --importPath=@proj/${plugin}` + ); const results = await runCLIAsync(`e2e ${plugin}-e2e`); expect(results.stdout).toContain('Compiling TypeScript files'); expectTestsPass(results); @@ -75,7 +79,9 @@ forEachCli((currentCLIName) => { const plugin = uniq('plugin'); const version = '1.0.0'; - runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter}`); + runCLI( + `generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter} --importPath=@proj/${plugin}` + ); runCLI( `generate @nrwl/nx-plugin:migration --project=${plugin} --version=${version} --packageJsonUpdates=false` ); @@ -112,7 +118,9 @@ forEachCli((currentCLIName) => { const plugin = uniq('plugin'); const schematic = uniq('schematic'); - runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter}`); + runCLI( + `generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter} --importPath=@proj/${plugin}` + ); runCLI( `generate @nrwl/nx-plugin:schematic ${schematic} --project=${plugin}` ); @@ -152,7 +160,9 @@ forEachCli((currentCLIName) => { const plugin = uniq('plugin'); const builder = uniq('builder'); - runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter}`); + runCLI( + `generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter} --importPath=@proj/${plugin}` + ); runCLI(`generate @nrwl/nx-plugin:builder ${builder} --project=${plugin}`); const lintResults = runCLI(`lint ${plugin}`); @@ -190,7 +200,7 @@ forEachCli((currentCLIName) => { ensureProject(); const plugin = uniq('plugin'); runCLI( - `generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter} --directory subdir` + `generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter} --directory subdir --importPath=@proj/${plugin}` ); checkFilesExist(`libs/subdir/${plugin}/package.json`); const workspace = readJson(workspaceConfigName()); @@ -206,7 +216,7 @@ forEachCli((currentCLIName) => { ensureProject(); const plugin = uniq('plugin'); runCLI( - `generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter} --tags=e2etag,e2ePackage` + `generate @nrwl/nx-plugin:plugin ${plugin} --linter=${linter} --tags=e2etag,e2ePackage --importPath=@proj/${plugin}` ); const nxJson = readJson('nx.json'); expect(nxJson.projects[plugin].tags).toEqual(['e2etag', 'e2ePackage']); diff --git a/packages/create-nx-plugin/bin/create-nx-plugin.ts b/packages/create-nx-plugin/bin/create-nx-plugin.ts index 6dd9d78f5175d..68db365d7dcfb 100644 --- a/packages/create-nx-plugin/bin/create-nx-plugin.ts +++ b/packages/create-nx-plugin/bin/create-nx-plugin.ts @@ -80,9 +80,11 @@ function createWorkspace( } function createNxPlugin(workspaceName, pluginName) { - console.log(`nx generate @nrwl/nx-plugin:plugin ${pluginName}`); + console.log( + `nx generate @nrwl/nx-plugin:plugin ${pluginName} --importPath=${workspaceName}/${pluginName}` + ); execSync( - `node ./node_modules/@nrwl/cli/bin/nx.js generate @nrwl/nx-plugin:plugin ${pluginName}`, + `node ./node_modules/@nrwl/cli/bin/nx.js generate @nrwl/nx-plugin:plugin ${pluginName} --importPath=${workspaceName}/${pluginName}`, { cwd: workspaceName, stdio: [0, 1, 2], diff --git a/packages/nx-plugin/src/schematics/builder/builder.spec.ts b/packages/nx-plugin/src/schematics/builder/builder.spec.ts index 6ce4f45ce480b..4fc36b48e05bd 100644 --- a/packages/nx-plugin/src/schematics/builder/builder.spec.ts +++ b/packages/nx-plugin/src/schematics/builder/builder.spec.ts @@ -11,7 +11,11 @@ describe('NxPlugin builder', () => { beforeEach(async () => { projectName = 'my-plugin'; appTree = createEmptyWorkspace(ngSchematics.Tree.empty()); - appTree = await runSchematic('plugin', { name: projectName }, appTree); + appTree = await runSchematic( + 'plugin', + { name: projectName, importPath: '@proj/my-plugin' }, + appTree + ); }); it('should generate files', async () => { diff --git a/packages/nx-plugin/src/schematics/migration/migration.spec.ts b/packages/nx-plugin/src/schematics/migration/migration.spec.ts index a99c2f988c715..e66903155e609 100644 --- a/packages/nx-plugin/src/schematics/migration/migration.spec.ts +++ b/packages/nx-plugin/src/schematics/migration/migration.spec.ts @@ -11,7 +11,11 @@ describe('NxPlugin migration', () => { beforeEach(async () => { projectName = 'my-plugin'; appTree = createEmptyWorkspace(ngSchematics.Tree.empty()); - appTree = await runSchematic('plugin', { name: projectName }, appTree); + appTree = await runSchematic( + 'plugin', + { name: projectName, importPath: '@proj/my-plugin' }, + appTree + ); }); it('should update the workspace.json file', async () => { diff --git a/packages/nx-plugin/src/schematics/plugin/plugin.spec.ts b/packages/nx-plugin/src/schematics/plugin/plugin.spec.ts index ec7004687d30a..f44a522dd3c0c 100644 --- a/packages/nx-plugin/src/schematics/plugin/plugin.spec.ts +++ b/packages/nx-plugin/src/schematics/plugin/plugin.spec.ts @@ -10,7 +10,11 @@ describe('NxPlugin plugin', () => { }); it('should update the workspace.json file', async () => { - const tree = await runSchematic('plugin', { name: 'myPlugin' }, appTree); + const tree = await runSchematic( + 'plugin', + { name: 'myPlugin', importPath: '@proj/my-plugin' }, + appTree + ); const workspace = await readWorkspace(tree); const project = workspace.projects['my-plugin']; expect(project.root).toEqual('libs/my-plugin'); @@ -62,7 +66,11 @@ describe('NxPlugin plugin', () => { }); it('should update the tsconfig.lib.json file', async () => { - const tree = await runSchematic('plugin', { name: 'myPlugin' }, appTree); + const tree = await runSchematic( + 'plugin', + { name: 'myPlugin', importPath: '@proj/my-plugin' }, + appTree + ); const tsLibConfig = readJsonInTree( tree, 'libs/my-plugin/tsconfig.lib.json' @@ -71,7 +79,11 @@ describe('NxPlugin plugin', () => { }); it('should create schematic and builder files', async () => { - const tree = await runSchematic('plugin', { name: 'myPlugin' }, appTree); + const tree = await runSchematic( + 'plugin', + { name: 'myPlugin', importPath: '@proj/my-plugin' }, + appTree + ); expect(tree.exists('libs/my-plugin/collection.json')).toBeTruthy(); expect(tree.exists('libs/my-plugin/builders.json')).toBeTruthy(); expect( @@ -118,7 +130,11 @@ describe('NxPlugin plugin', () => { it('should not generate test files', async () => { const tree = await runSchematic( 'plugin', - { name: 'myPlugin', unitTestRunner: 'none' }, + { + name: 'myPlugin', + importPath: '@proj/my-plugin', + unitTestRunner: 'none', + }, appTree ); diff --git a/packages/nx-plugin/src/schematics/plugin/plugin.ts b/packages/nx-plugin/src/schematics/plugin/plugin.ts index 020af1f1b9e74..060624b639bb4 100644 --- a/packages/nx-plugin/src/schematics/plugin/plugin.ts +++ b/packages/nx-plugin/src/schematics/plugin/plugin.ts @@ -22,6 +22,7 @@ export default function (schema: NormalizedSchema): Rule { externalSchematic('@nrwl/node', 'lib', { ...schema, publishable: true, + importPath: schema.importPath, unitTestRunner: options.unitTestRunner, }), addFiles(options), diff --git a/packages/nx-plugin/src/schematics/plugin/schema.d.ts b/packages/nx-plugin/src/schematics/plugin/schema.d.ts index 3843aafadc7b5..9c448eeca4fd5 100644 --- a/packages/nx-plugin/src/schematics/plugin/schema.d.ts +++ b/packages/nx-plugin/src/schematics/plugin/schema.d.ts @@ -3,6 +3,7 @@ import { Linter } from '@nrwl/workspace'; export interface Schema { name: string; directory?: string; + importPath: string; skipTsConfig: boolean; skipFormat: boolean; tags?: string; diff --git a/packages/nx-plugin/src/schematics/plugin/schema.json b/packages/nx-plugin/src/schematics/plugin/schema.json index 28e7d0c2382b8..578fd4d3f2724 100644 --- a/packages/nx-plugin/src/schematics/plugin/schema.json +++ b/packages/nx-plugin/src/schematics/plugin/schema.json @@ -5,7 +5,7 @@ "type": "object", "examples": [ { - "command": "g plugin my-plugin --directory=plugins", + "command": "g plugin my-plugin --directory=plugins --importPath=@myorg/my-plugin", "description": "Generate libs/plugins/my-plugin" } ], @@ -24,6 +24,10 @@ "description": "A directory where the plugin is placed", "alias": "d" }, + "importPath": { + "type": "string", + "description": "How the plugin will be published, like @myorg/my-awesome-plugin. Note this must be a valid npm name" + }, "linter": { "description": "The tool to use for running lint checks.", "type": "string", @@ -52,5 +56,5 @@ "description": "Do not update tsconfig.json for development experience." } }, - "required": ["name"] + "required": ["name", "importPath"] } diff --git a/packages/nx-plugin/src/schematics/schematic/schematic.spec.ts b/packages/nx-plugin/src/schematics/schematic/schematic.spec.ts index 48799a43d6342..5d95c2be4f4c0 100644 --- a/packages/nx-plugin/src/schematics/schematic/schematic.spec.ts +++ b/packages/nx-plugin/src/schematics/schematic/schematic.spec.ts @@ -11,7 +11,11 @@ describe('NxPlugin schematic', () => { beforeEach(async () => { projectName = 'my-plugin'; appTree = createEmptyWorkspace(ngSchematics.Tree.empty()); - appTree = await runSchematic('plugin', { name: projectName }, appTree); + appTree = await runSchematic( + 'plugin', + { name: projectName, importPath: '@proj/my-plugin' }, + appTree + ); }); it('should generate files', async () => {