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 {