Compare commits

...

65 Commits

Author SHA1 Message Date
FrozenPandaz 73407b61cc chore(misc): publish 15.9.7 2023-09-13 10:34:44 -04:00
legobeat 3a85b546a7 fix(misc): backport semver patch to 15.x (#17759) (#18986)
Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
2023-09-13 10:33:06 -04:00
FrozenPandaz d5c9f5139c chore(misc): publish 15.9.6 2023-08-25 11:20:13 -04:00
Jason Jean 57aa1335fc fix(webpack): use new watcher for webpack build coordination plugin (#18822) 2023-08-25 10:09:12 -04:00
FrozenPandaz f770780e7c chore(misc): publish 15.9.5 2023-08-21 13:15:38 -04:00
Leosvel Pérez Espinosa 2e2a2131b1 fix(core): pin @yarnpkg/parsers version to avoid dropping support for node.js v16 (#17860) 2023-08-21 10:51:36 -04:00
FrozenPandaz 06c3e5c9e1 chore(misc): publish 15.9.4 2023-05-05 15:24:31 -04:00
FrozenPandaz 8293700ad4 chore(core): fix snapshot format 2023-05-04 12:56:58 -04:00
Miroslav Jonaš d4946f002c fix(core): ensure safe removal of folders on daemon reset (#16766)
(cherry picked from commit b7b04a7e43)
2023-05-04 11:36:46 -04:00
Jason Jean 16a2b91106 fix(js): default to analyzing source code and package.json (#16737)
(cherry picked from commit 3788723bd2)
2023-05-04 11:36:45 -04:00
Jason Jean 8f6ec8c438 fix(core): improve touched projects locator performance (#16682)
(cherry picked from commit 96fca453b4)
2023-05-04 11:36:45 -04:00
Miroslav Jonaš 226cd68921 fix(core): ensure stale dependencies are pruned in graph (#16533)
(cherry picked from commit f138a34675)
2023-05-04 11:32:25 -04:00
FrozenPandaz 80792b60b9 chore(misc): publish 15.9.3 2023-04-27 13:54:12 -04:00
Jack Hsu 33303fe107 fix(storybook): add @swc/core when adding storybook-addon-swc package for Next.js storybook config (#16553) 2023-04-26 08:43:16 -04:00
Jack Hsu a62db8a989 fix(nextjs): use require("next") since exports.default is no longer provided (#16497)
(cherry picked from commit 786323d5cd)
2023-04-25 12:28:18 -04:00
Nicholas Cunningham 7008532aad fix(nextjs): Let Next.js handler serve public/ folder (#16318)
Co-authored-by: Louis DeScioli <louis@spatial.io>
(cherry picked from commit d54f848810)
2023-04-25 12:28:17 -04:00
Craigory Coppola 2742ed1e12 fix(core): use located tsconfig name for tsconfig-paths when registering local plugin transpiler (#16476)
(cherry picked from commit 0e68441265)
2023-04-25 12:28:17 -04:00
Craigory Coppola ddbf33bbd6 fix(misc): create-nx-workspace should display short error messages (#16474)
(cherry picked from commit ddf613c481)
2023-04-25 12:28:16 -04:00
Craigory Coppola 6ed88eff68 fix(misc): create-nx-workspace should work with custom presets on windows (#16473)
(cherry picked from commit 04a0c7734f)
2023-04-25 12:28:16 -04:00
Jason Jean 0ab5be86c8 fix(core): fix cache recalculation (#16468)
(cherry picked from commit a10b6b1290)
2023-04-25 12:28:15 -04:00
Craigory Coppola 2ca198b00b fix(core): hide message when migrating to a new major and already connected to cloud (#16466)
(cherry picked from commit 505b5dc004)
2023-04-25 12:28:15 -04:00
Miroslav Jonaš 1f714fd722 fix(core): do not expect each package to have hoisted version in lockfile (#16393)
(cherry picked from commit a51ac58a45)
2023-04-25 12:28:14 -04:00
Caleb Ukle 857bde7a7b fix(testing): nullcheck polyfills in ng component testing (#16411)
(cherry picked from commit 1ad7e3de48)
2023-04-25 12:28:14 -04:00
Miroslav Jonaš 426f22a46f fix(linter): don't confuse buildable libs for secondary ng entry point (#16367)
(cherry picked from commit c8960b733f)
2023-04-25 12:28:13 -04:00
Jack Hsu 037919a033 fix(js): update swc options so path mappings can work in all environments (#16390)
(cherry picked from commit ab609a26d8)
2023-04-25 12:28:13 -04:00
Miroslav Jonaš b6d1cad56a fix(core): do not attempt to remove non-existent package from package.json (#16399)
(cherry picked from commit 1b7587f8eb)
2023-04-25 12:28:12 -04:00
Colum Ferry e9725e91b5 fix(angular): throw error when generating component with multiple candidate modules (#16332)
(cherry picked from commit 48b356d9c1)
2023-04-25 12:28:12 -04:00
Katerina Skroumpelou fafe2e451b fix(react): install rollup package when generating lib (#16373)
(cherry picked from commit a4ef9596d9)
2023-04-25 12:28:07 -04:00
Jonathan Cammisuli 121c6bd3c3 fix(core): use the module name as the name of the nx plugin (#16335)
(cherry picked from commit fa40367f2f)
2023-04-24 14:37:47 -04:00
Caleb Ukle 9dfc66cc99 fix(testing): support custom workspaceRoot for angular CT (#15485)
(cherry picked from commit 26fbd1daa0)
2023-04-24 14:37:35 -04:00
Ali Ataf 44a0c83860 fix(angular): consider app-routing.module.ts when setting up module federation host routes (#16285)
Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
(cherry picked from commit 3e45445e26)
2023-04-24 14:37:30 -04:00
Caleb Ukle 33a691ba36 fix(testing): use jest v29 deps in @nrwl/jest (#16256)
(cherry picked from commit b15db0663f)
2023-04-24 14:37:23 -04:00
Craigory Coppola ffe3fb1de6 fix(core): run-many doesn't strip quotes surrounding target names (#16211)
(cherry picked from commit 5da650abb1)
2023-04-24 14:37:19 -04:00
Miroslav Jonaš 7abdfd4fb3 fix(js): report createGlobPatternsForDependencies errors on tailwind (#16266)
(cherry picked from commit e6f6fc14a9)
2023-04-24 14:36:53 -04:00
Matt Lewis 8c4d791309 fix(core): correctly pass resolved compilerOptions to ts-node (#16240)
(cherry picked from commit 98b4aa6561)
2023-04-24 14:36:21 -04:00
Colum Ferry b6e55d8e95 Revert "cleanup(angular): remove karma generation support (#15419)" (#16397) 2023-04-19 14:33:36 +01:00
Emily Xiong f552169f1a chore(expo): disable expo install e2e tests (#16073)
(cherry picked from commit fdbdd989b3)
2023-04-12 12:14:31 -04:00
Miroslav Jonaš 2baf547612 fix(core): respect analyzePackageJson for processProjectGraph (#16220)
(cherry picked from commit afb37c8691)
2023-04-11 12:37:02 -04:00
Craigory Coppola 4f67552bf3 fix(core): deleted projects should not be in cached graph (#16210)
(cherry picked from commit 1df2d68d6d)
2023-04-11 12:36:59 -04:00
LongYinan 6fd9038959 fix(core): only use native hasher if available (#16197)
(cherry picked from commit 7625956b20)
2023-04-11 12:36:46 -04:00
Dmitry Marcautan 5989783f1b fix(misc): shared deps that rely on default configuration are filtered out (#16193)
Co-authored-by: Dmitry Marcautan <dmitry.marcautan@infopulse.com>
(cherry picked from commit 432e07440e)
2023-04-11 12:36:32 -04:00
Caleb Ukle 2361aea4ad fix(node): update ts-jest config for jest 29 (#16201)
(cherry picked from commit 3f4d010ac3)
2023-04-11 12:36:14 -04:00
Caleb Ukle cb5f4fda7e fix(testing): merge reporter/coverage values from vite config (#16165)
(cherry picked from commit 9ba8444930)
2023-04-11 12:35:54 -04:00
Caleb Ukle 86e9c031eb fix(testing): correctly error with jest.mock for invalid filepaths (#15445)
(cherry picked from commit 083f4f12c8)
2023-04-11 12:35:43 -04:00
Rick nagual ec0dff3e3e fix(react-native): add aliasFields to handle replaced files in browser env (#16106)
(cherry picked from commit 7a4adf5893)
2023-04-11 12:35:12 -04:00
Craigory Coppola c46f81b551 fix(core): don't show global version warning when Nx is invoked by itself (#16102)
(cherry picked from commit 2887596099)
2023-04-11 12:34:54 -04:00
Miroslav Jonaš 0867dbaa96 fix(core): ensurePackage fails on pnpm workspaces (#16002)
(cherry picked from commit b3a3f2a724)
2023-04-11 12:34:44 -04:00
Miroslav Jonaš 926908e314 fix(core): fix "migrate-to-inputs" migration (#16145)
(cherry picked from commit aaa1113ab0)
2023-04-11 12:34:35 -04:00
Leosvel Pérez Espinosa 8754cd2c75 fix(angular): fix migrations cli type and use @angular-devkit/build-angular to determine ng devkit version (#16157)
(cherry picked from commit e9f97f87e9)
2023-04-11 12:34:03 -04:00
Caleb Ukle f960776563 fix(testing): prevent loop w/ Cypress watch & Vite processor (#16099)
(cherry picked from commit 8b4e5f6616)
2023-04-11 12:33:21 -04:00
Colum Ferry ac448874f7 fix(angular): host and remote app generation with directory (#16113)
(cherry picked from commit 44c5471dce)
2023-04-11 12:32:53 -04:00
Craigory Coppola 5000fa2bac fix(core): don't error if lock file not present when precomputing js dependencies (#16094)
(cherry picked from commit 18c016bb06)
2023-04-11 12:32:48 -04:00
Nicholas Cunningham b6ae2da4c6 fix(js): remove thirdParty default so that package.json will be generated by default (#16015)
(cherry picked from commit c02ec9f1f7)
2023-04-11 12:32:42 -04:00
Craigory Coppola 995f1a8cdb fix(devkit): tree.children should support writes to directories that have the same name as their parent (#16074)
(cherry picked from commit 63e67bf142)
2023-04-11 12:31:48 -04:00
Craigory Coppola 9a0ab09716 fix(core): should not throw 'unable to resolve nx/package.json' (#16072)
(cherry picked from commit 8092df31e2)
2023-04-11 12:31:13 -04:00
Miroslav Jonaš 2b5e7d7d79 fix(linter): support ESM js imports in ast utils (#16049)
(cherry picked from commit 89d0b8bf6f)
2023-04-11 12:31:03 -04:00
Dmytro Holysh 3bcc072182 fix(core): anyOf should validate if at least 1 condition passes (#14769)
Co-authored-by: AgentEnder <craigorycoppola@gmail.com>
(cherry picked from commit c3ba5ab66f)
2023-04-11 12:30:49 -04:00
Miroslav Jonaš 20cb87b6df fix(core): fix NX_PERF_LOGGING toggles (#16054)
(cherry picked from commit 45a1a6e9c7)
2023-04-11 12:30:19 -04:00
Colum Ferry 1ccff1c424 fix(misc): allow npmScope to be single @ (#16053)
(cherry picked from commit c0d95a7b01)
2023-04-11 12:30:11 -04:00
Katerina Skroumpelou d6bb263c2e fix(storybook): v7 gen docs and install react deps (#16057)
(cherry picked from commit c91c365fa2)
2023-04-11 12:29:59 -04:00
Leosvel Pérez Espinosa d6b80b3239 fix(angular): validate standalone option in the directive generator (#16051)
(cherry picked from commit bf9542a150)
2023-04-11 12:29:48 -04:00
Leosvel Pérez Espinosa c4b602a251 fix(angular): fix path and selector handling in directive generator (#16017)
(cherry picked from commit 9b60863b7b)
2023-04-11 12:29:45 -04:00
Jack Hsu 6f5dbd9321 fix(bundling): add missing @babel/core dependency to @nrwl/webpack (#16023)
(cherry picked from commit bfea455e64)
2023-04-11 12:29:34 -04:00
Craigory Coppola fcaf69caf1 fix(misc): create-nx-workspace should show some stdout if stderr empty on error (#16020)
(cherry picked from commit 778400b1cf)
2023-04-11 12:29:28 -04:00
Miroslav Jonaš 6b398d6f14 fix(core): support yarn berry patches in pruned lock file (#15993)
(cherry picked from commit 30f993e57e)
2023-04-11 12:29:20 -04:00
164 changed files with 2750 additions and 662 deletions
+16
View File
@@ -3723,6 +3723,22 @@
"isExternal": false,
"disableCollapsible": false
},
{
"id": "karma",
"path": "/packages/angular/generators/karma",
"name": "karma",
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{
"id": "karma-project",
"path": "/packages/angular/generators/karma-project",
"name": "karma-project",
"children": [],
"isExternal": false,
"disableCollapsible": false
},
{
"id": "library",
"path": "/packages/angular/generators/library",
+21 -3
View File
@@ -15,12 +15,12 @@
"githubRoot": "https://github.com/nrwl/nx/blob/master",
"name": "angular",
"packageName": "@nrwl/angular",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, Cypress and Karma. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"documents": {
"/packages/angular/documents/overview": {
"id": "overview",
"name": "Overview",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, Cypress and Karma. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"file": "generated/packages/angular/documents/overview",
"itemList": [],
"isExternal": false,
@@ -31,7 +31,7 @@
"/packages/angular/documents/angular-nx-version-matrix": {
"id": "angular-nx-version-matrix",
"name": "Angular and Nx Version Matrix",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, Cypress and Karma. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"file": "generated/packages/angular/documents/angular-nx-version-matrix",
"itemList": [],
"isExternal": false,
@@ -198,6 +198,24 @@
"path": "/packages/angular/generators/init",
"type": "generator"
},
"/packages/angular/generators/karma": {
"description": "Adds Karma configuration to a workspace.",
"file": "generated/packages/angular/generators/karma.json",
"hidden": false,
"name": "karma",
"originalFilePath": "/packages/angular/src/generators/karma/schema.json",
"path": "/packages/angular/generators/karma",
"type": "generator"
},
"/packages/angular/generators/karma-project": {
"description": "Adds Karma configuration to a project.",
"file": "generated/packages/angular/generators/karma-project.json",
"hidden": false,
"name": "karma-project",
"originalFilePath": "/packages/angular/src/generators/karma-project/schema.json",
"path": "/packages/angular/generators/karma-project",
"type": "generator"
},
"/packages/angular/generators/library": {
"description": "Creates an Angular library.",
"file": "generated/packages/angular/generators/library.json",
+21 -3
View File
@@ -11,12 +11,12 @@
"source": "/packages/add-nx-to-monorepo/src"
},
{
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, Cypress and Karma. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"documents": [
{
"id": "overview",
"name": "Overview",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, Cypress and Karma. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"file": "generated/packages/angular/documents/overview",
"itemList": [],
"isExternal": false,
@@ -27,7 +27,7 @@
{
"id": "angular-nx-version-matrix",
"name": "Angular and Nx Version Matrix",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, Cypress and Karma. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"file": "generated/packages/angular/documents/angular-nx-version-matrix",
"itemList": [],
"isExternal": false,
@@ -192,6 +192,24 @@
"path": "angular/generators/init",
"type": "generator"
},
{
"description": "Adds Karma configuration to a workspace.",
"file": "generated/packages/angular/generators/karma.json",
"hidden": false,
"name": "karma",
"originalFilePath": "/packages/angular/src/generators/karma/schema.json",
"path": "angular/generators/karma",
"type": "generator"
},
{
"description": "Adds Karma configuration to a project.",
"file": "generated/packages/angular/generators/karma-project.json",
"hidden": false,
"name": "karma-project",
"originalFilePath": "/packages/angular/src/generators/karma-project/schema.json",
"path": "angular/generators/karma-project",
"type": "generator"
},
{
"description": "Creates an Angular library.",
"file": "generated/packages/angular/generators/library.json",
@@ -96,7 +96,7 @@
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"enum": ["karma", "jest", "none"],
"description": "Test runner to use for unit tests.",
"default": "jest"
},
@@ -54,7 +54,7 @@
"description": "The HTML selector to use for this directive."
},
"standalone": {
"description": "Whether the generated directive is standalone.",
"description": "Whether the generated directive is standalone. _Note: This is only supported in Angular versions >= 14.1.0_.",
"type": "boolean",
"default": false
},
@@ -103,7 +103,7 @@
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"enum": ["karma", "jest", "none"],
"description": "Test runner to use for unit tests.",
"default": "jest"
},
@@ -17,7 +17,7 @@
"properties": {
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"enum": ["karma", "jest", "none"],
"description": "Test runner to use for unit tests.",
"default": "jest",
"x-priority": "important"
@@ -95,7 +95,7 @@
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"enum": ["karma", "jest", "none"],
"description": "Test runner to use for unit tests.",
"default": "jest"
},
@@ -97,7 +97,7 @@
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"enum": ["karma", "jest", "none"],
"description": "Test runner to use for unit tests.",
"default": "jest"
},
@@ -153,8 +153,7 @@
},
"thirdParty": {
"type": "boolean",
"description": "Includes third-party packages in the bundle (i.e. npm packages).",
"default": true
"description": "Includes third-party packages in the bundle (i.e. npm packages)."
},
"dependenciesFieldType": {
"type": "string",
@@ -56,11 +56,17 @@ You can generate Storybook configuration for an individual project with this com
nx g @nrwl/storybook:configuration project-name --storybook7Configuration --storybook7UiFramework=@storybook/react-webpack5
```
Make sure to use the framework-specific generators if your project is using Angular, React, Next.js or React Native: [`@nrwl/angular:storybook-configuration`](/packages/angular/generators/storybook-configuration), [`@nrwl/react:storybook-configuration`](/packages/react/generators/storybook-configuration), [`@nrwl/react-native:storybook-configuration`](/packages/react-native/generators/storybook-configuration), for example:
```shell
nx g @nrwl/angular:storybook-configuration my-angular-project --storybook7Configuration
```
{% callout type="info" title="For Nx versions <15.9" %}
The flag is called `storybook7betaConfiguration` for Nx versions <15.9.
{% /callout %}
In the field `storybook7UiFramework` you must choose one of the following Storybook frameworks:
If you are NOT using a framework-specific generator, in the field `storybook7UiFramework` you must choose one of the following Storybook frameworks:
- `@storybook/angular`
- `@storybook/html-webpack5`
@@ -81,7 +87,7 @@ In the field `storybook7UiFramework` you must choose one of the following Storyb
In Storybook 7, [the `framework` field in `.storybook/main.js|ts` is mandatory](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#framework-field-mandatory). You must choose one of the above frameworks when setting up your application.
If you are using one of the framework-specific generators (e.g. [`@nrwl/angular:storybook-configuration`](/packages/angular/generators/storybook-configuration), or [`@nrwl/react:storybook-configuration`](/packages/react/generators/storybook-configuration) for React and Nextjs projects, or [`@nrwl/react-native:storybook-configuration`](/packages/react-native/generators/storybook-configuration), the generator will automatically choose the correct framework for you.
If you are using one of the framework-specific generators, the generator will automatically choose the correct framework for you.
Choosing one of these frameworks will have the following effects on your workspace:
@@ -89,7 +95,7 @@ Choosing one of these frameworks will have the following effects on your workspa
2. Nx will generate a root `.storybook` folder and a project-level `.storybook` folder (located under `libs/your-project/.storybook` or `apps/your-project/.storybook`) containing the essential configuration files for Storybook.
3. If you are working on an Angular, a React or a React Native project (and you choose `@storybook/angular`, `@storybook/react` or `@storybook/react-native`) the Nx generator will also generate stories for all the components in your project.
3. If you are working on an Angular, a React or a React Native project and you use one of the framework-specific generators, Nx will also generate stories for all the components in your project.
4. Nx will create new `targets` in your project's `project.json`, called `storybook` and `build-storybook`, containing all the necessary configuration to serve and build Storybook.
@@ -56,11 +56,17 @@ You can generate Storybook configuration for an individual project with this com
nx g @nrwl/storybook:configuration project-name --storybook7Configuration --storybook7UiFramework=@storybook/react-webpack5
```
Make sure to use the framework-specific generators if your project is using Angular, React, Next.js or React Native: [`@nrwl/angular:storybook-configuration`](/packages/angular/generators/storybook-configuration), [`@nrwl/react:storybook-configuration`](/packages/react/generators/storybook-configuration), [`@nrwl/react-native:storybook-configuration`](/packages/react-native/generators/storybook-configuration), for example:
```shell
nx g @nrwl/angular:storybook-configuration my-angular-project --storybook7Configuration
```
{% callout type="info" title="For Nx versions <15.9" %}
The flag is called `storybook7betaConfiguration` for Nx versions <15.9.
{% /callout %}
In the field `storybook7UiFramework` you must choose one of the following Storybook frameworks:
If you are NOT using a framework-specific generator, in the field `storybook7UiFramework` you must choose one of the following Storybook frameworks:
- `@storybook/angular`
- `@storybook/html-webpack5`
@@ -81,7 +87,7 @@ In the field `storybook7UiFramework` you must choose one of the following Storyb
In Storybook 7, [the `framework` field in `.storybook/main.js|ts` is mandatory](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#framework-field-mandatory). You must choose one of the above frameworks when setting up your application.
If you are using one of the framework-specific generators (e.g. [`@nrwl/angular:storybook-configuration`](/packages/angular/generators/storybook-configuration), or [`@nrwl/react:storybook-configuration`](/packages/react/generators/storybook-configuration) for React and Nextjs projects, or [`@nrwl/react-native:storybook-configuration`](/packages/react-native/generators/storybook-configuration), the generator will automatically choose the correct framework for you.
If you are using one of the framework-specific generators, the generator will automatically choose the correct framework for you.
Choosing one of these frameworks will have the following effects on your workspace:
@@ -89,7 +95,7 @@ Choosing one of these frameworks will have the following effects on your workspa
2. Nx will generate a root `.storybook` folder and a project-level `.storybook` folder (located under `libs/your-project/.storybook` or `apps/your-project/.storybook`) containing the essential configuration files for Storybook.
3. If you are working on an Angular, a React or a React Native project (and you choose `@storybook/angular`, `@storybook/react` or `@storybook/react-native`) the Nx generator will also generate stories for all the components in your project.
3. If you are working on an Angular, a React or a React Native project and you use one of the framework-specific generators, Nx will also generate stories for all the components in your project.
4. Nx will create new `targets` in your project's `project.json`, called `storybook` and `build-storybook`, containing all the necessary configuration to serve and build Storybook.
@@ -9,8 +9,10 @@ import {
updateFile,
updateProjectConfig,
removeFile,
checkFilesExist,
} from '../../utils';
import { names } from '@nrwl/devkit';
import { join } from 'path';
describe('Angular Cypress Component Tests', () => {
let projectName: string;
@@ -114,6 +116,20 @@ describe('Angular Cypress Component Tests', () => {
);
}
});
it('should use root level tailwinds config', () => {
useRootLevelTailwindConfig(
join('libs', buildableLibName, 'tailwind.config.js')
);
checkFilesExist('tailwind.config.js');
checkFilesDoNotExist(`libs/${buildableLibName}/tailwind.config.js`);
if (runCypressTests()) {
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
'All specs passed!'
);
}
});
});
function createApp(appName: string) {
@@ -386,3 +402,21 @@ function updateBuilableLibTestsToAssertAppStyles(
}
);
}
function useRootLevelTailwindConfig(existingConfigPath: string) {
createFile(
'tailwind.config.js',
`const { join } = require('path');
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [join(__dirname, '**/*.{html,js,ts}')],
theme: {
extend: {},
},
plugins: [],
};
`
);
removeFile(existingConfigPath);
}
+3 -1
View File
@@ -93,7 +93,9 @@ describe('expo', () => {
expect(prebuildResult.combinedOutput).toContain('Config synced');
});
it('should install', async () => {
// TODO(emily): expo-cli always fetches the latest version of react native
// re-enable it when expo-cli is fixed
xit('should install', async () => {
// run install command
const installResults = await runCLIAsync(
`install ${appName} --no-interactive --check`
+5 -4
View File
@@ -134,8 +134,9 @@ export function x() {
// update .swcrc to use path mappings
updateJson(`libs/${lib}/.swcrc`, (json) => {
json.jsc.baseUrl = '.';
json.jsc.paths = {
'src/*': ['src/*'],
'~/*': ['./src/*'],
};
return json;
});
@@ -144,7 +145,7 @@ export function x() {
updateFile(`libs/${lib}/src/lib/${lib}.ts`, () => {
return `
// @ts-ignore
import { x } from 'src/x';
import { x } from '~/x';
export function myLib() {
console.log(x());
@@ -154,8 +155,8 @@ myLib();
`;
});
// now run build
runCLI(`build ${lib}`);
// now run build without type checking (since we're using path mappings not in tsconfig)
runCLI(`build ${lib} --skipTypeCheck`);
// invoke the lib with node
const result = execSync(`node dist/libs/${lib}/src/lib/${lib}.js`, {
+24 -6
View File
@@ -37,9 +37,18 @@ describe('nx init (for React)', () => {
expect(craToNxOutput).toContain('🎉 Done!');
const packageJson = readJson('package.json');
expect(packageJson.devDependencies['@nrwl/jest']).toBeDefined();
expect(packageJson.devDependencies['@nrwl/vite']).toBeUndefined();
expect(packageJson.devDependencies['@nrwl/webpack']).toBeDefined();
expect(
packageJson.devDependencies['@nrwl/jest'] ||
packageJson.devDependencies['@nx/jest']
).toBeDefined();
expect(
packageJson.devDependencies['@nrwl/vite'] ||
packageJson.devDependencies['@nx/vite']
).toBeUndefined();
expect(
packageJson.devDependencies['@nrwl/webpack'] ||
packageJson.devDependencies['@nx/webpack']
).toBeDefined();
runCLI(`build ${appName}`, {
env: {
@@ -68,9 +77,18 @@ describe('nx init (for React)', () => {
expect(craToNxOutput).toContain('🎉 Done!');
const packageJson = readJson('package.json');
expect(packageJson.devDependencies['@nrwl/jest']).toBeUndefined();
expect(packageJson.devDependencies['@nrwl/vite']).toBeDefined();
expect(packageJson.devDependencies['@nrwl/webpack']).toBeUndefined();
expect(
packageJson.devDependencies['@nrwl/jest'] ||
packageJson.devDependencies['@nx/jest']
).toBeUndefined();
expect(
packageJson.devDependencies['@nrwl/vite'] ||
packageJson.devDependencies['@nx/vite']
).toBeDefined();
expect(
packageJson.devDependencies['@nrwl/webpack'] ||
packageJson.devDependencies['@nx/webpack']
).toBeUndefined();
const viteConfig = readFile(`apps/${appName}/vite.config.js`);
expect(viteConfig).toContain('port: 4200'); // default port
+5 -3
View File
@@ -13,8 +13,9 @@ import { ChildProcess, exec, execSync, ExecSyncOptions } from 'child_process';
import { join } from 'path';
import * as isCI from 'is-ci';
import { Workspaces } from '../../packages/nx/src/config/workspaces';
import { updateFile } from './file-utils';
import { exists, updateFile } from './file-utils';
import { logError, stripConsoleColors } from './log-utils';
import { existsSync } from 'fs-extra';
export interface RunCmdOpts {
silenceError?: boolean;
@@ -118,6 +119,7 @@ export function getPackageManagerCommand({
} {
const npmMajorVersion = getNpmMajorVersion();
const publishedVersion = getPublishedVersion();
const isPnpmWorkspace = existsSync(join(path, 'pnpm-workspace.yaml'));
return {
npm: {
@@ -158,8 +160,8 @@ export function getPackageManagerCommand({
runUninstalledPackage: 'pnpm dlx',
install: 'pnpm i',
ciInstall: 'pnpm install --frozen-lockfile',
addProd: `pnpm add`,
addDev: `pnpm add -D`,
addProd: isPnpmWorkspace ? 'pnpm add -w' : 'pnpm add',
addDev: isPnpmWorkspace ? 'pnpm add -Dw' : 'pnpm add -D',
list: 'npm ls --depth 10',
runLerna: `pnpm exec lerna`,
},
+3
View File
@@ -292,6 +292,8 @@ export default defineConfig({
},
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['junit'],
outputFile: 'junit.xml',
coverage: {
enabled: true,
reportsDirectory: 'coverage',
@@ -313,6 +315,7 @@ export default defineConfig({
expect(results).toContain(
`Successfully ran target test for project ${lib}`
);
expect(results).toContain(`JUNIT report written`);
}, 100_000);
it('should be able to run tests with inSourceTests set to true', async () => {
+2 -2
View File
@@ -1,11 +1,11 @@
/* eslint-disable */
export default {
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
'^.+\\.[tj]sx?$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
maxWorkers: 1,
globals: { 'ts-jest': { tsconfig: '<rootDir>/tsconfig.spec.json' } },
globals: {},
displayName: 'e2e-workspace-create-npm',
preset: '../../jest.preset.js',
};
@@ -1,33 +1,48 @@
import {
checkFilesExist,
cleanupProject,
getSelectedPackageManager,
packageInstall,
readJson,
runCLI,
runCommand,
runCreateWorkspace,
uniq,
} from '@nrwl/e2e/utils';
describe('create-nx-workspace --preset=npm', () => {
let wsName;
const wsName = uniq('npm');
let orginalGlobCache;
beforeAll(() => {
orginalGlobCache = process.env.NX_PROJECT_GLOB_CACHE;
// glob cache is causing previous projects to show in Workspace for maxWorkers overrides
// which fails due to files no longer being available
process.env.NX_PROJECT_GLOB_CACHE = 'false';
beforeEach(() => {
wsName = uniq('npm');
runCreateWorkspace(wsName, {
preset: 'npm',
packageManager: getSelectedPackageManager(),
});
});
afterEach(() => cleanupProject());
afterEach(() => {
// cleanup previous projects
runCommand(`rm -rf packages/** tsconfig.base.json`);
});
afterAll(() => {
process.env.NX_PROJECT_GLOB_CACHE = orginalGlobCache;
cleanupProject({ skipReset: true });
});
it('should add angular application', () => {
packageInstall('@nrwl/angular', wsName);
const appName = uniq('my-app');
expect(() => {
runCLI(
`generate @nrwl/angular:app ${appName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/angular:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
}, 1_000_000);
@@ -37,9 +52,7 @@ describe('create-nx-workspace --preset=npm', () => {
const libName = uniq('lib');
expect(() => {
runCLI(
`generate @nrwl/angular:lib ${libName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/angular:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
@@ -48,32 +61,13 @@ describe('create-nx-workspace --preset=npm', () => {
});
}, 1_000_000);
it('should add workspace library', () => {
packageInstall('@nrwl/workspace', wsName);
const libName = uniq('lib');
expect(() =>
runCLI(
`generate @nrwl/workspace:library ${libName} --skipPackageJson --no-interactive`
)
).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
expect(tsconfig.compilerOptions.paths).toEqual({
[libName]: [`packages/${libName}/src/index.ts`],
});
});
it('should add js library', () => {
packageInstall('@nrwl/js', wsName);
const libName = uniq('lib');
expect(() =>
runCLI(
`generate @nrwl/js:library ${libName} --skipPackageJson --no-interactive`
)
runCLI(`generate @nrwl/js:library ${libName} --no-interactive`)
).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
@@ -88,9 +82,7 @@ describe('create-nx-workspace --preset=npm', () => {
const appName = uniq('my-app');
expect(() =>
runCLI(
`generate @nrwl/web:app ${appName} --skipPackageJson --no-interactive`
)
runCLI(`generate @nrwl/web:app ${appName} --no-interactive`)
).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
@@ -101,9 +93,7 @@ describe('create-nx-workspace --preset=npm', () => {
const appName = uniq('my-app');
expect(() => {
runCLI(
`generate @nrwl/react:app ${appName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/react:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
@@ -114,9 +104,7 @@ describe('create-nx-workspace --preset=npm', () => {
const libName = uniq('lib');
expect(() => {
runCLI(
`generate @nrwl/react:lib ${libName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/react:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
@@ -131,9 +119,7 @@ describe('create-nx-workspace --preset=npm', () => {
const appName = uniq('my-app');
expect(() => {
runCLI(
`generate @nrwl/next:app ${appName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/next:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
@@ -144,9 +130,7 @@ describe('create-nx-workspace --preset=npm', () => {
const libName = uniq('lib');
expect(() => {
runCLI(
`generate @nrwl/next:lib ${libName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/next:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
@@ -162,7 +146,7 @@ describe('create-nx-workspace --preset=npm', () => {
expect(() => {
runCLI(
`generate @nrwl/react-native:app ${appName} --install=false --skipPackageJson --no-interactive`
`generate @nrwl/react-native:app ${appName} --install=false --no-interactive`
);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
@@ -174,9 +158,7 @@ describe('create-nx-workspace --preset=npm', () => {
const libName = uniq('lib');
expect(() => {
runCLI(
`generate @nrwl/react-native:lib ${libName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/react-native:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
@@ -191,9 +173,7 @@ describe('create-nx-workspace --preset=npm', () => {
const appName = uniq('my-app');
expect(() => {
runCLI(
`generate @nrwl/node:app ${appName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/node:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
@@ -204,9 +184,7 @@ describe('create-nx-workspace --preset=npm', () => {
const libName = uniq('lib');
expect(() => {
runCLI(
`generate @nrwl/node:lib ${libName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/node:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
@@ -221,9 +199,7 @@ describe('create-nx-workspace --preset=npm', () => {
const appName = uniq('my-app');
expect(() => {
runCLI(
`generate @nrwl/nest:app ${appName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/nest:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
@@ -234,9 +210,7 @@ describe('create-nx-workspace --preset=npm', () => {
const libName = uniq('lib');
expect(() => {
runCLI(
`generate @nrwl/nest:lib ${libName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/nest:lib ${libName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
const tsconfig = readJson(`tsconfig.base.json`);
@@ -251,9 +225,7 @@ describe('create-nx-workspace --preset=npm', () => {
const appName = uniq('my-app');
expect(() => {
runCLI(
`generate @nrwl/express:app ${appName} --skipPackageJson --no-interactive`
);
runCLI(`generate @nrwl/express:app ${appName} --no-interactive`);
}).not.toThrowError();
checkFilesExist('tsconfig.base.json');
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"packages": ["build/packages/*", "build/packages/nx/native-packages/*"],
"version": "15.9.2",
"version": "15.9.7",
"granularPathspec": false,
"command": {
"publish": {
+8
View File
@@ -32,6 +32,14 @@ describe('nx-dev: Packages Section', () => {
path: '/packages/angular/generators/convert-tslint-to-eslint',
},
{ title: '@nrwl/angular:init', path: '/packages/angular/generators/init' },
{
title: '@nrwl/angular:karma',
path: '/packages/angular/generators/karma',
},
{
title: '@nrwl/angular:karma-project',
path: '/packages/angular/generators/karma-project',
},
{
title: '@nrwl/angular:library',
path: '/packages/angular/generators/library',
+3 -3
View File
@@ -115,7 +115,7 @@
"@types/react": "18.0.25",
"@types/react-dom": "18.0.9",
"@types/react-router-dom": "5.1.7",
"@types/semver": "^7.3.8",
"@types/semver": "^7.5.0",
"@types/tar-stream": "^2.2.2",
"@types/tmp": "^0.2.0",
"@types/yargs": "^17.0.10",
@@ -228,7 +228,7 @@
"rxjs-for-await": "0.0.2",
"sass": "1.55.0",
"sass-loader": "^12.2.0",
"semver": "7.3.4",
"semver": "7.5.4",
"source-map": "0.7.3",
"source-map-loader": "^3.0.0",
"source-map-support": "0.5.19",
@@ -287,7 +287,7 @@
"@tailwindcss/line-clamp": "^0.4.2",
"@tailwindcss/typography": "^0.5.7",
"@yarnpkg/lockfile": "^1.1.0",
"@yarnpkg/parsers": "^3.0.0-rc.18",
"@yarnpkg/parsers": "3.0.0-rc.46",
"@zkochan/js-yaml": "0.0.6",
"axios": "1.0.0",
"classnames": "^2.3.1",
+20
View File
@@ -50,6 +50,16 @@
"description": "Initializes the `@nrwl/angular` plugin.",
"hidden": true
},
"karma": {
"factory": "./src/generators/karma/compat",
"schema": "./src/generators/karma/schema.json",
"description": "Adds Karma configuration to a workspace."
},
"karma-project": {
"factory": "./src/generators/karma-project/compat",
"schema": "./src/generators/karma-project/schema.json",
"description": "Adds Karma configuration to a project."
},
"library": {
"factory": "./src/generators/library/library.compat#librarySchematic",
"schema": "./src/generators/library/schema.json",
@@ -207,6 +217,16 @@
"description": "Initializes the `@nrwl/angular` plugin.",
"hidden": true
},
"karma": {
"factory": "./src/generators/karma/karma",
"schema": "./src/generators/karma/schema.json",
"description": "Adds Karma configuration to a workspace."
},
"karma-project": {
"factory": "./src/generators/karma-project/karma-project",
"schema": "./src/generators/karma-project/schema.json",
"description": "Adds Karma configuration to a project."
},
"library": {
"factory": "./src/generators/library/library",
"schema": "./src/generators/library/schema.json",
+2
View File
@@ -7,6 +7,8 @@ export * from './src/generators/convert-tslint-to-eslint/convert-tslint-to-eslin
export * from './src/generators/directive/directive';
export * from './src/generators/host/host';
export * from './src/generators/init/init';
export * from './src/generators/karma-project/karma-project';
export * from './src/generators/karma/karma';
export * from './src/generators/library-secondary-entry-point/library-secondary-entry-point';
export * from './src/generators/library/library';
export * from './src/generators/move/move';
+2 -2
View File
@@ -121,7 +121,7 @@
"factory": "./src/migrations/update-15-2-0/update-workspace-config"
},
"update-platform-server-exports": {
"cli": "ng",
"cli": "nx",
"version": "15.2.0-beta.0",
"requires": {
"@angular/core": ">=15.0.0"
@@ -130,7 +130,7 @@
"factory": "./src/migrations/update-15-2-0/remove-platform-server-exports"
},
"update-karma-main-file": {
"cli": "ng",
"cli": "nx",
"version": "15.2.0-beta.0",
"requires": {
"@angular/core": ">=15.0.0"
+2 -2
View File
@@ -2,7 +2,7 @@
"name": "@nrwl/angular",
"version": "0.0.1",
"private": false,
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, Cypress and Karma. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Upgrading AngularJS applications \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
"repository": {
"type": "git",
"url": "https://github.com/nrwl/nx.git",
@@ -50,7 +50,7 @@
"ignore": "^5.0.4",
"magic-string": "~0.26.2",
"minimatch": "3.0.5",
"semver": "7.3.4",
"semver": "7.5.4",
"ts-node": "10.9.1",
"tsconfig-paths": "^4.1.2",
"webpack": "^5.75.0",
+140 -66
View File
@@ -19,11 +19,11 @@ import {
readCachedProjectGraph,
readTargetOptions,
stripIndents,
workspaceRoot,
} from '@nrwl/devkit';
import { existsSync, lstatSync, mkdirSync, writeFileSync } from 'fs';
import { dirname, join, relative, sep } from 'path';
import type { BrowserBuilderSchema } from '../src/builders/webpack-browser/schema';
import { gte } from 'semver';
/**
* Angular nx preset for Cypress Component Testing
@@ -78,11 +78,14 @@ ${e.stack ? e.stack : e}`
Has project config? ${!!graph.nodes?.[buildTarget.project]?.data}`);
}
const fromWorkspaceRoot = relative(workspaceRoot, pathToConfig);
const fromWorkspaceRoot = relative(ctContext.root, pathToConfig);
const normalizedFromWorkspaceRootPath = lstatSync(pathToConfig).isFile()
? dirname(fromWorkspaceRoot)
: fromWorkspaceRoot;
const offset = offsetFromRoot(normalizedFromWorkspaceRootPath);
const offset = isOffsetNeeded(ctContext, ctProjectConfig)
? offsetFromRoot(normalizedFromWorkspaceRootPath)
: undefined;
const buildContext = createExecutorContext(
graph,
graph.nodes[buildTarget.project]?.data.targets,
@@ -101,6 +104,15 @@ ${e.stack ? e.stack : e}`
...nxBaseCypressPreset(pathToConfig),
// NOTE: cannot use a glob pattern since it will break cypress generated tsconfig.
specPattern: ['src/**/*.cy.ts', 'src/**/*.cy.js'],
// cypress defaults to a relative path from the workspaceRoot instead of projectRoot
// set as absolute path in case this changes internally to cypress, this path isn't OS dependent
indexHtmlFile: joinPathFragments(
ctContext.root,
ctProjectConfig.root,
'cypress',
'support',
'component-index.html'
),
devServer: {
// cypress uses string union type,
// need to use const to prevent typing to string
@@ -156,8 +168,12 @@ function getBuildableTarget(ctContext: ExecutorContext) {
function normalizeBuildTargetOptions(
buildContext: ExecutorContext,
ctContext: ExecutorContext,
offset: string
): { root: string; sourceRoot: string; buildOptions: BrowserBuilderSchema } {
offset?: string
): {
root: string;
sourceRoot: string;
buildOptions: BrowserBuilderSchema & { workspaceRoot: string };
} {
const options = readTargetOptions<BrowserBuilderSchema>(
{
project: buildContext.projectName,
@@ -168,39 +184,45 @@ function normalizeBuildTargetOptions(
);
const buildOptions = withSchemaDefaults(options);
// polyfill entries might be local files or files that are resolved from node_modules
// like zone.js.
// prevents error from webpack saying can't find <offset>/zone.js.
const handlePolyfillPath = (polyfill: string) => {
const maybeFullPath = join(workspaceRoot, polyfill.split('/').join(sep));
if (existsSync(maybeFullPath)) {
return joinPathFragments(offset, polyfill);
}
return polyfill;
};
// paths need to be unix paths for angular devkit
buildOptions.polyfills =
Array.isArray(buildOptions.polyfills) && buildOptions.polyfills.length > 0
? (buildOptions.polyfills as string[]).map((p) => handlePolyfillPath(p))
: handlePolyfillPath(buildOptions.polyfills as string);
buildOptions.main = joinPathFragments(offset, buildOptions.main);
buildOptions.index =
typeof buildOptions.index === 'string'
? joinPathFragments(offset, buildOptions.index)
: {
...buildOptions.index,
input: joinPathFragments(offset, buildOptions.index.input),
};
// cypress creates a tsconfig if one isn't preset
// that contains all the support required for angular and component tests
delete buildOptions.tsConfig;
buildOptions.fileReplacements = buildOptions.fileReplacements.map((fr) => {
fr.replace = joinPathFragments(offset, fr.replace);
fr.with = joinPathFragments(offset, fr.with);
return fr;
});
if (offset) {
// polyfill entries might be local files or files that are resolved from node_modules
// like zone.js.
// prevents error from webpack saying can't find <offset>/zone.js.
const handlePolyfillPath = (polyfill: string) => {
const maybeFullPath = join(ctContext.root, polyfill.split('/').join(sep));
if (existsSync(maybeFullPath)) {
return joinPathFragments(offset, polyfill);
}
return polyfill;
};
// paths need to be unix paths for angular devkit
if (buildOptions.polyfills) {
buildOptions.polyfills =
Array.isArray(buildOptions.polyfills) &&
buildOptions.polyfills.length > 0
? (buildOptions.polyfills as string[]).map((p) =>
handlePolyfillPath(p)
)
: handlePolyfillPath(buildOptions.polyfills as string);
}
buildOptions.main = joinPathFragments(offset, buildOptions.main);
buildOptions.index =
typeof buildOptions.index === 'string'
? joinPathFragments(offset, buildOptions.index)
: {
...buildOptions.index,
input: joinPathFragments(offset, buildOptions.index.input),
};
buildOptions.fileReplacements = buildOptions.fileReplacements.map((fr) => {
fr.replace = joinPathFragments(offset, fr.replace);
fr.with = joinPathFragments(offset, fr.with);
return fr;
});
}
// if the ct project isn't being used in the build project
// then we don't want to have the assets/scripts/styles be included to
@@ -213,29 +235,31 @@ function normalizeBuildTargetOptions(
ctContext.projectName
)
) {
buildOptions.assets = buildOptions.assets.map((asset) => {
return typeof asset === 'string'
? joinPathFragments(offset, asset)
: { ...asset, input: joinPathFragments(offset, asset.input) };
});
buildOptions.styles = buildOptions.styles.map((style) => {
return typeof style === 'string'
? joinPathFragments(offset, style)
: { ...style, input: joinPathFragments(offset, style.input) };
});
buildOptions.scripts = buildOptions.scripts.map((script) => {
return typeof script === 'string'
? joinPathFragments(offset, script)
: { ...script, input: joinPathFragments(offset, script.input) };
});
if (buildOptions.stylePreprocessorOptions?.includePaths.length > 0) {
buildOptions.stylePreprocessorOptions = {
includePaths: buildOptions.stylePreprocessorOptions.includePaths.map(
(path) => {
return joinPathFragments(offset, path);
}
),
};
if (offset) {
buildOptions.assets = buildOptions.assets.map((asset) => {
return typeof asset === 'string'
? joinPathFragments(offset, asset)
: { ...asset, input: joinPathFragments(offset, asset.input) };
});
buildOptions.styles = buildOptions.styles.map((style) => {
return typeof style === 'string'
? joinPathFragments(offset, style)
: { ...style, input: joinPathFragments(offset, style.input) };
});
buildOptions.scripts = buildOptions.scripts.map((script) => {
return typeof script === 'string'
? joinPathFragments(offset, script)
: { ...script, input: joinPathFragments(offset, script.input) };
});
if (buildOptions.stylePreprocessorOptions?.includePaths.length > 0) {
buildOptions.stylePreprocessorOptions = {
includePaths: buildOptions.stylePreprocessorOptions.includePaths.map(
(path) => {
return joinPathFragments(offset, path);
}
),
};
}
}
} else {
const stylePath = getTempStylesForTailwind(ctContext);
@@ -256,9 +280,15 @@ Note: this may fail, setting the correct 'sourceRoot' for ${buildContext.project
}
return {
root: joinPathFragments(offset, config.root),
sourceRoot: joinPathFragments(offset, config.sourceRoot),
buildOptions,
root: offset ? joinPathFragments(offset, config.root) : config.root,
sourceRoot: offset
? joinPathFragments(offset, config.sourceRoot)
: config.sourceRoot,
buildOptions: {
...buildOptions,
// this property is only valid for cy v12.9.0+
workspaceRoot: offset ? undefined : ctContext.root,
},
};
}
@@ -309,13 +339,14 @@ function getTempStylesForTailwind(ctExecutorContext: ExecutorContext) {
ctProjectConfig.root,
'tailwind.config'
);
const isTailWindInCtProject =
existsSync(ctProjectTailwindConfig + '.js') ||
existsSync(ctProjectTailwindConfig + '.cjs');
const exts = ['js', 'cjs'];
const isTailWindInCtProject = exts.some((ext) =>
existsSync(`${ctProjectTailwindConfig}.${ext}`)
);
const rootTailwindPath = join(ctExecutorContext.root, 'tailwind.config');
const isTailWindInRoot =
existsSync(rootTailwindPath + '.js') ||
existsSync(rootTailwindPath + '.cjs');
const isTailWindInRoot = exts.some((ext) =>
existsSync(`${rootTailwindPath}.${ext}`)
);
if (isTailWindInRoot || isTailWindInCtProject) {
const pathToStyle = getTempTailwindPath(ctExecutorContext);
@@ -339,3 +370,46 @@ function getTempStylesForTailwind(ctExecutorContext: ExecutorContext) {
}
}
}
function isOffsetNeeded(
ctExecutorContext: ExecutorContext,
ctProjectConfig: ProjectConfiguration
) {
try {
const { version = null } = require('cypress/package.json');
const supportsWorkspaceRoot = !!version && gte(version, '12.9.0');
// if using cypress <v12.9.0 then we require the offset
if (!supportsWorkspaceRoot) {
return true;
}
if (
ctProjectConfig.projectType === 'library' &&
// angular will only see this config if the library root is the build project config root
// otherwise it will be set to the buildTarget root which is the app root where this config doesn't exist
// causing tailwind styles from the libs project root to not work
['js', 'cjs'].some((ext) =>
existsSync(
join(
ctExecutorContext.root,
ctProjectConfig.root,
`tailwind.config.${ext}`
)
)
)
) {
return true;
}
return false;
} catch (e) {
if (process.env.NX_VERBOSE_LOGGING === 'true') {
logger.error(e);
}
// unable to determine if we don't require an offset
// safest to assume we do
return true;
}
}
@@ -584,6 +584,27 @@ describe('app', () => {
});
});
describe('karma', () => {
it('should generate a karma config', async () => {
await generateApp(appTree, 'myApp', {
unitTestRunner: UnitTestRunner.Karma,
});
expect(appTree.exists('apps/my-app/tsconfig.spec.json')).toBeTruthy();
expect(appTree.exists('apps/my-app/karma.conf.js')).toBeTruthy();
expect(
readProjectConfiguration(appTree, 'my-app').targets.test.executor
).toEqual('@angular-devkit/build-angular:karma');
const tsconfigAppJson = readJson(
appTree,
'apps/my-app/tsconfig.app.json'
);
expect(tsconfigAppJson.compilerOptions.outDir).toEqual(
'../../dist/out-tsc'
);
});
});
describe('none', () => {
it('should not generate test configuration', async () => {
await generateApp(appTree, 'myApp', {
@@ -4,6 +4,7 @@ import type { NormalizedSchema } from './normalized-schema';
import { jestProjectGenerator } from '@nrwl/jest';
import { UnitTestRunner } from '../../../utils/test-runners';
import karmaProjectGenerator from '../../karma-project/karma-project';
export async function addUnitTestRunner(host: Tree, options: NormalizedSchema) {
if (options.unitTestRunner === UnitTestRunner.Jest) {
@@ -15,5 +16,10 @@ export async function addUnitTestRunner(host: Tree, options: NormalizedSchema) {
skipPackageJson: options.skipPackageJson,
skipFormat: true,
});
} else if (options.unitTestRunner === UnitTestRunner.Karma) {
await karmaProjectGenerator(host, {
project: options.name,
skipFormat: true,
});
}
}
@@ -99,7 +99,7 @@
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"enum": ["karma", "jest", "none"],
"description": "Test runner to use for unit tests.",
"default": "jest"
},
@@ -542,6 +542,47 @@ describe('component Generator', () => {
"
`);
});
it('should throw an error when there are more than one candidate modules that the component can be added to', async () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'lib1', {
projectType: 'library',
sourceRoot: 'libs/lib1/src',
root: 'libs/lib1',
});
tree.write(
'libs/lib1/src/lib/lib.module.ts',
`
import { NgModule } from '@angular/core';
@NgModule({
declarations: [],
exports: []
})
export class LibModule {}`
);
tree.write(
'libs/lib1/src/lib/lib2.module.ts',
`
import { NgModule } from '@angular/core';
@NgModule({
declarations: [],
exports: []
})
export class Lib2Module {}`
);
// ACT & ASSERT
await expect(
componentGenerator(tree, {
name: 'example',
project: 'lib1',
path: 'libs/lib1/src/lib',
})
).rejects.toThrow();
});
});
describe('secondary entry points', () => {
@@ -74,7 +74,9 @@ function findModule(
if (filteredMatches.length == 1) {
return filteredMatches[0];
} else if (filteredMatches.length > 1) {
return null;
throw new Error(
"Found more than one candidate module to add the component to. Please specify which module the component should be added to by using the '--module' option."
);
}
dir = dirname(dir);
@@ -12,10 +12,11 @@ describe('directive generator', () => {
addProjectConfiguration(tree, 'test', {
root: 'test',
sourceRoot: 'test/src',
projectType: 'application',
});
tree.write(
'test/src/test.module.ts',
'test/src/app/test.module.ts',
`import {NgModule} from "@angular/core";
@NgModule({
imports: [],
@@ -33,11 +34,13 @@ describe('directive generator', () => {
await generateDirectiveWithDefaultOptions(tree);
// ASSERT
expect(tree.read('test/src/test.directive.ts', 'utf-8')).toMatchSnapshot();
expect(
tree.read('test/src/test.directive.spec.ts', 'utf-8')
tree.read('test/src/app/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(
tree.read('test/src/app/test.directive.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should import the directive correctly when flat=false', async () => {
@@ -48,12 +51,12 @@ describe('directive generator', () => {
// ASSERT
expect(
tree.read('test/src/test/test.directive.ts', 'utf-8')
tree.read('test/src/app/test/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/test/test.directive.spec.ts', 'utf-8')
tree.read('test/src/app/test/test.directive.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should not import the directive when standalone=true', async () => {
@@ -63,11 +66,13 @@ describe('directive generator', () => {
await generateDirectiveWithDefaultOptions(tree, { standalone: true });
// ASSERT
expect(tree.read('test/src/test.directive.ts', 'utf-8')).toMatchSnapshot();
expect(
tree.read('test/src/test.directive.spec.ts', 'utf-8')
tree.read('test/src/app/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(
tree.read('test/src/app/test.directive.spec.ts', 'utf-8')
).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should import the directive correctly when flat=false and path is nested deeper', async () => {
@@ -76,17 +81,20 @@ describe('directive generator', () => {
// ACT
await generateDirectiveWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-directives',
path: 'test/src/app/my-directives',
});
// ASSERT
expect(
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
tree.read('test/src/app/my-directives/test/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/my-directives/test/test.directive.spec.ts', 'utf-8')
tree.read(
'test/src/app/my-directives/test/test.directive.spec.ts',
'utf-8'
)
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should export the directive correctly when flat=false and path is nested deeper', async () => {
@@ -95,18 +103,21 @@ describe('directive generator', () => {
// ACT
await generateDirectiveWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-directives',
path: 'test/src/app/my-directives',
export: true,
});
// ASSERT
expect(
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
tree.read('test/src/app/my-directives/test/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/my-directives/test/test.directive.spec.ts', 'utf-8')
tree.read(
'test/src/app/my-directives/test/test.directive.spec.ts',
'utf-8'
)
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should not import the directive when skipImport=true', async () => {
@@ -115,18 +126,21 @@ describe('directive generator', () => {
// ACT
await generateDirectiveWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-directives',
path: 'test/src/app/my-directives',
skipImport: true,
});
// ASSERT
expect(
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
tree.read('test/src/app/my-directives/test/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.read('test/src/my-directives/test/test.directive.spec.ts', 'utf-8')
tree.read(
'test/src/app/my-directives/test/test.directive.spec.ts',
'utf-8'
)
).toMatchSnapshot();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});
it('should not generate test file when skipTests=true', async () => {
@@ -135,18 +149,18 @@ describe('directive generator', () => {
// ACT
await generateDirectiveWithDefaultOptions(tree, {
flat: false,
path: 'test/src/my-directives',
path: 'test/src/app/my-directives',
skipTests: true,
});
// ASSERT
expect(
tree.read('test/src/my-directives/test/test.directive.ts', 'utf-8')
tree.read('test/src/app/my-directives/test/test.directive.ts', 'utf-8')
).toMatchSnapshot();
expect(
tree.exists('test/src/my-directives/test/test.directive.spec.ts')
tree.exists('test/src/app/my-directives/test/test.directive.spec.ts')
).toBeFalsy();
expect(tree.read('test/src/test.module.ts', 'utf-8')).toMatchSnapshot();
expect(tree.read('test/src/app/test.module.ts', 'utf-8')).toMatchSnapshot();
});
});
@@ -1,58 +1,40 @@
import type { ProjectConfiguration, Tree } from '@nrwl/devkit';
import type { Tree } from '@nrwl/devkit';
import {
formatFiles,
generateFiles,
getProjects,
joinPathFragments,
names,
readNxJson,
readProjectConfiguration,
} from '@nrwl/devkit';
import type { Schema } from './schema';
import { checkPathUnderProjectRoot } from '../utils/path';
import { addToNgModule, findModule } from '../utils';
let tsModule: typeof import('typescript');
import { normalizeOptions, validateOptions } from './lib';
import type { Schema } from './schema';
export async function directiveGenerator(tree: Tree, schema: Schema) {
const projects = getProjects(tree);
if (!projects.has(schema.project)) {
throw new Error(`Project "${schema.project}" does not exist!`);
}
validateOptions(tree, schema);
const options = normalizeOptions(tree, schema);
checkPathUnderProjectRoot(tree, schema.project, schema.path);
const directiveNames = names(options.name);
const project = readProjectConfiguration(
tree,
schema.project
) as ProjectConfiguration & { prefix?: string };
const path = schema.path ?? `${project.sourceRoot}`;
const directiveNames = names(schema.name);
const selector =
schema.selector ??
buildSelector(tree, schema.name, schema.prefix ?? project.prefix);
const pathToGenerateFiles = schema.flat
const pathToGenerateFiles = options.flat
? './files/__directiveFileName__'
: './files';
await generateFiles(
generateFiles(
tree,
joinPathFragments(__dirname, pathToGenerateFiles),
path,
options.path,
{
selector,
selector: options.selector,
directiveClassName: directiveNames.className,
directiveFileName: directiveNames.fileName,
standalone: schema.standalone,
standalone: options.standalone,
tpl: '',
}
);
if (schema.skipTests) {
if (options.skipTests) {
const pathToSpecFile = joinPathFragments(
path,
`${!schema.flat ? `${directiveNames.fileName}/` : ``}${
options.path,
`${!options.flat ? `${directiveNames.fileName}/` : ``}${
directiveNames.fileName
}.directive.spec.ts`
);
@@ -60,31 +42,24 @@ export async function directiveGenerator(tree: Tree, schema: Schema) {
tree.delete(pathToSpecFile);
}
if (!schema.skipImport && !schema.standalone) {
const modulePath = findModule(tree, path, schema.module);
if (!options.skipImport && !options.standalone) {
const modulePath = findModule(tree, options.path, options.module);
addToNgModule(
tree,
path,
options.path,
modulePath,
directiveNames.fileName,
`${directiveNames.className}Directive`,
`${directiveNames.fileName}.directive`,
'declarations',
schema.flat,
schema.export
options.flat,
options.export
);
}
if (!schema.skipFormat) {
if (!options.skipFormat) {
await formatFiles(tree);
}
}
function buildSelector(tree: Tree, name: string, prefix: string) {
let selector = names(name).fileName;
const selectorPrefix = names(prefix ?? readNxJson(tree).npmScope).fileName;
return names(`${selectorPrefix}-${selector}`).propertyName;
}
export default directiveGenerator;
@@ -0,0 +1,2 @@
export * from './normalize-options';
export * from './validate-options';
@@ -0,0 +1,54 @@
import type { ProjectConfiguration, Tree } from '@nrwl/devkit';
import {
joinPathFragments,
names,
readNxJson,
readProjectConfiguration,
} from '@nrwl/devkit';
import { parseName } from '../../utils/names';
import type { Schema } from '../schema';
export function normalizeOptions(tree: Tree, options: Schema) {
const { prefix, projectType, root, sourceRoot } = readProjectConfiguration(
tree,
options.project
) as ProjectConfiguration & { prefix?: string };
const projectSourceRoot = sourceRoot ?? joinPathFragments(root, 'src');
const { name, path: namePath } = parseName(options.name);
const path =
options.path ??
joinPathFragments(
projectSourceRoot,
projectType === 'application' ? 'app' : 'lib',
namePath
);
const selector =
options.selector ?? buildSelector(tree, name, options.prefix, prefix);
return {
...options,
name,
path,
projectRoot: root,
projectSourceRoot,
selector,
};
}
function buildSelector(
tree: Tree,
name: string,
prefix: string,
projectPrefix: string
): string {
let selector = name;
prefix ??= projectPrefix ?? readNxJson(tree).npmScope;
if (prefix) {
selector = `${prefix}-${selector}`;
}
return names(selector).propertyName;
}
@@ -0,0 +1,21 @@
import type { Tree } from '@nrwl/devkit';
import { getProjects, stripIndents } from '@nrwl/devkit';
import { lt } from 'semver';
import { checkPathUnderProjectRoot } from '../../utils/path';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { Schema } from '../schema';
export function validateOptions(tree: Tree, options: Schema): void {
const projects = getProjects(tree);
if (!projects.has(options.project)) {
throw new Error(`Project "${options.project}" does not exist!`);
}
checkPathUnderProjectRoot(tree, options.project, options.path);
const installedAngularVersionInfo = getInstalledAngularVersionInfo(tree);
if (lt(installedAngularVersionInfo.version, '14.1.0') && options.standalone) {
throw new Error(stripIndents`The "standalone" option is only supported in Angular >= 14.1.0. You are currently using "${installedAngularVersionInfo.version}".
You can resolve this error by removing the "standalone" option or by migrating to Angular 14.1.0.`);
}
}
@@ -63,7 +63,7 @@
"description": "The HTML selector to use for this directive."
},
"standalone": {
"description": "Whether the generated directive is standalone.",
"description": "Whether the generated directive is standalone. _Note: This is only supported in Angular versions >= 14.1.0_.",
"type": "boolean",
"default": false
},
+3 -1
View File
@@ -1,4 +1,5 @@
import {
extractLayoutDirectory,
formatFiles,
getProjects,
runTasksInSerial,
@@ -39,7 +40,8 @@ export async function host(tree: Tree, options: Schema) {
});
}
const appName = normalizeProjectName(options.name, options.directory);
const { projectDirectory } = extractLayoutDirectory(options.directory);
const appName = normalizeProjectName(options.name, projectDirectory);
const appInstallTask = await applicationGenerator(tree, {
...options,
@@ -106,7 +106,7 @@
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"enum": ["karma", "jest", "none"],
"description": "Test runner to use for unit tests.",
"default": "jest"
},
@@ -118,6 +118,62 @@ describe('init', () => {
});
describe('--unit-test-runner', () => {
describe('karma', () => {
it('should add karma dependencies', async () => {
// ACT
await init(tree, {
unitTestRunner: UnitTestRunner.Karma,
linter: Linter.EsLint,
skipFormat: false,
});
const { devDependencies } = readJson(tree, 'package.json');
// ASSERT
expect(devDependencies['karma']).toBeDefined();
expect(devDependencies['karma-chrome-launcher']).toBeDefined();
expect(devDependencies['karma-coverage']).toBeDefined();
expect(devDependencies['karma-jasmine']).toBeDefined();
expect(devDependencies['karma-jasmine-html-reporter']).toBeDefined();
expect(devDependencies['jasmine-core']).toBeDefined();
expect(devDependencies['jasmine-spec-reporter']).toBeDefined();
expect(devDependencies['@types/jasmine']).toBeDefined();
});
it('should add karma configuration', async () => {
// ACT
await init(tree, {
unitTestRunner: UnitTestRunner.Karma,
linter: Linter.EsLint,
skipFormat: false,
});
const hasKarmaConfigFile = tree.exists('karma.conf.js');
// ASSERT
expect(hasKarmaConfigFile).toBeTruthy();
});
it('should set defaults', async () => {
// ACT
await init(tree, {
unitTestRunner: UnitTestRunner.Karma,
linter: Linter.EsLint,
skipFormat: false,
});
const { generators } = readJson<NxJsonConfiguration>(tree, 'nx.json');
// ASSERT
expect(generators['@nrwl/angular:application'].unitTestRunner).toEqual(
'karma'
);
expect(generators['@nrwl/angular:library'].unitTestRunner).toEqual(
'karma'
);
});
});
describe('jest', () => {
it('should add jest dependencies', async () => {
// ACT
@@ -470,6 +526,79 @@ bar
});
describe('--unit-test-runner', () => {
describe('karma', () => {
it('should add karma dependencies', async () => {
// ACT
await init(tree, {
unitTestRunner: UnitTestRunner.Karma,
linter: Linter.EsLint,
skipFormat: false,
});
const { devDependencies } = readJson(tree, 'package.json');
// ASSERT
expect(devDependencies['karma']).toEqual(
backwardCompatibleVersions.angularV14.karmaVersion
);
expect(devDependencies['karma-chrome-launcher']).toEqual(
backwardCompatibleVersions.angularV14.karmaChromeLauncherVersion
);
expect(devDependencies['karma-coverage']).toEqual(
backwardCompatibleVersions.angularV14.karmaCoverageVersion
);
expect(devDependencies['karma-jasmine']).toEqual(
backwardCompatibleVersions.angularV14.karmaJasmineVersion
);
expect(devDependencies['karma-jasmine-html-reporter']).toEqual(
backwardCompatibleVersions.angularV14
.karmaJasmineHtmlReporterVersion
);
expect(devDependencies['jasmine-core']).toEqual(
backwardCompatibleVersions.angularV14.jasmineCoreVersion
);
expect(devDependencies['jasmine-spec-reporter']).toEqual(
backwardCompatibleVersions.angularV14.jasmineSpecReporterVersion
);
expect(devDependencies['@types/jasmine']).toEqual(
backwardCompatibleVersions.angularV14.typesJasmineVersion
);
});
it('should add karma configuration', async () => {
// ACT
await init(tree, {
unitTestRunner: UnitTestRunner.Karma,
linter: Linter.EsLint,
skipFormat: false,
});
const hasKarmaConfigFile = tree.exists('karma.conf.js');
// ASSERT
expect(hasKarmaConfigFile).toBeTruthy();
});
it('should set defaults', async () => {
// ACT
await init(tree, {
unitTestRunner: UnitTestRunner.Karma,
linter: Linter.EsLint,
skipFormat: false,
});
const { generators } = readJson<NxJsonConfiguration>(tree, 'nx.json');
// ASSERT
expect(
generators['@nrwl/angular:application'].unitTestRunner
).toEqual('karma');
expect(generators['@nrwl/angular:library'].unitTestRunner).toEqual(
'karma'
);
});
});
describe('jest', () => {
it('should add jest dependencies', async () => {
// ACT
@@ -14,6 +14,7 @@ import { jestInitGenerator } from '@nrwl/jest';
import { Linter } from '@nrwl/linter';
import { initGenerator as jsInitGenerator } from '@nrwl/js';
import { E2eTestRunner, UnitTestRunner } from '../../utils/test-runners';
import { karmaGenerator } from '../karma/karma';
import {
addDependenciesToPackageJsonIfDontExist,
getInstalledPackageVersion,
@@ -171,6 +172,10 @@ async function addUnitTestRunner(
jestPresetAngularVersion: string
): Promise<GeneratorCallback> {
switch (options.unitTestRunner) {
case UnitTestRunner.Karma:
return await karmaGenerator(tree, {
skipPackageJson: options.skipPackageJson,
});
case UnitTestRunner.Jest:
if (!options.skipPackageJson) {
addDependenciesToPackageJsonIfDontExist(
@@ -14,7 +14,7 @@
"properties": {
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"enum": ["karma", "jest", "none"],
"description": "Test runner to use for unit tests.",
"default": "jest",
"x-priority": "important"
@@ -0,0 +1,4 @@
import { convertNxGenerator } from '@nrwl/devkit';
import { karmaProjectGenerator } from './karma-project';
export default convertNxGenerator(karmaProjectGenerator);
@@ -0,0 +1,16 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/6.4/config/configuration-file.html
const { join } = require('path');
const getBaseKarmaConfig = require('<%= offsetFromRoot %>karma.conf');
module.exports = function(config) {
const baseConfig = getBaseKarmaConfig();
config.set({
...baseConfig,
coverageReporter: {
...baseConfig.coverageReporter,
dir: join(__dirname, '<%= offsetFromRoot %>coverage/<%= projectRoot %>')
}
});
};
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "<%= offsetFromRoot %>dist/out-tsc",
"types": ["jasmine", "node"]
},
"include": ["src/**/*.spec.ts", "src/**/*.test.ts", "src/**/*.d.ts"]
}
@@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/6.4/config/configuration-file.html
const { constants } = require('karma');
const { join } = require('path');
module.exports = (config) => {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter: {
dir: join(__dirname, 'coverage/<%= projectName %>'),
subdir: '.',
reporters: [{ type: 'html' }, { type: 'text-summary' }],
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: constants.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: true,
restartOnFileChange: true,
});
};
@@ -0,0 +1,28 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/6.4/config/configuration-file.html
const { join } = require('path');<% if (rootProjectWithConfigSet) { %>
const setBaseKarmaConfig = require('<%= offsetFromRoot %>karma.conf');
module.exports = function (config) {
setBaseKarmaConfig(config);
config.set({
coverageReporter: {
dir: join(__dirname, '<%= offsetFromRoot %>coverage/<%= projectRoot %>')
}
});
};<% } else { %>
const getBaseKarmaConfig = require('<%= offsetFromRoot %>karma.conf');
module.exports = function (config) {
const baseConfig = getBaseKarmaConfig(config);
config.set({
...baseConfig,
coverageReporter: {
...baseConfig.coverageReporter,
dir: join(__dirname, '<%= offsetFromRoot %>coverage/<%= projectRoot %>')
}
});
};<% } %>
@@ -0,0 +1,27 @@
import type { GeneratorCallback, Tree } from '@nrwl/devkit';
import { formatFiles } from '@nrwl/devkit';
import { checkProjectTestTarget } from './lib/check-test-target';
import { generateKarmaProjectFiles } from './lib/generate-karma-project-files';
import { updateTsConfigs } from './lib/update-tsconfig';
import { updateWorkspaceConfig } from './lib/update-workspace-config';
import type { KarmaProjectOptions } from './schema';
import { karmaGenerator } from '../karma/karma';
export async function karmaProjectGenerator(
tree: Tree,
options: KarmaProjectOptions
): Promise<GeneratorCallback> {
const installTask = await karmaGenerator(tree, options);
checkProjectTestTarget(tree, options.project);
generateKarmaProjectFiles(tree, options.project);
updateTsConfigs(tree, options.project);
updateWorkspaceConfig(tree, options.project);
if (!options.skipFormat) {
await formatFiles(tree);
}
return installTask;
}
export default karmaProjectGenerator;
@@ -0,0 +1,9 @@
import type { Tree } from '@nrwl/devkit';
import { readProjectConfiguration } from '@nrwl/devkit';
export function checkProjectTestTarget(tree: Tree, project: string): void {
const projectConfig = readProjectConfiguration(tree, project);
if (projectConfig.targets.test) {
throw new Error(`"${project}" already has a test target.`);
}
}
@@ -0,0 +1,89 @@
import type { Tree } from '@nrwl/devkit';
import {
generateFiles,
getProjects,
joinPathFragments,
offsetFromRoot,
readProjectConfiguration,
} from '@nrwl/devkit';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import { v14TestFile } from './v14-test-file';
import { ensureTypescript } from '@nrwl/js/src/utils/typescript/ensure-typescript';
export function generateKarmaProjectFiles(tree: Tree, project: string): void {
const projectConfig = readProjectConfiguration(tree, project);
generateFiles(
tree,
joinPathFragments(__dirname, '..', 'files', 'common'),
projectConfig.root,
{
tmpl: '',
projectRoot: projectConfig.root,
offsetFromRoot: offsetFromRoot(projectConfig.root),
}
);
if (projectConfig.root === '' || projectConfig.root === '.') {
generateFiles(
tree,
joinPathFragments(__dirname, '..', 'files', 'root-project'),
projectConfig.root,
{
tmpl: '',
projectName: project,
}
);
} else if (isWorkspaceWithProjectAtRoot(tree)) {
generateFiles(
tree,
joinPathFragments(
__dirname,
'..',
'files',
'workspace-with-root-project'
),
projectConfig.root,
{
tmpl: '',
projectRoot: projectConfig.root,
offsetFromRoot: offsetFromRoot(projectConfig.root),
rootProjectWithConfigSet: isUsingConfigSetInBaseKarmaConfig(tree),
}
);
}
const installedAngularVersion = getInstalledAngularVersionInfo(tree);
if (installedAngularVersion.major === 14) {
tree.write(
joinPathFragments(projectConfig.sourceRoot, 'test.ts'),
v14TestFile({ isLibrary: projectConfig.projectType === 'library' })
);
}
}
function isWorkspaceWithProjectAtRoot(tree: Tree): boolean {
const projects = getProjects(tree);
for (const [, project] of projects) {
if (project.root === '.' || project.root === '') {
return true;
}
}
return false;
}
function isUsingConfigSetInBaseKarmaConfig(tree: Tree) {
if (!tree.exists('karma.conf.js')) {
return false;
}
ensureTypescript();
const { tsquery } = require('@phenomnomnominal/tsquery');
const CONFIG_SET_SELECTOR =
'PropertyAccessExpression:has(Identifier[name=config], Identifier[name=set])';
const ast = tsquery.ast(tree.read('karma.conf.js', 'utf-8'));
const nodes = tsquery(ast, CONFIG_SET_SELECTOR, { visitAllChildren: true });
return nodes.length > 0;
}
@@ -0,0 +1,57 @@
import type { Tree } from '@nrwl/devkit';
import {
joinPathFragments,
readProjectConfiguration,
updateJson,
} from '@nrwl/devkit';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
export function updateTsConfigs(tree: Tree, project: string): void {
const projectConfig = readProjectConfiguration(tree, project);
updateJson(
tree,
joinPathFragments(projectConfig.root, 'tsconfig.json'),
(json) => {
return {
...json,
references: [
...(json.references || []),
{
path: './tsconfig.spec.json',
},
],
};
}
);
const installedAngularVersion = getInstalledAngularVersionInfo(tree);
let extraFiles: string[] =
installedAngularVersion.major === 14 ? ['src/test.ts'] : [];
if (
projectConfig.projectType == 'application' &&
projectConfig.targets.build?.options?.polyfills &&
typeof projectConfig.targets.build.options.polyfills === 'string'
) {
let polyfillsPath = projectConfig.targets.build.options.polyfills;
polyfillsPath = polyfillsPath.startsWith(projectConfig.root)
? polyfillsPath.replace(`${projectConfig.root}/`, '')
: polyfillsPath;
extraFiles = [...extraFiles, polyfillsPath];
}
if (!extraFiles.length) {
return;
}
updateJson(
tree,
joinPathFragments(projectConfig.root, 'tsconfig.spec.json'),
(json) => {
return {
...json,
files: [...(json.files ?? []), ...extraFiles],
};
}
);
}
@@ -0,0 +1,50 @@
import type { Tree } from '@nrwl/devkit';
import {
joinPathFragments,
readProjectConfiguration,
updateProjectConfiguration,
} from '@nrwl/devkit';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
export function updateWorkspaceConfig(tree: Tree, project: string): void {
const installedAngularVersionInfo = getInstalledAngularVersionInfo(tree);
const projectConfig = readProjectConfiguration(tree, project);
projectConfig.targets.test = {
executor: '@angular-devkit/build-angular:karma',
options: {
...(installedAngularVersionInfo.major === 14
? { main: joinPathFragments(projectConfig.sourceRoot, 'test.ts') }
: {}),
tsConfig: joinPathFragments(projectConfig.root, 'tsconfig.spec.json'),
karmaConfig: joinPathFragments(projectConfig.root, 'karma.conf.js'),
polyfills: ['zone.js', 'zone.js/testing'],
},
};
if (projectConfig.projectType === 'application') {
const polyfills = projectConfig.targets.build?.options?.polyfills;
let polyfillsPath =
polyfills && typeof polyfills === 'string' ? polyfills : undefined;
projectConfig.targets.test.options = {
...projectConfig.targets.test.options,
polyfills: polyfillsPath ?? projectConfig.targets.test.options.polyfills,
styles: [],
scripts: [],
assets: [],
};
}
if (
projectConfig.targets.lint &&
projectConfig.targets.lint.executor ===
'@angular-devkit/build-angular:tslint'
) {
projectConfig.targets.lint.options.tsConfig = [
...projectConfig.targets.lint.options.tsConfig,
joinPathFragments(projectConfig.root, 'tsconfig.spec.json'),
];
}
updateProjectConfiguration(tree, project, projectConfig);
}
@@ -0,0 +1,28 @@
export const v14TestFile = (opts: {
isLibrary: boolean;
}) => `// This file is required by karma.conf.js and loads recursively all the .spec and framework files
${
opts.isLibrary
? `
import 'zone.js/dist/zone';
`
: ``
}
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);`;
@@ -0,0 +1,5 @@
export interface KarmaProjectOptions {
project: string;
skipFormat?: boolean;
skipPackageJson?: boolean;
}
@@ -0,0 +1,31 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "NxKarmaProjectGenerator",
"title": "Create Karma Configuration for a project",
"description": "Add Karma configuration to an angular project.",
"cli": "nx",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The name of the project.",
"x-dropdown": "projects",
"x-priority": "important"
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
"default": false,
"x-priority": "internal"
},
"skipPackageJson": {
"description": "Skip updating package.json.",
"type": "boolean",
"default": false,
"x-priority": "internal"
}
},
"additionalProperties": false,
"required": ["project"],
"examplesFile": "../../../docs/karma-project-examples.md"
}
@@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
const { join } = require('path');
const { constants } = require('karma');
module.exports = () => {
return {
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter: {
dir: join(__dirname, './coverage'),
subdir: '.',
reporters: [{ type: 'html' }, { type: 'text-summary' }],
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: constants.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: true,
restartOnFileChange: true
};
};
@@ -0,0 +1,80 @@
import type { Tree } from '@nrwl/devkit';
import {
generateFiles,
joinPathFragments,
readJson,
readNxJson,
updateNxJson,
} from '@nrwl/devkit';
import { backwardCompatibleVersions } from '../../../utils/backward-compatible-versions';
import { addDependenciesToPackageJsonIfDontExist } from '../../utils/version-utils';
import { GeneratorOptions } from './schema';
function addTestInputs(tree: Tree) {
const nxJson = readNxJson(tree);
const productionFileSet = nxJson.namedInputs?.production;
if (productionFileSet) {
productionFileSet.push(
// Exclude spec files from production fileset
'!{projectRoot}/**/*.spec.[jt]s',
// Remove tsconfig.spec.json
'!{projectRoot}/tsconfig.spec.json',
// Remove karma.conf.js
'!{projectRoot}/karma.conf.js'
);
// Dedupe and set
nxJson.namedInputs.production = Array.from(new Set(productionFileSet));
}
// Test targets depend on all their project's sources + production sources of dependencies
nxJson.targetDefaults ??= {};
nxJson.targetDefaults.test ??= {};
nxJson.targetDefaults.test.inputs ??= [
'default',
productionFileSet ? '^production' : '^default',
];
nxJson.targetDefaults.test.inputs.push('{workspaceRoot}/karma.conf.js');
updateNxJson(tree, nxJson);
}
export function karmaGenerator(tree: Tree, options: GeneratorOptions) {
const packageJson = readJson(tree, 'package.json');
if (!tree.exists('karma.conf.js')) {
generateFiles(tree, joinPathFragments(__dirname, 'files'), '.', {
tmpl: '',
});
addTestInputs(tree);
}
if (options.skipPackageJson || packageJson.devDependencies['karma']) {
return () => {};
}
return addDependenciesToPackageJsonIfDontExist(
tree,
{},
{
karma: backwardCompatibleVersions.angularV14.karmaVersion,
'karma-chrome-launcher':
backwardCompatibleVersions.angularV14.karmaChromeLauncherVersion,
'karma-coverage':
backwardCompatibleVersions.angularV14.karmaCoverageVersion,
'karma-jasmine':
backwardCompatibleVersions.angularV14.karmaJasmineVersion,
'karma-jasmine-html-reporter':
backwardCompatibleVersions.angularV14.karmaJasmineHtmlReporterVersion,
'jasmine-core': backwardCompatibleVersions.angularV14.jasmineCoreVersion,
'jasmine-spec-reporter':
backwardCompatibleVersions.angularV14.jasmineSpecReporterVersion,
'@types/jasmine':
backwardCompatibleVersions.angularV14.typesJasmineVersion,
'@types/node': backwardCompatibleVersions.angularV14.typesNodeVersion,
}
);
}
export default karmaGenerator;
@@ -0,0 +1,3 @@
export interface GeneratorOptions {
skipPackageJson?: boolean;
}
@@ -0,0 +1,4 @@
import { convertNxGenerator } from '@nrwl/devkit';
import { karmaGenerator } from './karma';
export default convertNxGenerator(karmaGenerator);
@@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
const { join } = require('path');
const { constants } = require('karma');
module.exports = () => {
return {
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter: {
dir: join(__dirname, './coverage'),
subdir: '.',
reporters: [{ type: 'html' }, { type: 'text-summary' }],
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: constants.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: true,
restartOnFileChange: true
};
};
@@ -0,0 +1,84 @@
import * as devkit from '@nrwl/devkit';
import { NxJsonConfiguration, readJson, updateJson } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { karmaGenerator } from './karma';
describe('karma', () => {
let tree: devkit.Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});
it('should do nothing when karma is already installed and karma.conf.js exists', async () => {
jest.spyOn(devkit, 'generateFiles');
jest.spyOn(devkit, 'addDependenciesToPackageJson');
devkit.updateJson(tree, 'package.json', (json) => {
json.devDependencies = { karma: '~5.0.0' };
return json;
});
tree.write('karma.conf.js', '');
await karmaGenerator(tree, {});
expect(devkit.generateFiles).not.toHaveBeenCalled();
expect(devkit.addDependenciesToPackageJson).not.toHaveBeenCalled();
});
it('should create karma.conf.js when karma is installed', async () => {
jest.spyOn(devkit, 'generateFiles');
jest.spyOn(devkit, 'addDependenciesToPackageJson');
devkit.updateJson(tree, 'package.json', (json) => {
json.devDependencies = { karma: '~5.0.0' };
return json;
});
await karmaGenerator(tree, {});
expect(devkit.generateFiles).toHaveBeenCalled();
expect(devkit.addDependenciesToPackageJson).not.toHaveBeenCalled();
});
it('should add karma dependencies', async () => {
karmaGenerator(tree, {});
const { devDependencies } = devkit.readJson(tree, 'package.json');
expect(devDependencies['karma']).toBeDefined();
expect(devDependencies['karma-chrome-launcher']).toBeDefined();
expect(devDependencies['karma-coverage']).toBeDefined();
expect(devDependencies['karma-jasmine']).toBeDefined();
expect(devDependencies['karma-jasmine-html-reporter']).toBeDefined();
expect(devDependencies['jasmine-core']).toBeDefined();
expect(devDependencies['jasmine-spec-reporter']).toBeDefined();
expect(devDependencies['@types/jasmine']).toBeDefined();
expect(devDependencies['@types/node']).toBeDefined();
});
it('should add karma configuration', async () => {
await karmaGenerator(tree, {});
expect(tree.exists('karma.conf.js')).toBeTruthy();
});
it('should add inputs for test targets', async () => {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.namedInputs ??= {};
json.namedInputs.production = ['default', '^production'];
return json;
});
await karmaGenerator(tree, {});
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/karma.conf.js'
);
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/tsconfig.spec.json'
);
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/**/*.spec.[jt]s'
);
expect(nxJson.targetDefaults.test).toEqual({
inputs: ['default', '^production', '{workspaceRoot}/karma.conf.js'],
});
});
});
@@ -0,0 +1,97 @@
import type { Tree } from '@nrwl/devkit';
import {
generateFiles,
joinPathFragments,
readJson,
readNxJson,
updateNxJson,
} from '@nrwl/devkit';
import { join } from 'path';
import {
jasmineCoreVersion,
jasmineSpecReporterVersion,
karmaChromeLauncherVersion,
karmaCoverageVersion,
karmaJasmineHtmlReporterVersion,
karmaJasmineVersion,
karmaVersion,
typesJasmineVersion,
typesNodeVersion,
} from '../../utils/versions';
import {
addDependenciesToPackageJsonIfDontExist,
getGeneratorDirectoryForInstalledAngularVersion,
} from '../utils/version-utils';
import { GeneratorOptions } from './schema';
function addTestInputs(tree: Tree) {
const nxJson = readNxJson(tree);
const productionFileSet = nxJson.namedInputs?.production;
if (productionFileSet) {
productionFileSet.push(
// Exclude spec files from production fileset
'!{projectRoot}/**/*.spec.[jt]s',
// Remove tsconfig.spec.json
'!{projectRoot}/tsconfig.spec.json',
// Remove karma.conf.js
'!{projectRoot}/karma.conf.js'
);
// Dedupe and set
nxJson.namedInputs.production = Array.from(new Set(productionFileSet));
}
// Test targets depend on all their project's sources + production sources of dependencies
nxJson.targetDefaults ??= {};
nxJson.targetDefaults.test ??= {};
nxJson.targetDefaults.test.inputs ??= [
'default',
productionFileSet ? '^production' : '^default',
];
nxJson.targetDefaults.test.inputs.push('{workspaceRoot}/karma.conf.js');
updateNxJson(tree, nxJson);
}
export async function karmaGenerator(tree: Tree, options: GeneratorOptions) {
const generatorDirectory =
getGeneratorDirectoryForInstalledAngularVersion(tree);
if (generatorDirectory) {
let previousGenerator = await import(
join(__dirname, generatorDirectory, 'karma')
);
await previousGenerator.default(tree, options);
return;
}
const packageJson = readJson(tree, 'package.json');
if (!tree.exists('karma.conf.js')) {
generateFiles(tree, joinPathFragments(__dirname, 'files'), '.', {
tmpl: '',
});
addTestInputs(tree);
}
if (options.skipPackageJson || packageJson.devDependencies['karma']) {
return () => {};
}
return addDependenciesToPackageJsonIfDontExist(
tree,
{},
{
karma: karmaVersion,
'karma-chrome-launcher': karmaChromeLauncherVersion,
'karma-coverage': karmaCoverageVersion,
'karma-jasmine': karmaJasmineVersion,
'karma-jasmine-html-reporter': karmaJasmineHtmlReporterVersion,
'jasmine-core': jasmineCoreVersion,
'jasmine-spec-reporter': jasmineSpecReporterVersion,
'@types/jasmine': typesJasmineVersion,
'@types/node': typesNodeVersion,
}
);
}
export default karmaGenerator;
@@ -0,0 +1,97 @@
import * as devkit from '@nrwl/devkit';
import { NxJsonConfiguration, readJson, updateJson } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { karmaGenerator } from './karma';
describe('karma', () => {
let tree: devkit.Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
updateJson(tree, 'package.json', (json) => ({
...json,
dependencies: {
...json.dependencies,
'@angular/core': '14.1.0',
},
}));
});
describe('support angular v14', () => {
it('should do nothing when karma is already installed and karma.conf.js exists', async () => {
jest.spyOn(devkit, 'generateFiles');
jest.spyOn(devkit, 'addDependenciesToPackageJson');
devkit.updateJson(tree, 'package.json', (json) => {
json.devDependencies = { karma: '~5.0.0' };
return json;
});
tree.write('karma.conf.js', '');
await karmaGenerator(tree, {});
expect(devkit.generateFiles).not.toHaveBeenCalled();
expect(devkit.addDependenciesToPackageJson).not.toHaveBeenCalled();
});
it('should create karma.conf.js when karma is installed', async () => {
jest.spyOn(devkit, 'generateFiles');
jest.spyOn(devkit, 'addDependenciesToPackageJson');
devkit.updateJson(tree, 'package.json', (json) => {
json.devDependencies = { karma: '~5.0.0' };
return json;
});
await karmaGenerator(tree, {});
expect(devkit.generateFiles).toHaveBeenCalled();
expect(devkit.addDependenciesToPackageJson).not.toHaveBeenCalled();
});
it('should add karma dependencies', async () => {
await karmaGenerator(tree, {});
const { dependencies, devDependencies } = devkit.readJson(
tree,
'package.json'
);
expect(devDependencies['karma']).toBeDefined();
expect(devDependencies['karma-chrome-launcher']).toBeDefined();
expect(devDependencies['karma-coverage']).toBeDefined();
expect(devDependencies['karma-jasmine']).toBeDefined();
expect(devDependencies['karma-jasmine-html-reporter']).toBeDefined();
expect(devDependencies['jasmine-core']).toBeDefined();
expect(devDependencies['jasmine-spec-reporter']).toBeDefined();
expect(devDependencies['@types/jasmine']).toBeDefined();
expect(devDependencies['@types/node']).toBeDefined();
expect(dependencies['@angular/core']).toEqual('14.1.0');
});
it('should add karma configuration', async () => {
await karmaGenerator(tree, {});
expect(tree.exists('karma.conf.js')).toBeTruthy();
});
it('should add inputs for test targets', async () => {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.namedInputs ??= {};
json.namedInputs.production = ['default', '^production'];
return json;
});
await karmaGenerator(tree, {});
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/karma.conf.js'
);
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/tsconfig.spec.json'
);
expect(nxJson.namedInputs.production).toContain(
'!{projectRoot}/**/*.spec.[jt]s'
);
expect(nxJson.targetDefaults.test).toEqual({
inputs: ['default', '^production', '{workspaceRoot}/karma.conf.js'],
});
});
});
});
+3
View File
@@ -0,0 +1,3 @@
export interface GeneratorOptions {
skipPackageJson?: boolean;
}
@@ -0,0 +1,24 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "NxAngularKarmaGenerator",
"title": "Add Karma Configuration to the workspace.",
"description": "Add Karma configuration to an Nx workspace.",
"cli": "nx",
"type": "object",
"examples": [
{
"command": "nx g @nrwl/angular:karma",
"description": "Add a base karma configuration to the workspace."
}
],
"properties": {
"skipPackageJson": {
"type": "boolean",
"default": false,
"description": "Do not add dependencies to `package.json`.",
"x-priority": "internal"
}
},
"additionalProperties": false,
"required": []
}
@@ -8,7 +8,7 @@
"types": []
},
"exclude": [
"src/**/*.spec.ts",<% if(unitTesting) { %>
"src/**/*.spec.ts",<% if(unitTesting === 'jest') { %>
"src/test-setup.ts",<% } %>
"jest.config.ts",
"src/**/*.test.ts"
@@ -7,7 +7,6 @@ import {
offsetFromRoot,
} from '@nrwl/devkit';
import { NormalizedSchema } from './normalized-schema';
import { UnitTestRunner } from '../../../utils/test-runners';
import { getRootTsConfigFileName } from '@nrwl/js';
export function createFiles(
@@ -30,7 +29,7 @@ export function createFiles(
libFileName: options.libraryOptions.fileName,
libClassName: libNames.className,
libPropertyName: libNames.propertyName,
unitTesting: options.libraryOptions.unitTestRunner !== UnitTestRunner.None,
unitTesting: options.libraryOptions.unitTestRunner,
rootTsConfig: joinPathFragments(rootOffset, getRootTsConfigFileName(tree)),
skipModule: options.libraryOptions.skipModule,
projectRoot: options.libraryOptions.projectRoot,
@@ -355,6 +355,21 @@ describe('lib', () => {
]);
});
it('should leave the excludes alone when unitTestRunner is karma', async () => {
// ACT
await runLibraryGeneratorWithOpts({
unitTestRunner: UnitTestRunner.Karma,
});
// ASSERT
const tsconfigJson = readJson(tree, 'libs/my-lib/tsconfig.lib.json');
expect(tsconfigJson.exclude).toEqual([
'src/**/*.spec.ts',
'jest.config.ts',
'src/**/*.test.ts',
]);
});
it('should remove the excludes when unitTestRunner is none', async () => {
// ACT
await runLibraryGeneratorWithOpts({
@@ -13,6 +13,7 @@ import { lt } from 'semver';
import init from '../../generators/init/init';
import { E2eTestRunner } from '../../utils/test-runners';
import addLintingGenerator from '../add-linting/add-linting';
import karmaProjectGenerator from '../karma-project/karma-project';
import setupTailwindGenerator from '../setup-tailwind/setup-tailwind';
import {
getInstalledAngularVersionInfo,
@@ -131,6 +132,11 @@ async function addUnitTestRunner(
skipFormat: true,
skipPackageJson: options.skipPackageJson,
});
} else if (options.unitTestRunner === 'karma') {
await karmaProjectGenerator(host, {
project: options.name,
skipFormat: true,
});
}
}
@@ -4,6 +4,7 @@
"title": "Create a library",
"description": "Creates an Angular library.",
"type": "object",
"cli": "nx",
"properties": {
"name": {
@@ -95,7 +96,7 @@
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"enum": ["karma", "jest", "none"],
"description": "Test runner to use for unit tests.",
"default": "jest"
},
@@ -1,4 +1,5 @@
import {
extractLayoutDirectory,
formatFiles,
getProjects,
runTasksInSerial,
@@ -30,7 +31,8 @@ export async function remote(tree: Tree, options: Schema) {
);
}
const appName = normalizeProjectName(options.name, options.directory);
const { projectDirectory } = extractLayoutDirectory(options.directory);
const appName = normalizeProjectName(options.name, projectDirectory);
const port = options.port ?? findNextAvailablePort(tree);
const appInstallTask = await applicationGenerator(tree, {
@@ -100,7 +100,7 @@
},
"unitTestRunner": {
"type": "string",
"enum": ["jest", "none"],
"enum": ["karma", "jest", "none"],
"description": "Test runner to use for unit tests.",
"default": "jest"
},
@@ -34,12 +34,20 @@ export function updateHostAppRoutes(tree: Tree, options: Schema) {
`
);
const pathToHostRootRoutingFile = joinPathFragments(
let pathToHostRootRoutingFile = joinPathFragments(
sourceRoot,
'app/app.routes.ts'
);
const hostRootRoutingFile = tree.read(pathToHostRootRoutingFile, 'utf-8');
let hostRootRoutingFile = tree.read(pathToHostRootRoutingFile, 'utf-8');
if (!hostRootRoutingFile) {
pathToHostRootRoutingFile = joinPathFragments(
sourceRoot,
'app/app-routing.module.ts'
);
hostRootRoutingFile = tree.read(pathToHostRootRoutingFile, 'utf-8');
}
let sourceFile = tsModule.createSourceFile(
pathToHostRootRoutingFile,
@@ -0,0 +1,11 @@
import { normalizePath } from '@nrwl/devkit';
export type NameInfo = { name: string; path: string };
export function parseName(rawName: string): NameInfo {
const parsedName = normalizePath(rawName).split('/');
const name = parsedName.pop();
const path = parsedName.join('/');
return { name, path };
}
@@ -19,18 +19,9 @@ describe('Migration to update target and add useDefineForClassFields', () => {
});
await generateTestApplication(tree, {
name: 'karma',
unitTestRunner: UnitTestRunner.None,
unitTestRunner: UnitTestRunner.Karma,
});
const karmaProject = readProjectConfiguration(tree, 'karma');
karmaProject.targets.test = {
executor: '@angular-devkit/build-angular:karma',
options: {
tsConfig: 'apps/karma/tsconfig.spec.json',
},
};
updateProjectConfiguration(tree, 'karma', karmaProject);
// Create tsconfigs
const compilerOptions = { target: 'es2015', module: 'es2020' };
const configWithExtends = {
@@ -20,7 +20,7 @@ describe('installed-required-packages', () => {
'@angular/core': '~15.0.0',
},
devDependencies: {
'@angular/cli': '~15.0.0',
'@angular-devkit/build-angular': '~15.0.0',
},
}));
@@ -36,9 +36,9 @@ describe('installed-required-packages', () => {
`);
expect(pkgJson.devDependencies).toMatchInlineSnapshot(`
Object {
"@angular-devkit/build-angular": "~15.0.0",
"@angular-devkit/core": "~15.0.0",
"@angular-devkit/schematics": "~15.0.0",
"@angular/cli": "~15.0.0",
"@schematics/angular": "~15.0.0",
}
`);
@@ -53,7 +53,7 @@ describe('installed-required-packages', () => {
'@angular/core': '~14.0.0',
},
devDependencies: {
'@angular/cli': '~14.0.0',
'@angular-devkit/build-angular': '~14.0.0',
},
}));
@@ -69,9 +69,9 @@ describe('installed-required-packages', () => {
`);
expect(pkgJson.devDependencies).toMatchInlineSnapshot(`
Object {
"@angular-devkit/build-angular": "~14.0.0",
"@angular-devkit/core": "~14.0.0",
"@angular-devkit/schematics": "~14.0.0",
"@angular/cli": "~14.0.0",
"@schematics/angular": "~14.0.0",
}
`);
@@ -86,7 +86,7 @@ describe('installed-required-packages', () => {
'@angular/core': '~15.0.0',
},
devDependencies: {
'@angular/cli': '~15.0.0',
'@angular-devkit/build-angular': '~15.0.0',
'@angular-devkit/core': '~15.0.0',
'@angular-devkit/schematics': '~15.0.0',
'@schematics/angular': '~15.0.0',
@@ -105,9 +105,9 @@ describe('installed-required-packages', () => {
`);
expect(pkgJson.devDependencies).toMatchInlineSnapshot(`
Object {
"@angular-devkit/build-angular": "~15.0.0",
"@angular-devkit/core": "~15.0.0",
"@angular-devkit/schematics": "~15.0.0",
"@angular/cli": "~15.0.0",
"@schematics/angular": "~15.0.0",
}
`);
@@ -122,7 +122,7 @@ describe('installed-required-packages', () => {
'@angular/core': '~14.0.0',
},
devDependencies: {
'@angular/cli': '~14.0.0',
'@angular-devkit/build-angular': '~14.0.0',
'@angular-devkit/core': '~14.0.0',
'@angular-devkit/schematics': '~14.0.0',
'@schematics/angular': '~14.0.0',
@@ -141,9 +141,9 @@ describe('installed-required-packages', () => {
`);
expect(pkgJson.devDependencies).toMatchInlineSnapshot(`
Object {
"@angular-devkit/build-angular": "~14.0.0",
"@angular-devkit/core": "~14.0.0",
"@angular-devkit/schematics": "~14.0.0",
"@angular/cli": "~14.0.0",
"@schematics/angular": "~14.0.0",
}
`);
@@ -158,7 +158,7 @@ describe('installed-required-packages', () => {
'@angular/core': '~15.0.0',
},
devDependencies: {
'@angular/cli': '~15.0.0',
'@angular-devkit/build-angular': '~15.0.0',
'@angular-devkit/core': '~15.0.0',
'@schematics/angular': '~15.0.0',
},
@@ -176,9 +176,9 @@ describe('installed-required-packages', () => {
`);
expect(pkgJson.devDependencies).toMatchInlineSnapshot(`
Object {
"@angular-devkit/build-angular": "~15.0.0",
"@angular-devkit/core": "~15.0.0",
"@angular-devkit/schematics": "~15.0.0",
"@angular/cli": "~15.0.0",
"@schematics/angular": "~15.0.0",
}
`);
@@ -193,7 +193,7 @@ describe('installed-required-packages', () => {
'@angular/core': '~14.0.0',
},
devDependencies: {
'@angular/cli': '~14.0.0',
'@angular-devkit/build-angular': '~14.0.0',
'@angular-devkit/core': '~14.0.0',
'@schematics/angular': '~14.0.0',
},
@@ -211,9 +211,9 @@ describe('installed-required-packages', () => {
`);
expect(pkgJson.devDependencies).toMatchInlineSnapshot(`
Object {
"@angular-devkit/build-angular": "~14.0.0",
"@angular-devkit/core": "~14.0.0",
"@angular-devkit/schematics": "~14.0.0",
"@angular/cli": "~14.0.0",
"@schematics/angular": "~14.0.0",
}
`);
@@ -25,8 +25,8 @@ export default async function (tree: Tree) {
);
const angularCliVersion =
pkgJson.devDependencies?.['@angular/cli'] ??
pkgJson.dependencies?.['@angular/cli'] ??
pkgJson.devDependencies?.['@angular-devkit/build-angular'] ??
pkgJson.dependencies?.['@angular-devkit/build-angular'] ??
angularDevkitVersion;
const filteredPackages: Record<string, string> = packagesToInstall
@@ -35,6 +35,15 @@ export const backwardCompatibleVersions: Record<
autoprefixerVersion: '^10.4.0',
tsNodeVersion: '10.9.1',
jestPresetAngularVersion: '~12.2.3',
karmaVersion: '~6.4.0',
karmaChromeLauncherVersion: '~3.1.0',
karmaCoverageVersion: '~2.2.0',
karmaJasmineVersion: '~5.1.0',
karmaJasmineHtmlReporterVersion: '~2.0.0',
jasmineCoreVersion: '~4.2.0',
jasmineSpecReporterVersion: '~7.0.0',
typesJasmineVersion: '~4.0.0',
typesJasminewd2Version: '~2.0.3',
typesNodeVersion: '16.11.7',
jasmineMarblesVersion: '^0.9.2',
},
+3 -3
View File
@@ -60,10 +60,10 @@ export async function getModuleFederationConfig(
if (mfConfig.shared) {
dependencies.workspaceLibraries = dependencies.workspaceLibraries.filter(
(lib) => mfConfig.shared(lib.importKey, {})
(lib) => mfConfig.shared(lib.importKey, {}) !== false
);
dependencies.npmPackages = dependencies.npmPackages.filter((pkg) =>
mfConfig.shared(pkg, {})
dependencies.npmPackages = dependencies.npmPackages.filter(
(pkg) => mfConfig.shared(pkg, {}) !== false
);
}
+1 -1
View File
@@ -1,8 +1,8 @@
export enum UnitTestRunner {
Karma = 'karma',
Jest = 'jest',
None = 'none',
}
export enum E2eTestRunner {
Cypress = 'cypress',
None = 'none',
+9
View File
@@ -26,5 +26,14 @@ export const autoprefixerVersion = '^10.4.0';
export const tsNodeVersion = '10.9.1';
export const jestPresetAngularVersion = '~13.0.0';
export const karmaVersion = '~6.4.0';
export const karmaChromeLauncherVersion = '~3.1.0';
export const karmaCoverageVersion = '~2.2.0';
export const karmaJasmineVersion = '~5.1.0';
export const karmaJasmineHtmlReporterVersion = '~2.0.0';
export const jasmineCoreVersion = '~4.2.0';
export const jasmineSpecReporterVersion = '~7.0.0';
export const typesJasmineVersion = '~4.0.0';
export const typesJasminewd2Version = '~2.0.3';
export const typesNodeVersion = '16.11.7';
export const jasmineMarblesVersion = '^0.9.2';
+3 -2
View File
@@ -11,7 +11,7 @@ export function createGlobPatternsForDependencies(
) {
try {
return jsGenerateGlobs(dirPath, fileGlobPattern);
} catch {
} catch (e) {
/**
* It should not be possible to reach this point when the utility is invoked as part of the normal
* lifecycle of Nx executors. However, other tooling, such as the VSCode Tailwind IntelliSense plugin
@@ -22,7 +22,8 @@ export function createGlobPatternsForDependencies(
* fundamently unavailable in this tailwind-specific context.
*/
console.warn(
'\n[createGlobPatternsForDependencies] WARNING: There was no ProjectGraph available to read from, returning an empty array of glob patterns\n'
'\nWARNING: There was an error creating glob patterns, returning an empty array\n' +
`${e.message}\n`
);
return [];
}
@@ -12,6 +12,8 @@ export function spawnAndWait(command: string, args: string[], cwd: string) {
cwd,
stdio: 'inherit',
env: { ...process.env, NX_DAEMON: 'false' },
shell: true,
windowsHide: true,
});
childProcess.on('exit', (code) => {
@@ -33,7 +35,8 @@ export function execAndWait(command: string, cwd: string) {
if (error) {
const logFile = join(cwd, 'error.log');
writeFileSync(logFile, `${stdout}\n${stderr}`);
rej(new CreateNxWorkspaceError(stderr, error.code, logFile));
const message = stderr && stderr.trim().length ? stderr : stdout;
rej(new CreateNxWorkspaceError(message, error.code, logFile));
} else {
res({ code: 0, stdout });
}
@@ -10,7 +10,7 @@ export class CreateNxWorkspaceError extends Error {
}
export function mapErrorToBodyLines(error: Error): string[] {
const errorLines = error.message?.split('\n').filter((line) => !!line);
const errorLines = error.message?.split('\n').filter((line) => !!line.trim());
if (errorLines.length < 3) {
const lines = [`Error: ${error.message}`];
if (process.env.NX_VERBOSE_LOGGING) {
+1 -1
View File
@@ -40,7 +40,7 @@
"@nrwl/workspace": "file:../workspace",
"@phenomnomnominal/tsquery": "4.1.1",
"dotenv": "~10.0.0",
"semver": "7.3.4",
"semver": "7.5.4",
"detect-port": "^1.5.1"
},
"peerDependencies": {
@@ -1,35 +1,57 @@
// Adapted from: https://github.com/mammadataei/cypress-vite
/**
* https://github.com/mammadataei/cypress-vite
*
* MIT License
*
* Copyright (c) 2022 Mohammad Ataei
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**/
import * as path from 'path';
import { dirname, basename, extname } from 'path';
import type { RollupOutput, RollupWatcher, WatcherOptions } from 'rollup';
import type { InlineConfig } from 'vite';
type CypressPreprocessor = (
file: Record<string, any>
) => string | Promise<string>;
type BuildResult = RollupWatcher | RollupOutput | RollupOutput[];
const cache = new Map<string, string>();
/**
* Cypress preprocessor for running e2e tests using vite.
*
* @param {string} userConfigPath
* @example
* setupNodeEvents(on) {
* on(
* 'file:preprocessor',
* vitePreprocessor(path.resolve(__dirname, './vite.config.ts')),
* )
* },
* Use Vite as a file preprocess for Cypress test files.
* This preprocessor shouldn't be used directly.
* Instead, use the nxE2EPreset(__filename, { bundler: 'vite' }) function instead.
*/
function vitePreprocessor(userConfigPath?: string): CypressPreprocessor {
return async (file) => {
const { outputPath, filePath, shouldWatch } = file;
const fileName = path.basename(outputPath);
const filenameWithoutExtension = path.basename(
outputPath,
path.extname(outputPath)
);
if (cache.has(filePath)) {
return cache.get(filePath);
}
const defaultConfig = {
const fileName = basename(outputPath);
const filenameWithoutExtension = basename(outputPath, extname(outputPath));
const defaultConfig: InlineConfig = {
logLevel: 'silent',
define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
@@ -37,7 +59,7 @@ function vitePreprocessor(userConfigPath?: string): CypressPreprocessor {
build: {
emptyOutDir: false,
minify: false,
outDir: path.dirname(outputPath),
outDir: dirname(outputPath),
sourcemap: true,
write: true,
watch: getWatcherConfig(shouldWatch),
@@ -49,41 +71,47 @@ function vitePreprocessor(userConfigPath?: string): CypressPreprocessor {
},
},
};
cache.set(filePath, outputPath);
const { build } = require('vite');
const watcher = await build({
const watcher = (await build({
configFile: userConfigPath,
...defaultConfig,
})) as BuildResult;
return new Promise((resolve, reject) => {
if (shouldWatch && isWatcher(watcher)) {
watcher.on('event', (event) => {
if (event.code === 'END') {
resolve(outputPath);
file.emit('rerun');
}
if (event.code === 'ERROR') {
console.error(event);
reject(new Error(event.error.message));
}
});
file.on('close', () => {
cache.delete(filePath);
watcher.close();
});
} else {
resolve(outputPath);
}
});
if (shouldWatch && isWatcher(watcher)) {
watcher.on('event', (event) => {
if (event.code === 'END') {
file.emit('rerun');
}
if (event.code === 'ERROR') {
console.error(event);
}
});
file.on('close', () => {
watcher.close();
});
}
return outputPath;
};
}
function isWatcher(maybeWatcher: any): maybeWatcher is RollupWatcher {
return maybeWatcher.on !== undefined;
}
function getWatcherConfig(shouldWatch: boolean): WatcherOptions | null {
return shouldWatch ? {} : null;
}
type BuildResult = RollupWatcher | RollupOutput | RollupOutput[];
function isWatcher(watcher: BuildResult): watcher is RollupWatcher {
return (watcher as RollupWatcher).on !== undefined;
}
export default vitePreprocessor;
+1 -1
View File
@@ -39,7 +39,7 @@ export function getTempTailwindPath(context: ExecutorContext) {
}
/**
* Checks if the childProjectName is a decendent of the parentProjectName
* Checks if the childProjectName is a descendent of the parentProjectName
* in the project graph
**/
export function isCtProjectUsingBuildProject(
+1 -1
View File
@@ -32,7 +32,7 @@
"ignore": "^5.0.4",
"tmp": "~0.2.1",
"tslib": "^2.3.0",
"semver": "7.3.4"
"semver": "7.5.4"
},
"peerDependencies": {
"nx": ">= 14.1 <= 16"
+16 -4
View File
@@ -11,8 +11,13 @@ import { requireNx } from '../../nx';
import { dirSync } from 'tmp';
import { join } from 'path';
const { readJson, updateJson, getPackageManagerCommand, workspaceRoot } =
requireNx();
const {
readJson,
updateJson,
getPackageManagerCommand,
workspaceRoot,
detectPackageManager,
} = requireNx();
const UNIDENTIFIED_VERSION = 'UNIDENTIFIED_VERSION';
const NON_SEMVER_TAGS = {
@@ -448,9 +453,16 @@ export function ensurePackage<T extends any = any>(
const tempDir = dirSync().name;
console.log(`Fetching ${pkg}...`);
execSync(`${getPackageManagerCommand().addDev} ${pkg}@${requiredVersion}`, {
const packageManager = detectPackageManager();
let addCommand = getPackageManagerCommand(packageManager).addDev;
if (packageManager === 'pnpm') {
addCommand = 'pnpm add -D'; // we need to ensure that we are not using workspace command
}
const isVerbose = process.env.NX_VERBOSE_LOGGING === 'true';
execSync(`${addCommand} ${pkg}@${requiredVersion}`, {
cwd: tempDir,
stdio: 'ignore',
stdio: isVerbose ? 'inherit' : 'ignore',
});
addToNodePath(join(workspaceRoot, 'node_modules'));
@@ -43,6 +43,7 @@ describe('normalizeOptions', () => {
outputFileName: 'index.js',
singleEntry: true,
external: [],
thirdParty: false,
});
});
@@ -69,6 +70,7 @@ describe('normalizeOptions', () => {
additionalEntryPoints: ['apps/myapp/src/extra-entry.ts'],
singleEntry: false,
external: [],
thirdParty: false,
});
});
@@ -94,6 +96,7 @@ describe('normalizeOptions', () => {
outputFileName: 'test.js',
singleEntry: true,
external: [],
thirdParty: false,
});
});
@@ -108,6 +111,7 @@ describe('normalizeOptions', () => {
generatePackageJson: true,
additionalEntryPoints: ['apps/myapp/src/extra-entry.ts'],
outputFileName: 'test.js',
thirdParty: false,
},
context
)
@@ -135,6 +139,24 @@ describe('normalizeOptions', () => {
outputFileName: 'index.js',
singleEntry: true,
external: [],
thirdParty: false,
});
});
it('should override thirdParty if bundle:false', () => {
expect(
normalizeOptions(
{
main: 'apps/myapp/src/index.ts',
outputPath: 'dist/apps/myapp',
tsConfig: 'apps/myapp/tsconfig.app.json',
generatePackageJson: true,
bundle: false,
thirdParty: true,
assets: [],
},
context
)
).toEqual(expect.objectContaining({ thirdParty: false }));
});
});
@@ -3,7 +3,8 @@ import {
EsBuildExecutorOptions,
NormalizedEsBuildExecutorOptions,
} from '../schema';
import { ExecutorContext, joinPathFragments } from '@nrwl/devkit';
import { ExecutorContext, joinPathFragments, logger } from '@nrwl/devkit';
import chalk = require('chalk');
export function normalizeOptions(
options: EsBuildExecutorOptions,
@@ -20,6 +21,21 @@ export function normalizeOptions(
),
];
if (!options.bundle && options.thirdParty) {
logger.info(
chalk.yellow(
`Your build has conflicting options, ${chalk.bold(
'bundle:false'
)} and ${chalk.bold(
'thirdParty:true'
)}. Your package.json depedencies might not be generated correctly so we added an update ${chalk.bold(
'thirdParty:false'
)}`
)
);
}
const thirdParty = !options.bundle ? false : options.thirdParty;
if (options.additionalEntryPoints?.length > 0) {
const { outputFileName, ...rest } = options;
if (outputFileName) {
@@ -29,6 +45,7 @@ export function normalizeOptions(
}
return {
...rest,
thirdParty,
assets,
external: options.external ?? [],
singleEntry: false,
@@ -40,6 +57,7 @@ export function normalizeOptions(
} else {
return {
...options,
thirdParty,
assets,
external: options.external ?? [],
singleEntry: true,
@@ -128,8 +128,7 @@
},
"thirdParty": {
"type": "boolean",
"description": "Includes third-party packages in the bundle (i.e. npm packages).",
"default": true
"description": "Includes third-party packages in the bundle (i.e. npm packages)."
},
"dependenciesFieldType": {
"type": "string",
+1 -1
View File
@@ -37,7 +37,7 @@
"@typescript-eslint/utils": "^5.36.1",
"chalk": "^4.1.0",
"confusing-browser-globals": "^1.0.9",
"semver": "7.3.4"
"semver": "7.5.4"
},
"publishConfig": {
"access": "public"
@@ -317,7 +317,11 @@ export default createESLintRule<Options, MessageIds>({
if (
!allowCircularSelfDependency &&
!isRelativePath(imp) &&
!isAngularSecondaryEntrypoint(imp, sourceFilePath)
!isAngularSecondaryEntrypoint(
imp,
sourceFilePath,
sourceProject.data.root
)
) {
context.report({
node,
@@ -198,19 +198,33 @@ export function getRelativeImportPath(exportedMember, filePath, basePath) {
const modulePath = (exportDeclaration as any).moduleSpecifier.text;
let moduleFilePath = joinPathFragments(
dirname(filePath),
`${modulePath}.ts`
);
if (!existsSync(moduleFilePath)) {
// might be a tsx file
let moduleFilePath;
if (modulePath.endsWith('.js') || modulePath.endsWith('.jsx')) {
moduleFilePath = joinPathFragments(dirname(filePath), modulePath);
if (!existsSync(moduleFilePath)) {
const tsifiedModulePath = modulePath.replace(/\.js(x?)$/, '.ts$1');
moduleFilePath = joinPathFragments(
dirname(filePath),
`${tsifiedModulePath}`
);
}
} else if (modulePath.endsWith('.ts') || modulePath.endsWith('.tsx')) {
moduleFilePath = joinPathFragments(dirname(filePath), modulePath);
} else {
moduleFilePath = joinPathFragments(
dirname(filePath),
`${modulePath}.tsx`
`${modulePath}.ts`
);
if (!existsSync(moduleFilePath)) {
// might be a tsx file
moduleFilePath = joinPathFragments(
dirname(filePath),
`${modulePath}.tsx`
);
}
}
if (!existsSync(moduleFilePath)) {
// might be a index.ts
// might be an index.ts
moduleFilePath = joinPathFragments(
dirname(filePath),
`${modulePath}/index.ts`
@@ -401,6 +401,7 @@ describe('isAngularSecondaryEntrypoint', () => {
],
'@project/features': ['libs/features/src/index.ts'],
'@project/features/*': ['libs/features/*/random/folder/api.ts'],
'@project/buildable': ['libs/buildable/src/index.ts'],
},
},
};
@@ -421,25 +422,55 @@ describe('isAngularSecondaryEntrypoint', () => {
lib: { entryFile: 'random/folder/api.ts' },
}),
'libs/features/secondary/random/folder/api.ts': 'const bla = "foo"',
'libs/buildable/ng-package.json': JSON.stringify({
lib: { entryFile: 'src/index.ts' },
}),
};
vol.fromJSON(fsJson, '/root');
});
it('should return true for secondary entrypoints', () => {
expect(
isAngularSecondaryEntrypoint('@project/standard', 'apps/app.ts')
isAngularSecondaryEntrypoint(
'@project/standard',
'apps/app.ts',
'libs/standard'
)
).toBe(false);
expect(
isAngularSecondaryEntrypoint('@project/standard/secondary', 'apps/app.ts')
isAngularSecondaryEntrypoint(
'@project/standard/secondary',
'apps/app.ts',
'libs/standard'
)
).toBe(true);
expect(
isAngularSecondaryEntrypoint('@project/standard/tertiary', 'apps/app.ts')
isAngularSecondaryEntrypoint(
'@project/standard/tertiary',
'apps/app.ts',
'libs/standard'
)
).toBe(true);
expect(
isAngularSecondaryEntrypoint('@project/features', 'apps/app.ts')
isAngularSecondaryEntrypoint(
'@project/features',
'apps/app.ts',
'libs/features'
)
).toBe(false);
expect(
isAngularSecondaryEntrypoint('@project/features/secondary', 'apps/app.ts')
isAngularSecondaryEntrypoint(
'@project/features/secondary',
'apps/app.ts',
'libs/features'
)
).toBe(true);
expect(
isAngularSecondaryEntrypoint(
'@project/buildable',
'apps/app.ts',
'libs/buildable'
)
).toBe(false);
});
});
@@ -447,7 +447,8 @@ export function groupImports(
*/
export function isAngularSecondaryEntrypoint(
importExpr: string,
filePath: string
filePath: string,
projectRoot: string
): boolean {
const resolvedModule = resolveModuleByImport(
importExpr,
@@ -455,12 +456,14 @@ export function isAngularSecondaryEntrypoint(
join(workspaceRoot, getRootTsConfigFileName())
);
return !!resolvedModule && fileIsSecondaryEntryPoint(resolvedModule);
return (
!!resolvedModule && fileIsSecondaryEntryPoint(resolvedModule, projectRoot)
);
}
function fileIsSecondaryEntryPoint(file: string): boolean {
function fileIsSecondaryEntryPoint(file: string, projectRoot: string): boolean {
let parent = joinPathFragments(file, '../');
while (parent !== './') {
while (parent !== `${projectRoot}/`) {
// we need to find closest existing ng-package.json
// in order to determine if the file matches the secondary entry point
const ngPackageContent = readFileIfExisting(
+1
View File
@@ -205,6 +205,7 @@ function getPnpmResolver(extensions: string[]) {
modules: [join(workspaceRoot, 'node_modules'), 'node_modules'],
conditionNames: ['native', 'browser', 'require', 'default'],
mainFields: ['react-native', 'browser', 'main'],
aliasFields: ['browser'],
});
}
return resolver;

Some files were not shown because too many files have changed in this diff Show More