From fcecebeb3ad5007920e7ced230ef847b48a062e7 Mon Sep 17 00:00:00 2001 From: Victor Savkin Date: Wed, 10 Nov 2021 15:50:27 -0500 Subject: [PATCH] feat(js): add library and app generators --- CONTRIBUTING.md | 3 +- docs/angular/api-js/generators/application.md | 161 +++++ docs/angular/api-js/generators/library.md | 169 +++++ docs/map.json | 30 + docs/node/api-js/generators/application.md | 161 +++++ docs/node/api-js/generators/library.md | 169 +++++ docs/react/api-js/generators/application.md | 161 +++++ docs/react/api-js/generators/library.md | 169 +++++ e2e/js/project.json | 2 +- e2e/js/src/js.spec.ts | 121 +++- e2e/workspace-core/src/plugins.test.ts | 2 +- packages/js/generators.json | 38 +- packages/js/package.json | 4 +- .../application/application.spec.ts | 75 ++ .../src/generators/application/application.ts | 18 + .../generators/application/files/README.md | 19 + .../application/files/package.json__tmpl__ | 5 + .../src/app/__fileName__.spec.ts__tmpl__ | 7 + .../files/src/app/__fileName__.ts__tmpl__ | 3 + .../application/files/src/index.ts__tmpl__ | 3 + .../files/tsconfig.app.json__tmpl__ | 10 + .../application/files/tsconfig.json__tmpl__ | 14 + .../js/src/generators/application/schema.json | 99 +++ .../js/src/generators/library/files/README.md | 19 + .../library/files/package.json__tmpl__ | 5 + .../library/files/src/index.ts__tmpl__ | 1 + .../src/lib/__fileName__.spec.ts__tmpl__ | 7 + .../files/src/lib/__fileName__.ts__tmpl__ | 3 + .../library/files/tsconfig.json__tmpl__ | 14 + .../library/files/tsconfig.lib.json__tmpl__ | 10 + .../js/src/generators/library/library.spec.ts | 684 ++++++++++++++++++ packages/js/src/generators/library/library.ts | 24 + .../js/src/generators/library/schema.json | 104 +++ packages/js/src/utils/project-generator.ts | 299 ++++++++ packages/js/src/utils/schema.d.ts | 22 + packages/js/src/utils/versions.ts | 1 + .../documentation/generate-generators-data.ts | 2 +- scripts/e2e-build-package-publish.ts | 3 +- scripts/package.sh | 4 +- workspace.json | 2 +- 40 files changed, 2601 insertions(+), 46 deletions(-) create mode 100644 docs/angular/api-js/generators/application.md create mode 100644 docs/angular/api-js/generators/library.md create mode 100644 docs/node/api-js/generators/application.md create mode 100644 docs/node/api-js/generators/library.md create mode 100644 docs/react/api-js/generators/application.md create mode 100644 docs/react/api-js/generators/library.md create mode 100644 packages/js/src/generators/application/application.spec.ts create mode 100644 packages/js/src/generators/application/application.ts create mode 100644 packages/js/src/generators/application/files/README.md create mode 100644 packages/js/src/generators/application/files/package.json__tmpl__ create mode 100644 packages/js/src/generators/application/files/src/app/__fileName__.spec.ts__tmpl__ create mode 100644 packages/js/src/generators/application/files/src/app/__fileName__.ts__tmpl__ create mode 100644 packages/js/src/generators/application/files/src/index.ts__tmpl__ create mode 100644 packages/js/src/generators/application/files/tsconfig.app.json__tmpl__ create mode 100644 packages/js/src/generators/application/files/tsconfig.json__tmpl__ create mode 100644 packages/js/src/generators/application/schema.json create mode 100644 packages/js/src/generators/library/files/README.md create mode 100644 packages/js/src/generators/library/files/package.json__tmpl__ create mode 100644 packages/js/src/generators/library/files/src/index.ts__tmpl__ create mode 100644 packages/js/src/generators/library/files/src/lib/__fileName__.spec.ts__tmpl__ create mode 100644 packages/js/src/generators/library/files/src/lib/__fileName__.ts__tmpl__ create mode 100644 packages/js/src/generators/library/files/tsconfig.json__tmpl__ create mode 100644 packages/js/src/generators/library/files/tsconfig.lib.json__tmpl__ create mode 100644 packages/js/src/generators/library/library.spec.ts create mode 100644 packages/js/src/generators/library/library.ts create mode 100644 packages/js/src/generators/library/schema.json create mode 100644 packages/js/src/utils/project-generator.ts create mode 100644 packages/js/src/utils/schema.d.ts create mode 100644 packages/js/src/utils/versions.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc988c57cc..264a6589f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -222,8 +222,8 @@ The type must be one of the following: The scope must be one of the following: -- angular - anything Angular specific - core - anything Nx core specific +- js - anything related to @nrwl/js package or general js/ts support - nxdev - anything related to docs infrastructure - nextjs - anything Next specific - nest - anything Nest specific @@ -234,6 +234,7 @@ The scope must be one of the following: - storybook - anything Storybook specific - testing - anything testing specific (e.g., jest or cypress) - repo - anything related to managing the repo itself +- angular - anything Angular specific - misc - misc stuff - devkit - devkit-related changes diff --git a/docs/angular/api-js/generators/application.md b/docs/angular/api-js/generators/application.md new file mode 100644 index 0000000000..9b81cafee8 --- /dev/null +++ b/docs/angular/api-js/generators/application.md @@ -0,0 +1,161 @@ +# @nrwl/js:application + +Create a application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `angular.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/js:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +### Examples + +Generate libs/myapp/mylib: + +```bash +nx g lib mylib --directory=myapp +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Library name + +### compiler + +Default: `tsc` + +Type: `string` + +Possible values: `tsc`, `swc` + +The compiler used by the build and test targets + +### config + +Default: `project` + +Type: `string` + +Possible values: `workspace`, `project`, `npm-scripts` + +Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts + +### 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 + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `none` + +The tool to use for running lint checks. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files + +### skipTsConfig + +Default: `false` + +Type: `boolean` + +Do not update tsconfig.json for development experience. + +### strict + +Default: `true` + +Type: `boolean` + +Whether to enable tsconfig strict mode or not. + +### tags + +Type: `string` + +Add tags to the library (used for linting) + +### testEnvironment + +Default: `jsdom` + +Type: `string` + +Possible values: `jsdom`, `node` + +The test environment to use if unitTestRunner is set to jest + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests diff --git a/docs/angular/api-js/generators/library.md b/docs/angular/api-js/generators/library.md new file mode 100644 index 0000000000..2d7dd9ace9 --- /dev/null +++ b/docs/angular/api-js/generators/library.md @@ -0,0 +1,169 @@ +# @nrwl/js:library + +Create a library + +## Usage + +```bash +nx generate library ... +``` + +```bash +nx g lib ... # same +``` + +By default, Nx will search for `library` in the default collection provisioned in `angular.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/js:library ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g library ... --dry-run +``` + +### Examples + +Generate libs/myapp/mylib: + +```bash +nx g lib mylib --directory=myapp +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Library name + +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + +### compiler + +Default: `tsc` + +Type: `string` + +Possible values: `tsc`, `swc` + +The compiler used by the build and test targets + +### config + +Default: `project` + +Type: `string` + +Possible values: `workspace`, `project`, `npm-scripts` + +Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts + +### 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 + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `none` + +The tool to use for running lint checks. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files + +### skipTsConfig + +Default: `false` + +Type: `boolean` + +Do not update tsconfig.json for development experience. + +### strict + +Default: `true` + +Type: `boolean` + +Whether to enable tsconfig strict mode or not. + +### tags + +Type: `string` + +Add tags to the library (used for linting) + +### testEnvironment + +Default: `jsdom` + +Type: `string` + +Possible values: `jsdom`, `node` + +The test environment to use if unitTestRunner is set to jest + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests diff --git a/docs/map.json b/docs/map.json index 6efcf9da9e..4ed877fc03 100644 --- a/docs/map.json +++ b/docs/map.json @@ -359,6 +359,16 @@ "name": "js", "id": "js", "itemList": [ + { + "name": "application generator", + "id": "application", + "file": "angular/api-js/generators/application" + }, + { + "name": "library generator", + "id": "library", + "file": "angular/api-js/generators/library" + }, { "name": "tsc executor", "id": "tsc", @@ -1673,6 +1683,16 @@ "name": "js", "id": "js", "itemList": [ + { + "name": "application generator", + "id": "application", + "file": "react/api-js/generators/application" + }, + { + "name": "library generator", + "id": "library", + "file": "react/api-js/generators/library" + }, { "name": "tsc executor", "id": "tsc", @@ -2951,6 +2971,16 @@ "name": "js", "id": "js", "itemList": [ + { + "name": "application generator", + "id": "application", + "file": "node/api-js/generators/application" + }, + { + "name": "library generator", + "id": "library", + "file": "node/api-js/generators/library" + }, { "name": "tsc executor", "id": "tsc", diff --git a/docs/node/api-js/generators/application.md b/docs/node/api-js/generators/application.md new file mode 100644 index 0000000000..d3cb3f64db --- /dev/null +++ b/docs/node/api-js/generators/application.md @@ -0,0 +1,161 @@ +# @nrwl/js:application + +Create a application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/js:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +### Examples + +Generate libs/myapp/mylib: + +```bash +nx g lib mylib --directory=myapp +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Library name + +### compiler + +Default: `tsc` + +Type: `string` + +Possible values: `tsc`, `swc` + +The compiler used by the build and test targets + +### config + +Default: `project` + +Type: `string` + +Possible values: `workspace`, `project`, `npm-scripts` + +Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts + +### 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 + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `none` + +The tool to use for running lint checks. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files + +### skipTsConfig + +Default: `false` + +Type: `boolean` + +Do not update tsconfig.json for development experience. + +### strict + +Default: `true` + +Type: `boolean` + +Whether to enable tsconfig strict mode or not. + +### tags + +Type: `string` + +Add tags to the library (used for linting) + +### testEnvironment + +Default: `jsdom` + +Type: `string` + +Possible values: `jsdom`, `node` + +The test environment to use if unitTestRunner is set to jest + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests diff --git a/docs/node/api-js/generators/library.md b/docs/node/api-js/generators/library.md new file mode 100644 index 0000000000..c48725f0e4 --- /dev/null +++ b/docs/node/api-js/generators/library.md @@ -0,0 +1,169 @@ +# @nrwl/js:library + +Create a library + +## Usage + +```bash +nx generate library ... +``` + +```bash +nx g lib ... # same +``` + +By default, Nx will search for `library` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/js:library ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g library ... --dry-run +``` + +### Examples + +Generate libs/myapp/mylib: + +```bash +nx g lib mylib --directory=myapp +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Library name + +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + +### compiler + +Default: `tsc` + +Type: `string` + +Possible values: `tsc`, `swc` + +The compiler used by the build and test targets + +### config + +Default: `project` + +Type: `string` + +Possible values: `workspace`, `project`, `npm-scripts` + +Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts + +### 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 + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `none` + +The tool to use for running lint checks. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files + +### skipTsConfig + +Default: `false` + +Type: `boolean` + +Do not update tsconfig.json for development experience. + +### strict + +Default: `true` + +Type: `boolean` + +Whether to enable tsconfig strict mode or not. + +### tags + +Type: `string` + +Add tags to the library (used for linting) + +### testEnvironment + +Default: `jsdom` + +Type: `string` + +Possible values: `jsdom`, `node` + +The test environment to use if unitTestRunner is set to jest + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests diff --git a/docs/react/api-js/generators/application.md b/docs/react/api-js/generators/application.md new file mode 100644 index 0000000000..d3cb3f64db --- /dev/null +++ b/docs/react/api-js/generators/application.md @@ -0,0 +1,161 @@ +# @nrwl/js:application + +Create a application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/js:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +### Examples + +Generate libs/myapp/mylib: + +```bash +nx g lib mylib --directory=myapp +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Library name + +### compiler + +Default: `tsc` + +Type: `string` + +Possible values: `tsc`, `swc` + +The compiler used by the build and test targets + +### config + +Default: `project` + +Type: `string` + +Possible values: `workspace`, `project`, `npm-scripts` + +Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts + +### 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 + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `none` + +The tool to use for running lint checks. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files + +### skipTsConfig + +Default: `false` + +Type: `boolean` + +Do not update tsconfig.json for development experience. + +### strict + +Default: `true` + +Type: `boolean` + +Whether to enable tsconfig strict mode or not. + +### tags + +Type: `string` + +Add tags to the library (used for linting) + +### testEnvironment + +Default: `jsdom` + +Type: `string` + +Possible values: `jsdom`, `node` + +The test environment to use if unitTestRunner is set to jest + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests diff --git a/docs/react/api-js/generators/library.md b/docs/react/api-js/generators/library.md new file mode 100644 index 0000000000..c48725f0e4 --- /dev/null +++ b/docs/react/api-js/generators/library.md @@ -0,0 +1,169 @@ +# @nrwl/js:library + +Create a library + +## Usage + +```bash +nx generate library ... +``` + +```bash +nx g lib ... # same +``` + +By default, Nx will search for `library` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/js:library ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g library ... --dry-run +``` + +### Examples + +Generate libs/myapp/mylib: + +```bash +nx g lib mylib --directory=myapp +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Library name + +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + +### compiler + +Default: `tsc` + +Type: `string` + +Possible values: `tsc`, `swc` + +The compiler used by the build and test targets + +### config + +Default: `project` + +Type: `string` + +Possible values: `workspace`, `project`, `npm-scripts` + +Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts + +### 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 + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `none` + +The tool to use for running lint checks. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files + +### skipTsConfig + +Default: `false` + +Type: `boolean` + +Do not update tsconfig.json for development experience. + +### strict + +Default: `true` + +Type: `boolean` + +Whether to enable tsconfig strict mode or not. + +### tags + +Type: `string` + +Add tags to the library (used for linting) + +### testEnvironment + +Default: `jsdom` + +Type: `string` + +Possible values: `jsdom`, `node` + +The test environment to use if unitTestRunner is set to jest + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests diff --git a/e2e/js/project.json b/e2e/js/project.json index 8e60b21e5e..dd14cc327b 100644 --- a/e2e/js/project.json +++ b/e2e/js/project.json @@ -14,7 +14,7 @@ "command": "yarn e2e-build-package-publish" }, { - "command": "nx run-e2e-tests e2e-jest" + "command": "nx run-e2e-tests e2e-js" } ], "parallel": false diff --git a/e2e/js/src/js.spec.ts b/e2e/js/src/js.spec.ts index e936232d91..322d372622 100644 --- a/e2e/js/src/js.spec.ts +++ b/e2e/js/src/js.spec.ts @@ -1,42 +1,97 @@ import { checkFilesExist, - ensureNxProject, + newProject, readJson, - runNxCommandAsync, + runCLI, + runCLIAsync, + runCommand, uniq, -} from '@nrwl/nx-plugin/testing'; -describe('js e2e', () => { - it('should create js', async () => { - const plugin = uniq('js'); - ensureNxProject('@nrwl/js', 'dist//js'); - await runNxCommandAsync(`generate @nrwl/js:js ${plugin}`); + updateFile, +} from '../../utils'; - const result = await runNxCommandAsync(`build ${plugin}`); - expect(result.stdout).toContain('Executor ran'); +describe('js e2e', () => { + it('should create libs and apps with npm scripts', () => { + const scope = newProject(); + const npmScriptsLib = uniq('npmscriptslib'); + runCLI(`generate @nrwl/js:lib ${npmScriptsLib} --config=npm-scripts`); + const libPackageJson = readJson(`libs/${npmScriptsLib}/package.json`); + expect(libPackageJson.scripts.test).toBeDefined(); + expect(libPackageJson.scripts.build).toBeDefined(); + expect(runCLI(`test ${npmScriptsLib}`)).toContain('implement test'); + expect(runCLI(`test ${npmScriptsLib}`)).toContain('match the cache'); + + const npmScriptsApp = uniq('npmscriptsapp'); + runCLI(`generate @nrwl/js:app ${npmScriptsApp} --config=npm-scripts`); + const appPackageJson = readJson(`apps/${npmScriptsApp}/package.json`); + expect(appPackageJson.scripts.test).toBeDefined(); + expect(appPackageJson.scripts.build).toBeDefined(); + expect(runCLI(`test ${npmScriptsApp}`)).toContain('implement test'); + expect(runCLI(`test ${npmScriptsApp}`)).toContain('match the cache'); + + const tsconfig = readJson(`tsconfig.base.json`); + expect(tsconfig.compilerOptions.paths).toEqual({ + [`@${scope}/${npmScriptsLib}`]: [`libs/${npmScriptsLib}/src/index.ts`], + }); }, 120000); - describe('--directory', () => { - it('should create src in the specified directory', async () => { - const plugin = uniq('js'); - ensureNxProject('@nrwl/js', 'dist//js'); - await runNxCommandAsync( - `generate @nrwl/js:js ${plugin} --directory subdir` - ); - expect(() => - checkFilesExist(`libs/subdir/${plugin}/src/index.ts`) - ).not.toThrow(); - }, 120000); - }); + it('should create libs and apps with js executors (--compiler=tsc)', async () => { + const scope = newProject(); + const lib = uniq('lib'); + runCLI(`generate @nrwl/js:lib ${lib} --buildable --compiler=tsc`); + const libPackageJson = readJson(`libs/${lib}/package.json`); + expect(libPackageJson.scripts).toBeUndefined(); + expect((await runCLIAsync(`test ${lib}`)).combinedOutput).toContain( + 'Ran all test suites' + ); + expect((await runCLIAsync(`test ${lib}`)).combinedOutput).toContain( + 'match the cache' + ); - describe('--tags', () => { - it('should add tags to the project', async () => { - const plugin = uniq('js'); - ensureNxProject('@nrwl/js', 'dist//js'); - await runNxCommandAsync( - `generate @nrwl/js:js ${plugin} --tags e2etag,e2ePackage` - ); - const project = readJson(`libs/${plugin}/project.json`); - expect(project.tags).toEqual(['e2etag', 'e2ePackage']); - }, 120000); - }); + expect(runCLI(`build ${lib}`)).toContain('Done compiling TypeScript files'); + checkFilesExist( + `dist/libs/${lib}/package.json`, + `dist/libs/${lib}/src/index.js`, + `dist/libs/${lib}/src/lib/${lib}.js` + ); + + const app = uniq('app'); + runCLI(`generate @nrwl/js:app ${app} --buildable --compiler=tsc`); + const appPackageJson = readJson(`apps/${app}/package.json`); + expect(appPackageJson.scripts).toBeUndefined(); + expect((await runCLIAsync(`test ${app}`)).combinedOutput).toContain( + 'Ran all test suites' + ); + expect((await runCLIAsync(`test ${app}`)).combinedOutput).toContain( + 'match the cache' + ); + + expect(runCLI(`build ${app}`)).toContain('Done compiling TypeScript files'); + checkFilesExist( + `dist/apps/${app}/package.json`, + `dist/apps/${app}/src/index.js`, + `dist/apps/${app}/src/app/${app}.js` + ); + + expect(runCommand(`node dist/apps/${app}/src/index.js`)).toContain( + `Running ${app}` + ); + + const tsconfig = readJson(`tsconfig.base.json`); + expect(tsconfig.compilerOptions.paths).toEqual({ + [`@${scope}/${lib}`]: [`libs/${lib}/src/index.ts`], + }); + + updateFile(`apps/${app}/src/index.ts`, () => { + return ` + import { ${lib} } from '@${scope}/${lib}' + console.log('Running ' + ${lib}()) + `; + }); + + const output = runCLI(`build ${app}`); + expect(output).toContain('1 task(s) that it depends on'); + expect(output).toContain('Done compiling TypeScript files'); + + // expect(runCommand(`node dist/apps/${app}/src/index.js`)).toContain(`Running ${lib}`) + }, 120000); }); diff --git a/e2e/workspace-core/src/plugins.test.ts b/e2e/workspace-core/src/plugins.test.ts index d2903011b8..6c462e21b4 100644 --- a/e2e/workspace-core/src/plugins.test.ts +++ b/e2e/workspace-core/src/plugins.test.ts @@ -10,7 +10,7 @@ describe('Nx Plugins', () => { beforeAll(() => newProject()); afterAll(() => removeProject({ onlyOnCI: true })); - it('vvvshould use plugins defined in nx.json', () => { + it('should use plugins defined in nx.json', () => { const nxJson = readJson('nx.json'); nxJson.plugins = ['./tools/plugin']; updateFile('nx.json', JSON.stringify(nxJson)); diff --git a/packages/js/generators.json b/packages/js/generators.json index 030db21b58..88dc81fc70 100644 --- a/packages/js/generators.json +++ b/packages/js/generators.json @@ -1,6 +1,36 @@ { - "$schema": "http://json-schema.org/schema", - "name": "js", - "version": "0.0.1", - "generators": {} + "name": "nx/js", + "version": "0.1", + "schematics": { + "library": { + "factory": "./src/generators/library/library#librarySchematic", + "schema": "./src/generators/library/schema.json", + "aliases": ["lib"], + "x-type": "library", + "description": "Create a library" + }, + "application": { + "factory": "./src/generators/application/application#applicationSchematic", + "schema": "./src/generators/application/schema.json", + "aliases": ["app"], + "x-type": "application", + "description": "Create a application" + } + }, + "generators": { + "library": { + "factory": "./src/generators/library/library#libraryGenerator", + "schema": "./src/generators/library/schema.json", + "aliases": ["lib"], + "x-type": "library", + "description": "Create a library" + }, + "application": { + "factory": "./src/generators/application/application#applicationGenerator", + "schema": "./src/generators/application/schema.json", + "aliases": ["app"], + "x-type": "application", + "description": "Create a application" + } + } } diff --git a/packages/js/package.json b/packages/js/package.json index 3f90dda468..c235448873 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -6,6 +6,8 @@ "executors": "./executors.json", "dependencies": { "@nrwl/workspace": "*", - "@nrwl/devkit": "*" + "@nrwl/devkit": "*", + "@nrwl/jest": "*", + "@nrwl/linter": "*" } } diff --git a/packages/js/src/generators/application/application.spec.ts b/packages/js/src/generators/application/application.spec.ts new file mode 100644 index 0000000000..4f2e1dba79 --- /dev/null +++ b/packages/js/src/generators/application/application.spec.ts @@ -0,0 +1,75 @@ +import { readJson, readProjectConfiguration, Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import applicationGenerator from './application'; +import { Schema } from '../../utils/schema'; + +// most of the functionality is tested via the library generator because the library and application share +// most of the code +// only testing the difference here +describe('app', () => { + let tree: Tree; + const defaultOptions: Omit = { + skipTsConfig: false, + unitTestRunner: 'jest', + skipFormat: false, + linter: 'eslint', + testEnvironment: 'jsdom', + js: false, + pascalCaseFiles: false, + strict: true, + config: 'project', + compiler: 'tsc', + }; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(2); + }); + + it('should generate an empty ts app using --config=npm-scripts', async () => { + await applicationGenerator(tree, { + ...defaultOptions, + name: 'myApp', + config: 'npm-scripts', + }); + expect(readJson(tree, '/apps/my-app/package.json')).toEqual({ + name: '@proj/my-app', + version: '0.0.1', + type: 'commonjs', + scripts: { + build: "echo 'implement build'", + test: "echo 'implement test'", + }, + }); + + expect(tree.exists('apps/my-app/src/index.ts')).toBeTruthy(); + expect(tree.exists('apps/my-app/src/app/my-app.ts')).toBeTruthy(); + + // unitTestRunner property is ignored. + // It only works with our executors. + expect(tree.exists('apps/my-app/src/app/my-app.spec.ts')).toBeFalsy(); + const workspaceJson = readJson(tree, '/workspace.json'); + // Blocked on Craigory merging optional config PR + // expect(workspaceJson.projects['my-app']).toBeUndefined(); + // expect(tree.exists('apps/my-app/project.json')).toBeFalsy(); + }); + + it('should generate an empty ts app using --config=project', async () => { + await applicationGenerator(tree, { + ...defaultOptions, + name: 'my-app', + config: 'project', + }); + const projectConfig = readProjectConfiguration(tree, 'my-app'); + expect(projectConfig.root).toEqual('apps/my-app'); + expect(projectConfig.targets.build).toEqual({ + executor: '@nrwl/js:tsc', + options: { + assets: ['apps/my-app/*.md'], + main: 'apps/my-app/src/index.ts', + outputPath: 'dist/apps/my-app', + tsConfig: 'apps/my-app/tsconfig.app.json', + }, + outputs: ['{options.outputPath}'], + }); + }); +}); diff --git a/packages/js/src/generators/application/application.ts b/packages/js/src/generators/application/application.ts new file mode 100644 index 0000000000..8de41fd9bd --- /dev/null +++ b/packages/js/src/generators/application/application.ts @@ -0,0 +1,18 @@ +import { convertNxGenerator, getWorkspaceLayout, Tree } from '@nrwl/devkit'; +import { join } from 'path'; +import { projectGenerator } from '../../utils/project-generator'; +import { Schema } from '../../utils/schema'; + +export async function applicationGenerator(tree: Tree, schema: Schema) { + const { appsDir } = getWorkspaceLayout(tree); + return projectGenerator( + tree, + { ...schema, buildable: true, skipTsConfig: true }, + appsDir, + join(__dirname, './files'), + 'application' + ); +} + +export default applicationGenerator; +export const applicationSchematic = convertNxGenerator(applicationGenerator); diff --git a/packages/js/src/generators/application/files/README.md b/packages/js/src/generators/application/files/README.md new file mode 100644 index 0000000000..698e419c88 --- /dev/null +++ b/packages/js/src/generators/application/files/README.md @@ -0,0 +1,19 @@ +# <%= name %> + +This application was generated with [Nx](https://nx.dev). + +## Building + +Run `<%= cliCommand %> build <%= name %>` to build the application. + +## Starting + +Run `<%= cliCommand %> start <%= name %>` to start/run the application. + +<% if (hasUnitTestRunner) { %> + +## Running unit tests + +Run `<%= cliCommand %> test <%= name %>` to execute the unit tests via [Jest](https://jestjs.io). + +<% } %> diff --git a/packages/js/src/generators/application/files/package.json__tmpl__ b/packages/js/src/generators/application/files/package.json__tmpl__ new file mode 100644 index 0000000000..7ede38f8da --- /dev/null +++ b/packages/js/src/generators/application/files/package.json__tmpl__ @@ -0,0 +1,5 @@ +{ + "name": "<%= importPath %>", + "version": "0.0.1", + "type": "commonjs" +} diff --git a/packages/js/src/generators/application/files/src/app/__fileName__.spec.ts__tmpl__ b/packages/js/src/generators/application/files/src/app/__fileName__.spec.ts__tmpl__ new file mode 100644 index 0000000000..35b0948b95 --- /dev/null +++ b/packages/js/src/generators/application/files/src/app/__fileName__.spec.ts__tmpl__ @@ -0,0 +1,7 @@ +import { <%= propertyName %> } from './<%= fileName %>'; + +describe('<%= propertyName %>', () => { + it('should work', () => { + expect(<%= propertyName %>()).toEqual('<%= name %>'); + }) +}) \ No newline at end of file diff --git a/packages/js/src/generators/application/files/src/app/__fileName__.ts__tmpl__ b/packages/js/src/generators/application/files/src/app/__fileName__.ts__tmpl__ new file mode 100644 index 0000000000..ae311e3ac4 --- /dev/null +++ b/packages/js/src/generators/application/files/src/app/__fileName__.ts__tmpl__ @@ -0,0 +1,3 @@ +export function <%= propertyName %>(): string { + return '<%= name %>'; +} diff --git a/packages/js/src/generators/application/files/src/index.ts__tmpl__ b/packages/js/src/generators/application/files/src/index.ts__tmpl__ new file mode 100644 index 0000000000..430add4c85 --- /dev/null +++ b/packages/js/src/generators/application/files/src/index.ts__tmpl__ @@ -0,0 +1,3 @@ +import {<%= propertyName %>} from './app/<%= fileName %>'; + +console.log(`Running ${<%= propertyName %>()}`) diff --git a/packages/js/src/generators/application/files/tsconfig.app.json__tmpl__ b/packages/js/src/generators/application/files/tsconfig.app.json__tmpl__ new file mode 100644 index 0000000000..2efb9fca0e --- /dev/null +++ b/packages/js/src/generators/application/files/tsconfig.app.json__tmpl__ @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "<%= offsetFromRoot %>dist/out-tsc", + "declaration": true, + "types": [] + }, + "include": ["**/*.ts"<% if (js) { %>, "**/*.js"<% } %>], + "exclude": ["**/*.spec.ts"<% if (js) { %>, "**/*.spec.js"<% } %>] +} diff --git a/packages/js/src/generators/application/files/tsconfig.json__tmpl__ b/packages/js/src/generators/application/files/tsconfig.json__tmpl__ new file mode 100644 index 0000000000..2e4627cddc --- /dev/null +++ b/packages/js/src/generators/application/files/tsconfig.json__tmpl__ @@ -0,0 +1,14 @@ +{ + "extends": "<%= offsetFromRoot %>tsconfig.base.json", + "compilerOptions": { + "module": "CommonJS"<% if (js) { %>, + "allowJs": true<% } %> + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/packages/js/src/generators/application/schema.json b/packages/js/src/generators/application/schema.json new file mode 100644 index 0000000000..eb0d6c2cb7 --- /dev/null +++ b/packages/js/src/generators/application/schema.json @@ -0,0 +1,99 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "NxTypescriptLibrary", + "cli": "nx", + "title": "Create a TypeScript Library", + "type": "object", + "examples": [ + { + "command": "g lib mylib --directory=myapp", + "description": "Generate libs/myapp/mylib" + } + ], + "properties": { + "name": { + "type": "string", + "description": "Library name", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-prompt": "What name would you like to use for the library?", + "pattern": "^[a-zA-Z].*$" + }, + "directory": { + "type": "string", + "description": "A directory where the lib is placed" + }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint", "none"], + "default": "eslint" + }, + "unitTestRunner": { + "type": "string", + "enum": ["jest", "none"], + "description": "Test runner to use for unit tests", + "default": "jest" + }, + "tags": { + "type": "string", + "description": "Add tags to the library (used for linting)" + }, + "skipFormat": { + "description": "Skip formatting files", + "type": "boolean", + "default": false + }, + "skipTsConfig": { + "type": "boolean", + "description": "Do not update tsconfig.json for development experience.", + "default": false + }, + "testEnvironment": { + "type": "string", + "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" + }, + "pascalCaseFiles": { + "type": "boolean", + "description": "Use pascal case file names.", + "alias": "P", + "default": false + }, + "js": { + "type": "boolean", + "description": "Generate JavaScript files rather than TypeScript files", + "default": false + }, + "strict": { + "type": "boolean", + "description": "Whether to enable tsconfig strict mode or not.", + "default": true + }, + "setParserOptionsProject": { + "type": "boolean", + "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", + "default": false + }, + "config": { + "type": "string", + "enum": ["workspace", "project", "npm-scripts"], + "default": "project", + "description": "Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts" + }, + "compiler": { + "type": "string", + "enum": ["tsc", "swc"], + "default": "tsc", + "description": "The compiler used by the build and test targets" + } + }, + "required": ["name"] +} diff --git a/packages/js/src/generators/library/files/README.md b/packages/js/src/generators/library/files/README.md new file mode 100644 index 0000000000..33fea67f35 --- /dev/null +++ b/packages/js/src/generators/library/files/README.md @@ -0,0 +1,19 @@ +# <%= name %> + +This library was generated with [Nx](https://nx.dev). + +<% if (buildable) { %> + +## Building + +Run `<%= cliCommand %> build <%= name %>` to build the library. + +<% } %> + +<% if (hasUnitTestRunner) { %> + +## Running unit tests + +Run `<%= cliCommand %> test <%= name %>` to execute the unit tests via [Jest](https://jestjs.io). + +<% } %> diff --git a/packages/js/src/generators/library/files/package.json__tmpl__ b/packages/js/src/generators/library/files/package.json__tmpl__ new file mode 100644 index 0000000000..7ede38f8da --- /dev/null +++ b/packages/js/src/generators/library/files/package.json__tmpl__ @@ -0,0 +1,5 @@ +{ + "name": "<%= importPath %>", + "version": "0.0.1", + "type": "commonjs" +} diff --git a/packages/js/src/generators/library/files/src/index.ts__tmpl__ b/packages/js/src/generators/library/files/src/index.ts__tmpl__ new file mode 100644 index 0000000000..32176b3ef1 --- /dev/null +++ b/packages/js/src/generators/library/files/src/index.ts__tmpl__ @@ -0,0 +1 @@ +export * from './lib/<%= fileName %>'; diff --git a/packages/js/src/generators/library/files/src/lib/__fileName__.spec.ts__tmpl__ b/packages/js/src/generators/library/files/src/lib/__fileName__.spec.ts__tmpl__ new file mode 100644 index 0000000000..35b0948b95 --- /dev/null +++ b/packages/js/src/generators/library/files/src/lib/__fileName__.spec.ts__tmpl__ @@ -0,0 +1,7 @@ +import { <%= propertyName %> } from './<%= fileName %>'; + +describe('<%= propertyName %>', () => { + it('should work', () => { + expect(<%= propertyName %>()).toEqual('<%= name %>'); + }) +}) \ No newline at end of file diff --git a/packages/js/src/generators/library/files/src/lib/__fileName__.ts__tmpl__ b/packages/js/src/generators/library/files/src/lib/__fileName__.ts__tmpl__ new file mode 100644 index 0000000000..ae311e3ac4 --- /dev/null +++ b/packages/js/src/generators/library/files/src/lib/__fileName__.ts__tmpl__ @@ -0,0 +1,3 @@ +export function <%= propertyName %>(): string { + return '<%= name %>'; +} diff --git a/packages/js/src/generators/library/files/tsconfig.json__tmpl__ b/packages/js/src/generators/library/files/tsconfig.json__tmpl__ new file mode 100644 index 0000000000..f29aa94d03 --- /dev/null +++ b/packages/js/src/generators/library/files/tsconfig.json__tmpl__ @@ -0,0 +1,14 @@ +{ + "extends": "<%= offsetFromRoot %>tsconfig.base.json", + "compilerOptions": { + "module": "CommonJS"<% if (js) { %>, + "allowJs": true<% } %> + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/js/src/generators/library/files/tsconfig.lib.json__tmpl__ b/packages/js/src/generators/library/files/tsconfig.lib.json__tmpl__ new file mode 100644 index 0000000000..2efb9fca0e --- /dev/null +++ b/packages/js/src/generators/library/files/tsconfig.lib.json__tmpl__ @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "<%= offsetFromRoot %>dist/out-tsc", + "declaration": true, + "types": [] + }, + "include": ["**/*.ts"<% if (js) { %>, "**/*.js"<% } %>], + "exclude": ["**/*.spec.ts"<% if (js) { %>, "**/*.spec.js"<% } %>] +} diff --git a/packages/js/src/generators/library/library.spec.ts b/packages/js/src/generators/library/library.spec.ts new file mode 100644 index 0000000000..c266fc2602 --- /dev/null +++ b/packages/js/src/generators/library/library.spec.ts @@ -0,0 +1,684 @@ +import { + getProjects, + readJson, + readProjectConfiguration, + Tree, + updateJson, +} from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { Schema } from '../../utils/schema'; +import libraryGenerator from './library'; + +describe('lib', () => { + let tree: Tree; + const defaultOptions: Omit = { + skipTsConfig: false, + unitTestRunner: 'jest', + skipFormat: false, + linter: 'eslint', + testEnvironment: 'jsdom', + js: false, + pascalCaseFiles: false, + strict: true, + config: 'project', + }; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(2); + }); + + describe('configs', () => { + it('should generate an empty ts lib using --config=npm-scripts', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + config: 'npm-scripts', + }); + expect(readJson(tree, '/libs/my-lib/package.json')).toEqual({ + name: '@proj/my-lib', + version: '0.0.1', + type: 'commonjs', + scripts: { + build: "echo 'implement build'", + test: "echo 'implement test'", + }, + }); + + expect(tree.exists('libs/my-lib/src/index.ts')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/lib/my-lib.ts')).toBeTruthy(); + + // unitTestRunner property is ignored. + // It only works with our executors. + expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.ts')).toBeFalsy(); + const workspaceJson = readJson(tree, '/workspace.json'); + // Blocked on Craigory merging optional config PR + // expect(workspaceJson.projects['my-lib']).toBeUndefined(); + // expect(tree.exists('libs/my-lib/project.json')).toBeFalsy(); + }); + + it('should generate an empty ts lib using --config=project', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'my-lib', + config: 'project', + }); + const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[ + 'my-lib' + ]; + const projectConfig = readProjectConfiguration(tree, 'my-lib'); + expect(projectConfig.root).toEqual('libs/my-lib'); + expect(workspaceJsonEntry).toEqual('libs/my-lib'); + }); + + it('should generate an empty ts lib using --config=workspace', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'my-lib', + config: 'workspace', + }); + const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[ + 'my-lib' + ]; + const projectConfig = readProjectConfiguration(tree, 'my-lib'); + expect(projectConfig.root).toEqual('libs/my-lib'); + expect(projectConfig).toMatchObject(workspaceJsonEntry); + }); + }); + + describe('shared options', () => { + describe('not-nested', () => { + it('should update tags', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + tags: 'one,two', + }); + const projects = Object.fromEntries(getProjects(tree)); + expect(projects).toMatchObject({ + 'my-lib': { + tags: ['one', 'two'], + }, + }); + }); + + it('should update root tsconfig.json', async () => { + await libraryGenerator(tree, { ...defaultOptions, 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 update root tsconfig.json (no existing path mappings)', async () => { + updateJson(tree, 'tsconfig.base.json', (json) => { + json.compilerOptions.paths = undefined; + return json; + }); + + await libraryGenerator(tree, { ...defaultOptions, 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 () => { + await libraryGenerator(tree, { ...defaultOptions, name: 'myLib' }); + const tsconfigJson = readJson(tree, 'libs/my-lib/tsconfig.json'); + expect(tsconfigJson).toMatchInlineSnapshot(` + Object { + "compilerOptions": Object { + "forceConsistentCasingInFileNames": true, + "module": "CommonJS", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "strict": true, + }, + "extends": "../../tsconfig.base.json", + "files": Array [], + "include": Array [], + "references": Array [ + Object { + "path": "./tsconfig.lib.json", + }, + Object { + "path": "./tsconfig.spec.json", + }, + ], + } + `); + }); + }); + + describe('nested', () => { + it('should update tags', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + tags: 'one', + }); + let projects = Object.fromEntries(getProjects(tree)); + expect(projects).toMatchObject({ + 'my-dir-my-lib': { + tags: ['one'], + }, + }); + + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib2', + directory: 'myDir', + tags: 'one,two', + simpleModuleName: true, + }); + projects = Object.fromEntries(getProjects(tree)); + expect(projects).toMatchObject({ + 'my-dir-my-lib': { + tags: ['one'], + }, + 'my-dir-my-lib2': { + tags: ['one', 'two'], + }, + }); + }); + + it('should generate files', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + 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(); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.ts') + ).toBeTruthy(); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.spec.ts') + ).toBeTruthy(); + expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy(); + expect(tree.exists(`libs/my-dir/my-lib/.eslintrc.json`)).toBeTruthy(); + expect(tree.exists(`libs/my-dir/my-lib/package.json`)).toBeFalsy(); + }); + + it('should update workspace.json', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + config: 'workspace', + }); + const workspaceJson = readJson(tree, '/workspace.json'); + + expect(workspaceJson.projects['my-dir-my-lib'].root).toEqual( + 'libs/my-dir/my-lib' + ); + }); + + it('should update tsconfig.json', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + 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']); + expect( + tsconfigJson.compilerOptions.paths['my-dir-my-lib/*'] + ).toBeUndefined(); + }); + + it('should create a local tsconfig.json', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + }); + + const tsconfigJson = readJson(tree, 'libs/my-dir/my-lib/tsconfig.json'); + expect(tsconfigJson.references).toEqual([ + { + path: './tsconfig.lib.json', + }, + { + path: './tsconfig.spec.json', + }, + ]); + }); + }); + + describe('--strict', () => { + it('should update the projects tsconfig with strict false', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + strict: false, + }); + const tsconfigJson = readJson(tree, '/libs/my-lib/tsconfig.json'); + + expect(tsconfigJson.compilerOptions?.strict).not.toBeDefined(); + expect( + tsconfigJson.compilerOptions?.forceConsistentCasingInFileNames + ).not.toBeDefined(); + expect( + tsconfigJson.compilerOptions?.noImplicitReturns + ).not.toBeDefined(); + expect( + tsconfigJson.compilerOptions?.noFallthroughCasesInSwitch + ).not.toBeDefined(); + }); + + it('should default to strict true', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + }); + const tsconfigJson = readJson(tree, '/libs/my-lib/tsconfig.json'); + + expect(tsconfigJson.compilerOptions.strict).toBeTruthy(); + expect( + tsconfigJson.compilerOptions.forceConsistentCasingInFileNames + ).toBeTruthy(); + expect(tsconfigJson.compilerOptions.noImplicitReturns).toBeTruthy(); + expect( + tsconfigJson.compilerOptions.noFallthroughCasesInSwitch + ).toBeTruthy(); + }); + }); + + describe('--importPath', () => { + it('should update the tsconfig with the given import path', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + importPath: '@myorg/lib', + }); + const tsconfigJson = readJson(tree, '/tsconfig.base.json'); + + expect(tsconfigJson.compilerOptions.paths['@myorg/lib']).toBeDefined(); + }); + + it('should fail if the same importPath has already been used', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib1', + importPath: '@myorg/lib', + }); + + try { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib2', + importPath: '@myorg/lib', + }); + } catch (e) { + expect(e.message).toContain( + 'You already have a library using the import path' + ); + } + + expect.assertions(1); + }); + }); + + describe('--pascalCaseFiles', () => { + it('should generate files with upper case names', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + pascalCaseFiles: true, + }); + 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 () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + pascalCaseFiles: true, + }); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.ts') + ).toBeTruthy(); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.spec.ts') + ).toBeTruthy(); + }); + }); + }); + + describe('--linter', () => { + it('should add eslint dependencies', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + }); + + const packageJson = readJson(tree, 'package.json'); + expect(packageJson.devDependencies['eslint']).toBeDefined(); + expect(packageJson.devDependencies['@nrwl/linter']).toBeDefined(); + expect( + packageJson.devDependencies['@nrwl/eslint-plugin-nx'] + ).toBeDefined(); + }); + + describe('not nested', () => { + it('should update configuration', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + }); + expect(readProjectConfiguration(tree, 'my-lib').targets.lint).toEqual({ + executor: '@nrwl/linter:eslint', + outputs: ['{options.outputFile}'], + options: { + lintFilePatterns: ['libs/my-lib/**/*.ts'], + }, + }); + }); + + it('should create a local .eslintrc.json', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + }); + + const eslintJson = readJson(tree, 'libs/my-lib/.eslintrc.json'); + expect(eslintJson).toMatchInlineSnapshot(` + Object { + "extends": Array [ + "../../.eslintrc.json", + ], + "ignorePatterns": Array [ + "!**/*", + ], + "overrides": Array [ + Object { + "files": Array [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx", + ], + "rules": Object {}, + }, + Object { + "files": Array [ + "*.ts", + "*.tsx", + ], + "rules": Object {}, + }, + Object { + "files": Array [ + "*.js", + "*.jsx", + ], + "rules": Object {}, + }, + ], + } + `); + }); + }); + + describe('nested', () => { + it('should update configuration', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + }); + + expect( + readProjectConfiguration(tree, 'my-dir-my-lib').targets.lint + ).toEqual({ + executor: '@nrwl/linter:eslint', + outputs: ['{options.outputFile}'], + options: { + lintFilePatterns: ['libs/my-dir/my-lib/**/*.ts'], + }, + }); + }); + + it('should create a local .eslintrc.json', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + }); + + const eslintJson = readJson(tree, 'libs/my-dir/my-lib/.eslintrc.json'); + expect(eslintJson).toMatchInlineSnapshot(` + Object { + "extends": Array [ + "../../../.eslintrc.json", + ], + "ignorePatterns": Array [ + "!**/*", + ], + "overrides": Array [ + Object { + "files": Array [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx", + ], + "rules": Object {}, + }, + Object { + "files": Array [ + "*.ts", + "*.tsx", + ], + "rules": Object {}, + }, + Object { + "files": Array [ + "*.js", + "*.jsx", + ], + "rules": Object {}, + }, + ], + } + `); + }); + }); + + describe('--js flag', () => { + it('should generate js files instead of ts files', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + js: true, + }); + expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/index.js')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/lib/my-lib.js')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.js')).toBeTruthy(); + }); + + it('should update tsconfig.json with compilerOptions.allowJs: true', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + js: true, + }); + expect( + readJson(tree, 'libs/my-lib/tsconfig.json').compilerOptions.allowJs + ).toBeTruthy(); + }); + + it('should update tsconfig.lib.json include with **/*.js glob', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + js: true, + }); + expect(readJson(tree, 'libs/my-lib/tsconfig.lib.json').include).toEqual( + ['**/*.ts', '**/*.js'] + ); + }); + + it('should update root tsconfig.json with a js file path', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + js: true, + }); + const tsconfigJson = readJson(tree, '/tsconfig.base.json'); + expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([ + 'libs/my-lib/src/index.js', + ]); + }); + + it('should generate js files for nested libs as well', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + js: true, + }); + 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( + tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.js') + ).toBeTruthy(); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.spec.js') + ).toBeTruthy(); + expect(tree.exists('libs/my-dir/my-lib/src/index.js')).toBeTruthy(); + }); + + it('should configure the project for linting js files', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + directory: 'myDir', + js: true, + }); + expect( + readProjectConfiguration(tree, 'my-dir-my-lib').targets.lint.options + .lintFilePatterns + ).toEqual(['libs/my-dir/my-lib/**/*.js']); + expect(readJson(tree, 'libs/my-dir/my-lib/.eslintrc.json')) + .toMatchInlineSnapshot(` + Object { + "extends": Array [ + "../../../.eslintrc.json", + ], + "ignorePatterns": Array [ + "!**/*", + ], + "overrides": Array [ + Object { + "files": Array [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx", + ], + "rules": Object {}, + }, + Object { + "files": Array [ + "*.ts", + "*.tsx", + ], + "rules": Object {}, + }, + Object { + "files": Array [ + "*.js", + "*.jsx", + ], + "rules": Object {}, + }, + ], + } + `); + }); + }); + }); + + describe('--unit-test-runner jest', () => { + it('should generate test configuration', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + unitTestRunner: 'jest', + }); + + expect(tree.exists('libs/my-lib/tsconfig.spec.json')).toBeTruthy(); + expect(tree.exists('libs/my-lib/jest.config.js')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.ts')).toBeTruthy(); + + const projectConfig = readProjectConfiguration(tree, 'my-lib'); + expect(projectConfig.targets.test).toBeDefined(); + + expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy(); + expect(tree.read(`libs/my-lib/jest.config.js`, 'utf-8')) + .toMatchInlineSnapshot(` + "module.exports = { + displayName: 'my-lib', + preset: '../../jest.preset.js', + globals: { + 'ts-jest': { + tsconfig: '/tsconfig.spec.json', + } + }, + transform: { + '^.+\\\\\\\\.[tj]sx?$': 'ts-jest' + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/libs/my-lib' + }; + " + `); + const readme = tree.read('libs/my-lib/README.md', 'utf-8'); + expect(readme).toContain('nx test my-lib'); + }); + + describe('--buildable', () => { + it('should generate the build target', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + buildable: true, + compiler: 'tsc', + }); + + const config = readProjectConfiguration(tree, 'my-lib'); + expect(config.targets.build).toEqual({ + executor: '@nrwl/js:tsc', + options: { + assets: ['libs/my-lib/*.md'], + main: 'libs/my-lib/src/index.ts', + outputPath: 'dist/libs/my-lib', + tsConfig: 'libs/my-lib/tsconfig.lib.json', + }, + outputs: ['{options.outputPath}'], + }); + }); + + it('should generate a package.json file', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + name: 'myLib', + buildable: true, + compiler: 'tsc', + }); + + expect(tree.exists('libs/my-lib/package.json')).toBeTruthy(); + }); + }); + }); +}); diff --git a/packages/js/src/generators/library/library.ts b/packages/js/src/generators/library/library.ts new file mode 100644 index 0000000000..a1abcd8496 --- /dev/null +++ b/packages/js/src/generators/library/library.ts @@ -0,0 +1,24 @@ +import { + convertNxGenerator, + getWorkspaceLayout, + joinPathFragments, + Tree, + updateJson, +} from '@nrwl/devkit'; +import { join } from 'path'; +import { projectGenerator } from '../../utils/project-generator'; +import { Schema } from '../../utils/schema'; + +export async function libraryGenerator(tree: Tree, schema: Schema) { + const { libsDir } = getWorkspaceLayout(tree); + return projectGenerator( + tree, + schema, + libsDir, + join(__dirname, './files'), + 'library' + ); +} + +export default libraryGenerator; +export const librarySchematic = convertNxGenerator(libraryGenerator); diff --git a/packages/js/src/generators/library/schema.json b/packages/js/src/generators/library/schema.json new file mode 100644 index 0000000000..146c3f3a03 --- /dev/null +++ b/packages/js/src/generators/library/schema.json @@ -0,0 +1,104 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "NxTypescriptLibrary", + "cli": "nx", + "title": "Create a TypeScript Library", + "type": "object", + "examples": [ + { + "command": "g lib mylib --directory=myapp", + "description": "Generate libs/myapp/mylib" + } + ], + "properties": { + "name": { + "type": "string", + "description": "Library name", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-prompt": "What name would you like to use for the library?", + "pattern": "^[a-zA-Z].*$" + }, + "directory": { + "type": "string", + "description": "A directory where the lib is placed" + }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint", "none"], + "default": "eslint" + }, + "unitTestRunner": { + "type": "string", + "enum": ["jest", "none"], + "description": "Test runner to use for unit tests", + "default": "jest" + }, + "tags": { + "type": "string", + "description": "Add tags to the library (used for linting)" + }, + "skipFormat": { + "description": "Skip formatting files", + "type": "boolean", + "default": false + }, + "skipTsConfig": { + "type": "boolean", + "description": "Do not update tsconfig.json for development experience.", + "default": false + }, + "testEnvironment": { + "type": "string", + "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" + }, + "pascalCaseFiles": { + "type": "boolean", + "description": "Use pascal case file names.", + "alias": "P", + "default": false + }, + "js": { + "type": "boolean", + "description": "Generate JavaScript files rather than TypeScript files", + "default": false + }, + "strict": { + "type": "boolean", + "description": "Whether to enable tsconfig strict mode or not.", + "default": true + }, + "buildable": { + "type": "boolean", + "default": false, + "description": "Generate a buildable library." + }, + "setParserOptionsProject": { + "type": "boolean", + "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", + "default": false + }, + "config": { + "type": "string", + "enum": ["workspace", "project", "npm-scripts"], + "default": "project", + "description": "Determines how whether the project's executors should be configured in workspace.json, project.json or as npm scripts" + }, + "compiler": { + "type": "string", + "enum": ["tsc", "swc"], + "default": "tsc", + "description": "The compiler used by the build and test targets" + } + }, + "required": ["name"] +} diff --git a/packages/js/src/utils/project-generator.ts b/packages/js/src/utils/project-generator.ts new file mode 100644 index 0000000000..410529d95d --- /dev/null +++ b/packages/js/src/utils/project-generator.ts @@ -0,0 +1,299 @@ +import { + addProjectConfiguration, + formatFiles, + generateFiles, + GeneratorCallback, + getWorkspaceLayout, + joinPathFragments, + names, + offsetFromRoot, + ProjectConfiguration, + toJS, + Tree, + updateJson, +} from '@nrwl/devkit'; +import { join } from 'path'; +import { Schema } from './schema'; +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; + +// nx-ignore-next-line +const { jestProjectGenerator } = require('@nrwl/jest'); +// nx-ignore-next-line +const { lintProjectGenerator, Linter } = require('@nrwl/linter'); + +export async function projectGenerator( + tree: Tree, + schema: Schema, + destinationDir: string, + filesDir: string, + projectType: 'library' | 'application' +) { + const options = normalizeOptions(tree, schema, destinationDir); + + createFiles(tree, options, filesDir); + + addProject(tree, options, destinationDir, projectType); + + if (!schema.skipTsConfig) { + updateRootTsConfig(tree, options); + } + + const tasks: GeneratorCallback[] = []; + + if (options.linter !== 'none') { + const lintCallback = await addLint(tree, options); + tasks.push(lintCallback); + } + if (options.unitTestRunner === 'jest') { + const jestCallback = await addJest(tree, options); + tasks.push(jestCallback); + } + + if (!options.skipFormat) { + await formatFiles(tree); + } + + return runTasksInSerial(...tasks); +} + +export interface NormalizedSchema extends Schema { + name: string; + fileName: string; + projectRoot: string; + projectDirectory: string; + parsedTags: string[]; + importPath?: string; +} + +function addProject( + tree: Tree, + options: NormalizedSchema, + destinationDir: string, + projectType: 'library' | 'application' +) { + const projectConfiguration: ProjectConfiguration = { + root: options.projectRoot, + sourceRoot: joinPathFragments(options.projectRoot, 'src'), + projectType: projectType, + targets: {}, + tags: options.parsedTags, + }; + + if (options.buildable && options.config != 'npm-scripts') { + if (options.compiler === 'tsc') { + projectConfiguration.targets.build = { + executor: '@nrwl/js:tsc', + outputs: ['{options.outputPath}'], + options: { + outputPath: `dist/${destinationDir}/${options.projectDirectory}`, + main: + `${options.projectRoot}/src/index` + (options.js ? '.js' : '.ts'), + tsConfig: `${options.projectRoot}/${ + projectType === 'library' + ? 'tsconfig.lib.json' + : 'tsconfig.app.json' + }`, + assets: [`${options.projectRoot}/*.md`], + }, + }; + } else { + throw new Error(`Compiler ${options.compiler} is not supported`); + } + } + + if (options.config === 'workspace') { + addProjectConfiguration(tree, options.name, projectConfiguration, false); + } else if (options.config === 'project') { + addProjectConfiguration(tree, options.name, projectConfiguration, true); + } else { + addProjectConfiguration( + tree, + options.name, + { + root: projectConfiguration.root, + tags: projectConfiguration.tags, + }, + true + ); + } +} + +export function addLint( + tree: Tree, + options: NormalizedSchema +): Promise { + return lintProjectGenerator(tree, { + project: options.name, + linter: options.linter, + skipFormat: true, + tsConfigPaths: [ + joinPathFragments(options.projectRoot, 'tsconfig.lib.json'), + ], + eslintFilePatterns: [ + `${options.projectRoot}/**/*.${options.js ? 'js' : 'ts'}`, + ], + setParserOptionsProject: options.setParserOptionsProject, + }); +} + +function updateTsConfig(tree: Tree, options: NormalizedSchema) { + updateJson(tree, join(options.projectRoot, 'tsconfig.json'), (json) => { + if (options.strict) { + json.compilerOptions = { + ...json.compilerOptions, + forceConsistentCasingInFileNames: true, + strict: true, + noImplicitReturns: true, + noFallthroughCasesInSwitch: true, + }; + } + + return json; + }); +} + +function createFiles(tree: Tree, options: NormalizedSchema, filesDir: string) { + const { className, name, propertyName } = names(options.name); + + generateFiles(tree, filesDir, options.projectRoot, { + ...options, + dot: '.', + className, + name, + propertyName, + js: !!options.js, + cliCommand: 'nx', + strict: undefined, + tmpl: '', + offsetFromRoot: offsetFromRoot(options.projectRoot), + buildable: options.buildable === true, + hasUnitTestRunner: options.unitTestRunner !== 'none', + }); + + if (options.unitTestRunner === 'none') { + tree.delete( + join(options.projectRoot, 'src/lib', `${options.fileName}.spec.ts`) + ); + tree.delete( + join(options.projectRoot, 'src/app', `${options.fileName}.spec.ts`) + ); + } + + if (options.js) { + toJS(tree); + } + + const packageJsonPath = join(options.projectRoot, 'package.json'); + if (options.config === 'npm-scripts') { + updateJson(tree, packageJsonPath, (json) => { + json.scripts = { + build: "echo 'implement build'", + test: "echo 'implement test'", + }; + return json; + }); + } else if (!options.buildable) { + tree.delete(packageJsonPath); + } + + updateTsConfig(tree, options); +} + +async function addJest( + tree: Tree, + options: NormalizedSchema +): Promise { + return await jestProjectGenerator(tree, { + project: options.name, + setupFile: 'none', + supportTsx: true, + skipSerializers: true, + testEnvironment: options.testEnvironment, + skipFormat: true, + }); +} + +function normalizeOptions( + tree: Tree, + options: Schema, + destinationDir: string +): NormalizedSchema { + if (options.config === 'npm-scripts') { + options.unitTestRunner = 'none'; + options.linter = Linter.None; + options.buildable = false; + } + + const name = names(options.name).fileName; + const projectDirectory = options.directory + ? `${names(options.directory).fileName}/${name}` + : name; + + if (!options.unitTestRunner && options.config !== 'npm-scripts') { + options.unitTestRunner = 'jest'; + } + + if (!options.linter && options.config !== 'npm-scripts') { + options.linter = Linter.EsLint; + } + + const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); + const fileName = getCaseAwareFileName({ + fileName: options.simpleModuleName ? name : projectName, + pascalCaseFiles: options.pascalCaseFiles, + }); + + const { npmScope } = getWorkspaceLayout(tree); + + const projectRoot = joinPathFragments(destinationDir, projectDirectory); + + const parsedTags = options.tags + ? options.tags.split(',').map((s) => s.trim()) + : []; + + const defaultImportPath = `@${npmScope}/${projectDirectory}`; + const importPath = options.importPath || defaultImportPath; + + return { + ...options, + fileName, + name: projectName, + projectRoot, + projectDirectory, + parsedTags, + importPath, + }; +} + +function getCaseAwareFileName(options: { + pascalCaseFiles: boolean; + fileName: string; +}) { + const normalized = names(options.fileName); + + return options.pascalCaseFiles ? normalized.className : normalized.fileName; +} + +function updateRootTsConfig(host: Tree, options: NormalizedSchema) { + updateJson(host, 'tsconfig.base.json', (json) => { + const c = json.compilerOptions; + c.paths = c.paths || {}; + delete c.paths[options.name]; + + if (c.paths[options.importPath]) { + throw new Error( + `You already have a library using the import path "${options.importPath}". Make sure to specify a unique one.` + ); + } + + c.paths[options.importPath] = [ + joinPathFragments( + options.projectRoot, + './src', + 'index.' + (options.js ? 'js' : 'ts') + ), + ]; + + return json; + }); +} diff --git a/packages/js/src/utils/schema.d.ts b/packages/js/src/utils/schema.d.ts new file mode 100644 index 0000000000..8c3a187780 --- /dev/null +++ b/packages/js/src/utils/schema.d.ts @@ -0,0 +1,22 @@ +// nx-ignore-next-line +const { Linter } = require('@nrwl/linter'); + +export interface Schema { + name: string; + directory?: string; + skipFormat?: boolean; + tags?: string; + simpleModuleName?: boolean; + skipTsConfig?: boolean; + unitTestRunner?: 'jest' | 'none'; + linter?: Linter; + testEnvironment?: 'jsdom' | 'node'; + importPath?: string; + js?: boolean; + pascalCaseFiles?: boolean; + strict?: boolean; + buildable?: boolean; + setParserOptionsProject?: boolean; + config?: 'workspace' | 'project' | 'npm-scripts'; + compiler?: 'tsc' | 'swc'; +} diff --git a/packages/js/src/utils/versions.ts b/packages/js/src/utils/versions.ts new file mode 100644 index 0000000000..df701d624b --- /dev/null +++ b/packages/js/src/utils/versions.ts @@ -0,0 +1 @@ +export const nxVersion = '*'; diff --git a/scripts/documentation/generate-generators-data.ts b/scripts/documentation/generate-generators-data.ts index 8df5530a61..fde6142ec2 100644 --- a/scripts/documentation/generate-generators-data.ts +++ b/scripts/documentation/generate-generators-data.ts @@ -25,7 +25,7 @@ import { createSchemaFlattener, SchemaFlattener } from './schema-flattener'; /** * @WhatItDoes: Generates default documentation from the schematics' schema. * We need to construct an Array of objects containing all the information - * of the schematics and their associates schema info. It should be easily + * of the schematics and their associated schema info. It should be easily * parsable in order to be used in a rendering process using template. This * in order to generate a markdown file for each available schematic. */ diff --git a/scripts/e2e-build-package-publish.ts b/scripts/e2e-build-package-publish.ts index 17e9d09469..e75346f0a7 100644 --- a/scripts/e2e-build-package-publish.ts +++ b/scripts/e2e-build-package-publish.ts @@ -69,7 +69,7 @@ async function publishPackage(packagePath: string, npmMajorVersion: number) { // NPM@7 requires a token to publish, thus, is just a matter of fake a token to bypass npm. // See: https://twitter.com/verdaccio_npm/status/1357798427283910660 - if (npmMajorVersion === 7) { + if (npmMajorVersion >= 7) { writeFileSync( `${packagePath}/.npmrc`, `registry=${ @@ -88,6 +88,7 @@ async function publishPackage(packagePath: string, npmMajorVersion: number) { }); } catch (e) { console.log(e); + process.exit(1); } } diff --git a/scripts/package.sh b/scripts/package.sh index 3c24c69e6e..5eff33c332 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -18,7 +18,7 @@ npx nx run-many --target=build --all --parallel || { echo 'Build failed' ; exit cd build/packages if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i "" "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin,react-native,detox}/src/utils/versions.js + sed -i "" "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin,react-native,detox,js}/src/utils/versions.js sed -i "" "s|\*|$NX_VERSION|g" {react,next,gatsby,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin,react-native,detox}/package.json sed -i "" "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "" "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js @@ -29,7 +29,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then sed -i "" "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js sed -i "" "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js else - sed -i "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin,react-native,detox}/src/utils/versions.js + sed -i "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin,react-native,detox,js}/src/utils/versions.js sed -i "s|\*|$NX_VERSION|g" {react,next,gatsby,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin,react-native,detox}/package.json sed -i "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js diff --git a/workspace.json b/workspace.json index e81d195155..c701ec49ca 100644 --- a/workspace.json +++ b/workspace.json @@ -18,6 +18,7 @@ "e2e-detox": "e2e/detox", "e2e-gatsby": "e2e/gatsby", "e2e-jest": "e2e/jest", + "e2e-js": "e2e/js", "e2e-linter": "e2e/linter", "e2e-next": "e2e/next", "e2e-node": "e2e/node", @@ -35,7 +36,6 @@ "gatsby": "packages/gatsby", "jest": "packages/jest", "js": "packages/js", - "e2e-js": "e2e/js", "linter": "packages/linter", "nest": "packages/nest", "next": "packages/next",