feat(angular): remove deprecated functionalities for v21 (#30769)
Remove the deprecated functionalities scheduled to be removed in Nx v21. BREAKING CHANGE: Remove the deprecated data persistence operators previously exported in `@nx/angular` and the deprecated testing utils previously exported in `@nx/angular/testing`.
This commit is contained in:
parent
5c30d1b95a
commit
3eb9f6a822
@ -444,6 +444,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"migrations": {
|
"migrations": {
|
||||||
|
"/nx-api/angular/migrations/change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence": {
|
||||||
|
"description": "Change the data persistence operator imports to '@ngrx/router-store/data-persistence'.",
|
||||||
|
"file": "generated/packages/angular/migrations/change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence",
|
||||||
|
"version": "21.0.0-beta.5",
|
||||||
|
"originalFilePath": "/packages/angular",
|
||||||
|
"path": "/nx-api/angular/migrations/change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
"/nx-api/angular/migrations/set-continuous-option": {
|
"/nx-api/angular/migrations/set-continuous-option": {
|
||||||
"description": "Set the `continuous` option to `true` for continuous tasks.",
|
"description": "Set the `continuous` option to `true` for continuous tasks.",
|
||||||
"file": "generated/packages/angular/migrations/set-continuous-option.json",
|
"file": "generated/packages/angular/migrations/set-continuous-option.json",
|
||||||
@ -1174,56 +1184,6 @@
|
|||||||
"path": "/nx-api/angular/migrations/16.1.3-jest-package-updates",
|
"path": "/nx-api/angular/migrations/16.1.3-jest-package-updates",
|
||||||
"type": "migration"
|
"type": "migration"
|
||||||
},
|
},
|
||||||
"/nx-api/angular/migrations/remove-render-module-platform-server-exports": {
|
|
||||||
"description": "Remove exported `@angular/platform-server` `renderModule` method. The `renderModule` method is now exported by the Angular CLI.",
|
|
||||||
"file": "generated/packages/angular/migrations/remove-render-module-platform-server-exports.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "remove-render-module-platform-server-exports",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "/nx-api/angular/migrations/remove-render-module-platform-server-exports",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
"/nx-api/angular/migrations/remove-ngcc-invocation": {
|
|
||||||
"description": "Remove 'ngcc' invocation if exists from the 'postinstall' script in package.json.",
|
|
||||||
"file": "generated/packages/angular/migrations/remove-ngcc-invocation.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "remove-ngcc-invocation",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "/nx-api/angular/migrations/remove-ngcc-invocation",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
"/nx-api/angular/migrations/extract-app-config-for-standalone": {
|
|
||||||
"description": "Extract the app config for standalone apps",
|
|
||||||
"file": "generated/packages/angular/migrations/extract-app-config-for-standalone.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "extract-app-config-for-standalone",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "/nx-api/angular/migrations/extract-app-config-for-standalone",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
"/nx-api/angular/migrations/update-server-executor-config": {
|
|
||||||
"description": "Update server executors' configuration to disable 'buildOptimizer' for non optimized builds.",
|
|
||||||
"file": "generated/packages/angular/migrations/update-server-executor-config.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "update-server-executor-config",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "/nx-api/angular/migrations/update-server-executor-config",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
"/nx-api/angular/migrations/update-angular-cli-version-16-0-0": {
|
|
||||||
"description": "Update the @angular/cli package version to ~16.0.0.",
|
|
||||||
"file": "generated/packages/angular/migrations/update-angular-cli-version-16-0-0.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "update-angular-cli-version-16-0-0",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "/nx-api/angular/migrations/update-angular-cli-version-16-0-0",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
"/nx-api/angular/migrations/16.1.0-package-updates": {
|
"/nx-api/angular/migrations/16.1.0-package-updates": {
|
||||||
"description": "",
|
"description": "",
|
||||||
"file": "generated/packages/angular/migrations/16.1.0-package-updates.json",
|
"file": "generated/packages/angular/migrations/16.1.0-package-updates.json",
|
||||||
@ -1243,46 +1203,6 @@
|
|||||||
"originalFilePath": "/packages/angular",
|
"originalFilePath": "/packages/angular",
|
||||||
"path": "/nx-api/angular/migrations/16.1.0-angular-eslint-package-updates",
|
"path": "/nx-api/angular/migrations/16.1.0-angular-eslint-package-updates",
|
||||||
"type": "migration"
|
"type": "migration"
|
||||||
},
|
|
||||||
"/nx-api/angular/migrations/remove-protractor-defaults-from-generators": {
|
|
||||||
"description": "Remove protractor as default e2eTestRunner from nxJson and project configurations",
|
|
||||||
"file": "generated/packages/angular/migrations/remove-protractor-defaults-from-generators.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "remove-protractor-defaults-from-generators",
|
|
||||||
"version": "16.0.0-beta.6",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "/nx-api/angular/migrations/remove-protractor-defaults-from-generators",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
"/nx-api/angular/migrations/remove-karma-defaults-from-generators": {
|
|
||||||
"description": "Remove karma as default unitTestRunner from nxJson and project configurations",
|
|
||||||
"file": "generated/packages/angular/migrations/remove-karma-defaults-from-generators.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "remove-karma-defaults-from-generators",
|
|
||||||
"version": "16.0.0-beta.6",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "/nx-api/angular/migrations/remove-karma-defaults-from-generators",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
"/nx-api/angular/migrations/remove-library-generator-simple-module-name-option": {
|
|
||||||
"description": "Replace the deprecated library generator 'simpleModuleName' option from generator defaults with 'simpleName'",
|
|
||||||
"file": "generated/packages/angular/migrations/remove-library-generator-simple-module-name-option.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "remove-library-generator-simple-module-name-option",
|
|
||||||
"version": "16.0.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "/nx-api/angular/migrations/remove-library-generator-simple-module-name-option",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
"/nx-api/angular/migrations/update-16-0-0-add-nx-packages": {
|
|
||||||
"description": "Replace @nrwl/angular with @nx/angular",
|
|
||||||
"file": "generated/packages/angular/migrations/update-16-0-0-add-nx-packages.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "update-16-0-0-add-nx-packages",
|
|
||||||
"version": "16.0.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "/nx-api/angular/migrations/update-16-0-0-add-nx-packages",
|
|
||||||
"type": "migration"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"path": "/nx-api/angular"
|
"path": "/nx-api/angular"
|
||||||
|
|||||||
@ -439,6 +439,16 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"migrations": [
|
"migrations": [
|
||||||
|
{
|
||||||
|
"description": "Change the data persistence operator imports to '@ngrx/router-store/data-persistence'.",
|
||||||
|
"file": "generated/packages/angular/migrations/change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence",
|
||||||
|
"version": "21.0.0-beta.5",
|
||||||
|
"originalFilePath": "/packages/angular",
|
||||||
|
"path": "angular/migrations/change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence",
|
||||||
|
"type": "migration"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Set the `continuous` option to `true` for continuous tasks.",
|
"description": "Set the `continuous` option to `true` for continuous tasks.",
|
||||||
"file": "generated/packages/angular/migrations/set-continuous-option.json",
|
"file": "generated/packages/angular/migrations/set-continuous-option.json",
|
||||||
@ -1169,56 +1179,6 @@
|
|||||||
"path": "angular/migrations/16.1.3-jest-package-updates",
|
"path": "angular/migrations/16.1.3-jest-package-updates",
|
||||||
"type": "migration"
|
"type": "migration"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"description": "Remove exported `@angular/platform-server` `renderModule` method. The `renderModule` method is now exported by the Angular CLI.",
|
|
||||||
"file": "generated/packages/angular/migrations/remove-render-module-platform-server-exports.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "remove-render-module-platform-server-exports",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "angular/migrations/remove-render-module-platform-server-exports",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Remove 'ngcc' invocation if exists from the 'postinstall' script in package.json.",
|
|
||||||
"file": "generated/packages/angular/migrations/remove-ngcc-invocation.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "remove-ngcc-invocation",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "angular/migrations/remove-ngcc-invocation",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Extract the app config for standalone apps",
|
|
||||||
"file": "generated/packages/angular/migrations/extract-app-config-for-standalone.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "extract-app-config-for-standalone",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "angular/migrations/extract-app-config-for-standalone",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Update server executors' configuration to disable 'buildOptimizer' for non optimized builds.",
|
|
||||||
"file": "generated/packages/angular/migrations/update-server-executor-config.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "update-server-executor-config",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "angular/migrations/update-server-executor-config",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Update the @angular/cli package version to ~16.0.0.",
|
|
||||||
"file": "generated/packages/angular/migrations/update-angular-cli-version-16-0-0.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "update-angular-cli-version-16-0-0",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "angular/migrations/update-angular-cli-version-16-0-0",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"description": "",
|
"description": "",
|
||||||
"file": "generated/packages/angular/migrations/16.1.0-package-updates.json",
|
"file": "generated/packages/angular/migrations/16.1.0-package-updates.json",
|
||||||
@ -1238,46 +1198,6 @@
|
|||||||
"originalFilePath": "/packages/angular",
|
"originalFilePath": "/packages/angular",
|
||||||
"path": "angular/migrations/16.1.0-angular-eslint-package-updates",
|
"path": "angular/migrations/16.1.0-angular-eslint-package-updates",
|
||||||
"type": "migration"
|
"type": "migration"
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Remove protractor as default e2eTestRunner from nxJson and project configurations",
|
|
||||||
"file": "generated/packages/angular/migrations/remove-protractor-defaults-from-generators.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "remove-protractor-defaults-from-generators",
|
|
||||||
"version": "16.0.0-beta.6",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "angular/migrations/remove-protractor-defaults-from-generators",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Remove karma as default unitTestRunner from nxJson and project configurations",
|
|
||||||
"file": "generated/packages/angular/migrations/remove-karma-defaults-from-generators.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "remove-karma-defaults-from-generators",
|
|
||||||
"version": "16.0.0-beta.6",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "angular/migrations/remove-karma-defaults-from-generators",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Replace the deprecated library generator 'simpleModuleName' option from generator defaults with 'simpleName'",
|
|
||||||
"file": "generated/packages/angular/migrations/remove-library-generator-simple-module-name-option.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "remove-library-generator-simple-module-name-option",
|
|
||||||
"version": "16.0.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "angular/migrations/remove-library-generator-simple-module-name-option",
|
|
||||||
"type": "migration"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Replace @nrwl/angular with @nx/angular",
|
|
||||||
"file": "generated/packages/angular/migrations/update-16-0-0-add-nx-packages.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "update-16-0-0-add-nx-packages",
|
|
||||||
"version": "16.0.0-beta.1",
|
|
||||||
"originalFilePath": "/packages/angular",
|
|
||||||
"path": "angular/migrations/update-16-0-0-add-nx-packages",
|
|
||||||
"type": "migration"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "A browser builder target to serve in the format of `project:target[:configuration]`. Ignored if `buildTarget` is set.",
|
"description": "A browser builder target to serve in the format of `project:target[:configuration]`. Ignored if `buildTarget` is set.",
|
||||||
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
|
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
|
||||||
"x-deprecated": "Use 'buildTarget' instead. It will be removed in Nx v20."
|
"x-deprecated": "Use 'buildTarget' instead. It will be removed when Angular v20 is released."
|
||||||
},
|
},
|
||||||
"buildTarget": {
|
"buildTarget": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "A browser builder target to serve in the format of `project:target[:configuration]`.",
|
"description": "A browser builder target to serve in the format of `project:target[:configuration]`.",
|
||||||
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
|
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
|
||||||
"x-deprecated": "Use 'buildTarget' instead. It will be removed in Nx v20."
|
"x-deprecated": "Use 'buildTarget' instead. It will be removed when Angular v20 is released."
|
||||||
},
|
},
|
||||||
"buildTarget": {
|
"buildTarget": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@ -57,7 +57,7 @@
|
|||||||
},
|
},
|
||||||
"aliases": ["mv"],
|
"aliases": ["mv"],
|
||||||
"description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration.",
|
"description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration.",
|
||||||
"x-deprecated": "Use the `@nx/workspace:move` generator instead. This generator will be removed in Nx v19.",
|
"x-deprecated": "Use the `@nx/workspace:move` generator instead. This generator will be removed in Nx v22.",
|
||||||
"implementation": "/packages/angular/src/generators/move/move#angularMoveGenerator.ts",
|
"implementation": "/packages/angular/src/generators/move/move#angularMoveGenerator.ts",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"path": "/packages/angular/src/generators/move/schema.json",
|
"path": "/packages/angular/src/generators/move/schema.json",
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
"$id": "NxNgrxGenerator",
|
"$id": "NxNgrxGenerator",
|
||||||
"title": "Add NgRx support to an application or library.",
|
"title": "Add NgRx support to an application or library.",
|
||||||
"description": "Adds NgRx support to an application or library.",
|
"description": "Adds NgRx support to an application or library.",
|
||||||
"x-deprecated": "This generator is deprecated and will be removed in a future version of Nx. Use the 'ngrx-root-store' and 'ngrx-feature-store' generators instead.",
|
"x-deprecated": "Use the 'ngrx-root-store' and 'ngrx-feature-store' generators instead. It will be removed in Nx v22.",
|
||||||
"cli": "nx",
|
"cli": "nx",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"examples": [
|
"examples": [
|
||||||
@ -102,7 +102,7 @@
|
|||||||
"presets": []
|
"presets": []
|
||||||
},
|
},
|
||||||
"description": "Adds NgRx support to an application or library.",
|
"description": "Adds NgRx support to an application or library.",
|
||||||
"x-deprecated": "This generator is deprecated and will be removed in a future version of Nx. Use the 'ngrx-root-store' and 'ngrx-feature-store' generators instead.",
|
"x-deprecated": "Use the 'ngrx-root-store' and 'ngrx-feature-store' generators instead. It will be removed in Nx v22.",
|
||||||
"implementation": "/packages/angular/src/generators/ngrx/ngrx.ts",
|
"implementation": "/packages/angular/src/generators/ngrx/ngrx.ts",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
|
|||||||
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence",
|
||||||
|
"cli": "nx",
|
||||||
|
"version": "21.0.0-beta.5",
|
||||||
|
"requires": { "@ngrx/store": ">=16.0.0" },
|
||||||
|
"description": "Change the data persistence operator imports to '@ngrx/router-store/data-persistence'.",
|
||||||
|
"factory": "./src/migrations/update-21-0-0/change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence",
|
||||||
|
"implementation": "/packages/angular/src/migrations/update-21-0-0/change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence.ts",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/angular",
|
||||||
|
"schema": null,
|
||||||
|
"type": "migration",
|
||||||
|
"examplesFile": "#### Change the Data Persistence Operator Imports from `@nx/angular` to `@ngrx/router-store/data-persistence`\n\nThe data persistence operators (`fetch`, `navigation`, `optimisticUpdate`, and `pessimisticUpdate`) have been deprecated for a while and are now removed from the `@nx/angular` package. This migration automatically updates your import statements to use the `@ngrx/router-store/data-persistence` module and adds `@ngrx/router-store` to your dependencies if needed.\n\n#### Examples\n\nIf you import only data persistence operators from `@nx/angular`, the migration will update the import path to `@ngrx/router-store/data-persistence`.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"apps/app1/src/app/users/users.effects.ts\" highlightLines=[2] %}\nimport { Actions, createEffect, ofType } from '@ngrx/effects';\nimport { fetch } from '@nx/angular';\n\n@Injectable()\nexport class UsersEffects {\n // ...\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"apps/app1/src/app/users/users.effects.ts\" highlightLines=[2] %}\nimport { Injectable } from '@angular/core';\nimport { fetch } from '@ngrx/router-store/data-persistence';\n\n@Injectable()\nexport class UsersEffects {\n // ...\n}\n```\n\n{% /tab %}\n{% /tabs %}\n\nIf you import multiple data persistence operators from `@nx/angular`, the migration will update the import path for all of them.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"apps/app1/src/app/users/users.effects.ts\" highlightLines=[2] %}\nimport { Injectable } from '@angular/core';\nimport { fetch, navigation } from '@nx/angular';\n\n@Injectable()\nexport class UsersEffects {\n // ...\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"apps/app1/src/app/users/users.effects.ts\" highlightLines=[2] %}\nimport { Injectable } from '@angular/core';\nimport { fetch, navigation } from '@ngrx/router-store/data-persistence';\n\n@Injectable()\nexport class UsersEffects {\n // ...\n}\n```\n\n{% /tab %}\n\n{% /tab %}\n{% /tabs %}\n\nIf your imports mix data persistence operators with other utilities from `@nx/angular`, the migration will split them into separate import statements.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\"apps/app1/src/app/users/users.effects.ts\" highlightLines=[2] %}\nimport { Injectable } from '@angular/core';\nimport { fetch, someExtraUtility, navigation } from '@nx/angular';\n\n@Injectable()\nexport class UsersEffects {\n // ...\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```ts {% fileName=\"apps/app1/src/app/users/users.effects.ts\" highlightLines=[2,3] %}\nimport { Injectable } from '@angular/core';\nimport { fetch, navigation } from '@ngrx/router-store/data-persistence';\nimport { someExtraUtility } from '@nx/angular';\n\n@Injectable()\nexport class UsersEffects {\n // ...\n}\n```\n\n{% /tab %}\n{% /tabs %}\n\nIf you don't already have `@ngrx/router-store` in your dependencies, the migration will add it to your package.json.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```jsonc {% fileName=\"package.json\" %}\n{\n \"dependencies\": {\n \"@nx/angular\": \"^21.0.0\",\n \"@ngrx/store\": \"^19.1.0\",\n \"@ngrx/effects\": \"^19.1.0\"\n // ...\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```jsonc {% fileName=\"package.json\" highlightLines=[6] %}\n{\n \"dependencies\": {\n \"@nx/angular\": \"^21.0.0\",\n \"@ngrx/store\": \"^19.1.0\",\n \"@ngrx/effects\": \"^19.1.0\",\n \"@ngrx/router-store\": \"^19.1.0\"\n // ...\n }\n}\n```\n\n{% /tab %}\n{% /tabs %}\n"
|
||||||
|
}
|
||||||
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "extract-app-config-for-standalone",
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"requires": { "@angular/core": ">=16.0.0-rc.4" },
|
|
||||||
"description": "Extract the app config for standalone apps",
|
|
||||||
"factory": "./src/migrations/update-16-1-0/extract-standalone-config-from-bootstrap",
|
|
||||||
"implementation": "/packages/angular/src/migrations/update-16-1-0/extract-standalone-config-from-bootstrap.ts",
|
|
||||||
"aliases": [],
|
|
||||||
"hidden": false,
|
|
||||||
"path": "/packages/angular",
|
|
||||||
"schema": null,
|
|
||||||
"type": "migration",
|
|
||||||
"examplesFile": ""
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "remove-karma-defaults-from-generators",
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.0.0-beta.6",
|
|
||||||
"description": "Remove karma as default unitTestRunner from nxJson and project configurations",
|
|
||||||
"implementation": "/packages/angular/src/migrations/update-16-0-0/remove-karma-defaults.ts",
|
|
||||||
"aliases": [],
|
|
||||||
"hidden": false,
|
|
||||||
"path": "/packages/angular",
|
|
||||||
"schema": null,
|
|
||||||
"type": "migration",
|
|
||||||
"examplesFile": ""
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "remove-library-generator-simple-module-name-option",
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.0.0-beta.1",
|
|
||||||
"description": "Replace the deprecated library generator 'simpleModuleName' option from generator defaults with 'simpleName'",
|
|
||||||
"factory": "./src/migrations/update-16-0-0/remove-library-generator-simple-module-name-option",
|
|
||||||
"implementation": "/packages/angular/src/migrations/update-16-0-0/remove-library-generator-simple-module-name-option.ts",
|
|
||||||
"aliases": [],
|
|
||||||
"hidden": false,
|
|
||||||
"path": "/packages/angular",
|
|
||||||
"schema": null,
|
|
||||||
"type": "migration",
|
|
||||||
"examplesFile": ""
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "remove-ngcc-invocation",
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"requires": { "@angular/core": ">=16.0.0-rc.4" },
|
|
||||||
"description": "Remove 'ngcc' invocation if exists from the 'postinstall' script in package.json.",
|
|
||||||
"factory": "./src/migrations/update-16-1-0/remove-ngcc-invocation",
|
|
||||||
"implementation": "/packages/angular/src/migrations/update-16-1-0/remove-ngcc-invocation.ts",
|
|
||||||
"aliases": [],
|
|
||||||
"hidden": false,
|
|
||||||
"path": "/packages/angular",
|
|
||||||
"schema": null,
|
|
||||||
"type": "migration",
|
|
||||||
"examplesFile": ""
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "remove-protractor-defaults-from-generators",
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.0.0-beta.6",
|
|
||||||
"description": "Remove protractor as default e2eTestRunner from nxJson and project configurations",
|
|
||||||
"implementation": "/packages/angular/src/migrations/update-16-0-0/remove-protractor-defaults.ts",
|
|
||||||
"aliases": [],
|
|
||||||
"hidden": false,
|
|
||||||
"path": "/packages/angular",
|
|
||||||
"schema": null,
|
|
||||||
"type": "migration",
|
|
||||||
"examplesFile": ""
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "remove-render-module-platform-server-exports",
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"requires": { "@angular/core": ">=15.0.0" },
|
|
||||||
"description": "Remove exported `@angular/platform-server` `renderModule` method. The `renderModule` method is now exported by the Angular CLI.",
|
|
||||||
"factory": "./src/migrations/update-16-1-0/remove-render-module-platform-server-exports",
|
|
||||||
"implementation": "/packages/angular/src/migrations/update-16-1-0/remove-render-module-platform-server-exports.ts",
|
|
||||||
"aliases": [],
|
|
||||||
"hidden": false,
|
|
||||||
"path": "/packages/angular",
|
|
||||||
"schema": null,
|
|
||||||
"type": "migration",
|
|
||||||
"examplesFile": ""
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "update-16-0-0-add-nx-packages",
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.0.0-beta.1",
|
|
||||||
"description": "Replace @nrwl/angular with @nx/angular",
|
|
||||||
"implementation": "/packages/angular/src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages.ts",
|
|
||||||
"aliases": [],
|
|
||||||
"hidden": false,
|
|
||||||
"path": "/packages/angular",
|
|
||||||
"schema": null,
|
|
||||||
"type": "migration",
|
|
||||||
"examplesFile": ""
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "update-angular-cli-version-16-0-0",
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"requires": { "@angular/core": ">=16.0.0" },
|
|
||||||
"description": "Update the @angular/cli package version to ~16.0.0.",
|
|
||||||
"factory": "./src/migrations/update-16-1-0/update-angular-cli",
|
|
||||||
"implementation": "/packages/angular/src/migrations/update-16-1-0/update-angular-cli.ts",
|
|
||||||
"aliases": [],
|
|
||||||
"hidden": false,
|
|
||||||
"path": "/packages/angular",
|
|
||||||
"schema": null,
|
|
||||||
"type": "migration",
|
|
||||||
"examplesFile": ""
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "update-server-executor-config",
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"requires": { "@angular/core": ">=16.0.0-rc.4" },
|
|
||||||
"description": "Update server executors' configuration to disable 'buildOptimizer' for non optimized builds.",
|
|
||||||
"factory": "./src/migrations/update-16-1-0/update-server-executor-config",
|
|
||||||
"implementation": "/packages/angular/src/migrations/update-16-1-0/update-server-executor-config.ts",
|
|
||||||
"aliases": [],
|
|
||||||
"hidden": false,
|
|
||||||
"path": "/packages/angular",
|
|
||||||
"schema": null,
|
|
||||||
"type": "migration",
|
|
||||||
"examplesFile": ""
|
|
||||||
}
|
|
||||||
@ -70,8 +70,6 @@
|
|||||||
"@nestjs/schematics": "^9.1.0",
|
"@nestjs/schematics": "^9.1.0",
|
||||||
"@nestjs/swagger": "^6.0.0",
|
"@nestjs/swagger": "^6.0.0",
|
||||||
"@nestjs/testing": "^9.0.0",
|
"@nestjs/testing": "^9.0.0",
|
||||||
"@ngrx/router-store": "19.0.0",
|
|
||||||
"@ngrx/store": "19.0.0",
|
|
||||||
"@notionhq/client": "^2.2.15",
|
"@notionhq/client": "^2.2.15",
|
||||||
"@nuxt/kit": "^3.10.0",
|
"@nuxt/kit": "^3.10.0",
|
||||||
"@nuxt/schema": "^3.10.0",
|
"@nuxt/schema": "^3.10.0",
|
||||||
|
|||||||
@ -86,7 +86,7 @@
|
|||||||
"schema": "./src/generators/move/schema.json",
|
"schema": "./src/generators/move/schema.json",
|
||||||
"aliases": ["mv"],
|
"aliases": ["mv"],
|
||||||
"description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration.",
|
"description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration.",
|
||||||
"x-deprecated": "Use the `@nx/workspace:move` generator instead. This generator will be removed in Nx v19."
|
"x-deprecated": "Use the `@nx/workspace:move` generator instead. This generator will be removed in Nx v22."
|
||||||
},
|
},
|
||||||
"convert-to-with-mf": {
|
"convert-to-with-mf": {
|
||||||
"factory": "./src/generators/convert-to-with-mf/convert-to-with-mf",
|
"factory": "./src/generators/convert-to-with-mf/convert-to-with-mf",
|
||||||
@ -110,7 +110,7 @@
|
|||||||
"factory": "./src/generators/ngrx/ngrx",
|
"factory": "./src/generators/ngrx/ngrx",
|
||||||
"schema": "./src/generators/ngrx/schema.json",
|
"schema": "./src/generators/ngrx/schema.json",
|
||||||
"description": "Adds NgRx support to an application or library.",
|
"description": "Adds NgRx support to an application or library.",
|
||||||
"x-deprecated": "This generator is deprecated and will be removed in a future version of Nx. Use the 'ngrx-root-store' and 'ngrx-feature-store' generators instead."
|
"x-deprecated": "Use the 'ngrx-root-store' and 'ngrx-feature-store' generators instead. It will be removed in Nx v22."
|
||||||
},
|
},
|
||||||
"ngrx-feature-store": {
|
"ngrx-feature-store": {
|
||||||
"factory": "./src/generators/ngrx-feature-store/ngrx-feature-store",
|
"factory": "./src/generators/ngrx-feature-store/ngrx-feature-store",
|
||||||
|
|||||||
@ -1,6 +1 @@
|
|||||||
export {
|
export {};
|
||||||
fetch,
|
|
||||||
navigation,
|
|
||||||
optimisticUpdate,
|
|
||||||
pessimisticUpdate,
|
|
||||||
} from './src/runtime/nx/data-persistence';
|
|
||||||
|
|||||||
@ -1,74 +1,5 @@
|
|||||||
{
|
{
|
||||||
"generators": {
|
"generators": {
|
||||||
"remove-library-generator-simple-module-name-option": {
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.0.0-beta.1",
|
|
||||||
"description": "Replace the deprecated library generator 'simpleModuleName' option from generator defaults with 'simpleName'",
|
|
||||||
"factory": "./src/migrations/update-16-0-0/remove-library-generator-simple-module-name-option"
|
|
||||||
},
|
|
||||||
"update-16-0-0-add-nx-packages": {
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.0.0-beta.1",
|
|
||||||
"description": "Replace @nrwl/angular with @nx/angular",
|
|
||||||
"implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages"
|
|
||||||
},
|
|
||||||
"remove-protractor-defaults-from-generators": {
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.0.0-beta.6",
|
|
||||||
"description": "Remove protractor as default e2eTestRunner from nxJson and project configurations",
|
|
||||||
"implementation": "./src/migrations/update-16-0-0/remove-protractor-defaults"
|
|
||||||
},
|
|
||||||
"remove-karma-defaults-from-generators": {
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.0.0-beta.6",
|
|
||||||
"description": "Remove karma as default unitTestRunner from nxJson and project configurations",
|
|
||||||
"implementation": "./src/migrations/update-16-0-0/remove-karma-defaults"
|
|
||||||
},
|
|
||||||
"remove-render-module-platform-server-exports": {
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"requires": {
|
|
||||||
"@angular/core": ">=15.0.0"
|
|
||||||
},
|
|
||||||
"description": "Remove exported `@angular/platform-server` `renderModule` method. The `renderModule` method is now exported by the Angular CLI.",
|
|
||||||
"factory": "./src/migrations/update-16-1-0/remove-render-module-platform-server-exports"
|
|
||||||
},
|
|
||||||
"remove-ngcc-invocation": {
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"requires": {
|
|
||||||
"@angular/core": ">=16.0.0-rc.4"
|
|
||||||
},
|
|
||||||
"description": "Remove 'ngcc' invocation if exists from the 'postinstall' script in package.json.",
|
|
||||||
"factory": "./src/migrations/update-16-1-0/remove-ngcc-invocation"
|
|
||||||
},
|
|
||||||
"extract-app-config-for-standalone": {
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"requires": {
|
|
||||||
"@angular/core": ">=16.0.0-rc.4"
|
|
||||||
},
|
|
||||||
"description": "Extract the app config for standalone apps",
|
|
||||||
"factory": "./src/migrations/update-16-1-0/extract-standalone-config-from-bootstrap"
|
|
||||||
},
|
|
||||||
"update-server-executor-config": {
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"requires": {
|
|
||||||
"@angular/core": ">=16.0.0-rc.4"
|
|
||||||
},
|
|
||||||
"description": "Update server executors' configuration to disable 'buildOptimizer' for non optimized builds.",
|
|
||||||
"factory": "./src/migrations/update-16-1-0/update-server-executor-config"
|
|
||||||
},
|
|
||||||
"update-angular-cli-version-16-0-0": {
|
|
||||||
"cli": "nx",
|
|
||||||
"version": "16.1.0-beta.1",
|
|
||||||
"requires": {
|
|
||||||
"@angular/core": ">=16.0.0"
|
|
||||||
},
|
|
||||||
"description": "Update the @angular/cli package version to ~16.0.0.",
|
|
||||||
"factory": "./src/migrations/update-16-1-0/update-angular-cli"
|
|
||||||
},
|
|
||||||
"switch-data-persistence-operators-imports-to-ngrx-router-store": {
|
"switch-data-persistence-operators-imports-to-ngrx-router-store": {
|
||||||
"cli": "nx",
|
"cli": "nx",
|
||||||
"version": "16.2.0-beta.0",
|
"version": "16.2.0-beta.0",
|
||||||
@ -362,6 +293,15 @@
|
|||||||
"version": "21.0.0-beta.3",
|
"version": "21.0.0-beta.3",
|
||||||
"description": "Set the `continuous` option to `true` for continuous tasks.",
|
"description": "Set the `continuous` option to `true` for continuous tasks.",
|
||||||
"factory": "./src/migrations/update-21-0-0/set-continuous-option"
|
"factory": "./src/migrations/update-21-0-0/set-continuous-option"
|
||||||
|
},
|
||||||
|
"change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence": {
|
||||||
|
"cli": "nx",
|
||||||
|
"version": "21.0.0-beta.5",
|
||||||
|
"requires": {
|
||||||
|
"@ngrx/store": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"description": "Change the data persistence operator imports to '@ngrx/router-store/data-persistence'.",
|
||||||
|
"factory": "./src/migrations/update-21-0-0/change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packageJsonUpdates": {
|
"packageJsonUpdates": {
|
||||||
|
|||||||
@ -32,8 +32,7 @@
|
|||||||
"./src/builders/*/schema.json": "./src/builders/*/schema.json",
|
"./src/builders/*/schema.json": "./src/builders/*/schema.json",
|
||||||
"./src/builders/*.impl": "./src/builders/*.impl.js",
|
"./src/builders/*.impl": "./src/builders/*.impl.js",
|
||||||
"./src/executors/*/schema.json": "./src/executors/*/schema.json",
|
"./src/executors/*/schema.json": "./src/executors/*/schema.json",
|
||||||
"./src/executors/*.impl": "./src/executors/*.impl.js",
|
"./src/executors/*.impl": "./src/executors/*.impl.js"
|
||||||
"./src/executors/*/compat": "./src/executors/*/compat.js"
|
|
||||||
},
|
},
|
||||||
"author": "Victor Savkin",
|
"author": "Victor Savkin",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@ -1,700 +0,0 @@
|
|||||||
describe('temporary disabled', () => {
|
|
||||||
it('temporary disabled', () => {});
|
|
||||||
});
|
|
||||||
// import { Component, Injectable } from '@angular/core';
|
|
||||||
// import { fakeAsync, TestBed, tick } from '@angular/core/testing';
|
|
||||||
// import { Router } from '@angular/router';
|
|
||||||
// import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
// import { Actions, Effect, EffectsModule, ofType } from '@ngrx/effects';
|
|
||||||
// import { provideMockActions } from '@ngrx/effects/testing';
|
|
||||||
// import {
|
|
||||||
// StoreRouterConnectingModule,
|
|
||||||
// DefaultRouterStateSerializer,
|
|
||||||
// } from '@ngrx/router-store';
|
|
||||||
// import { Store, StoreModule } from '@ngrx/store';
|
|
||||||
// import { Observable, of, Subject, throwError } from 'rxjs';
|
|
||||||
// import { delay, withLatestFrom } from 'rxjs/operators';
|
|
||||||
//
|
|
||||||
// import {
|
|
||||||
// DataPersistence,
|
|
||||||
// pessimisticUpdate,
|
|
||||||
// optimisticUpdate,
|
|
||||||
// fetch,
|
|
||||||
// NxModule,
|
|
||||||
// } from '../index';
|
|
||||||
// import { readAll } from '../testing';
|
|
||||||
//
|
|
||||||
// // interfaces
|
|
||||||
// type Todo = {
|
|
||||||
// id: number;
|
|
||||||
// user: string;
|
|
||||||
// };
|
|
||||||
// type Todos = {
|
|
||||||
// selected: Todo;
|
|
||||||
// };
|
|
||||||
// type TodosState = {
|
|
||||||
// todos: Todos;
|
|
||||||
// user: string;
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// // actions
|
|
||||||
// type TodoLoaded = {
|
|
||||||
// type: 'TODO_LOADED';
|
|
||||||
// payload: Todo;
|
|
||||||
// };
|
|
||||||
// type UpdateTodo = {
|
|
||||||
// type: 'UPDATE_TODO';
|
|
||||||
// payload: { newTitle: string };
|
|
||||||
// };
|
|
||||||
// type Action = TodoLoaded;
|
|
||||||
//
|
|
||||||
// // reducers
|
|
||||||
// function todosReducer(state: Todos, action: Action): Todos {
|
|
||||||
// if (action.type === 'TODO_LOADED') {
|
|
||||||
// return { selected: action.payload };
|
|
||||||
// } else {
|
|
||||||
// return state;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// function userReducer(): string {
|
|
||||||
// return 'bob';
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Component({
|
|
||||||
// template: ` ROOT[<router-outlet></router-outlet>] `,
|
|
||||||
// })
|
|
||||||
// class RootCmp {}
|
|
||||||
//
|
|
||||||
// @Component({
|
|
||||||
// template: `
|
|
||||||
// Todo [
|
|
||||||
// <div *ngIf="todo | async as t">ID {{ t.id }} User {{ t.user }}</div>
|
|
||||||
// ]
|
|
||||||
// `,
|
|
||||||
// })
|
|
||||||
// class TodoComponent {
|
|
||||||
// todo = this.store.select('todos', 'selected');
|
|
||||||
// constructor(private store: Store<TodosState>) {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// xdescribe('DataPersistence', () => {
|
|
||||||
// describe('navigation', () => {
|
|
||||||
// beforeEach(() => {
|
|
||||||
// TestBed.configureTestingModule({
|
|
||||||
// declarations: [RootCmp, TodoComponent],
|
|
||||||
// imports: [
|
|
||||||
// StoreModule.forRoot(
|
|
||||||
// { todos: todosReducer, user: userReducer },
|
|
||||||
// {
|
|
||||||
// runtimeChecks: {
|
|
||||||
// strictStateImmutability: false,
|
|
||||||
// strictStateSerializability: false,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// ),
|
|
||||||
// StoreRouterConnectingModule.forRoot({
|
|
||||||
// serializer: DefaultRouterStateSerializer,
|
|
||||||
// }),
|
|
||||||
// RouterTestingModule.withRoutes([
|
|
||||||
// { path: 'todo/:id', component: TodoComponent },
|
|
||||||
// ]),
|
|
||||||
// NxModule.forRoot(),
|
|
||||||
// ],
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('successful navigation', () => {
|
|
||||||
// @Injectable()
|
|
||||||
// class TodoEffects {
|
|
||||||
// @Effect()
|
|
||||||
// loadTodo = this.s.navigation(TodoComponent, {
|
|
||||||
// run: (a, state) => {
|
|
||||||
// return {
|
|
||||||
// type: 'TODO_LOADED',
|
|
||||||
// payload: { id: a.params['id'], user: state.user },
|
|
||||||
// };
|
|
||||||
// },
|
|
||||||
// onError: () => null,
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// constructor(private s: DataPersistence<TodosState>) {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// beforeEach(() => {
|
|
||||||
// TestBed.configureTestingModule({
|
|
||||||
// imports: [EffectsModule.forRoot([TodoEffects])],
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work', fakeAsync(() => {
|
|
||||||
// const root = TestBed.createComponent(RootCmp);
|
|
||||||
//
|
|
||||||
// const router: Router = TestBed.get(Router);
|
|
||||||
// router.navigateByUrl('/todo/123');
|
|
||||||
// tick(0);
|
|
||||||
// root.detectChanges(false);
|
|
||||||
//
|
|
||||||
// expect(root.elementRef.nativeElement.innerHTML).toContain('ID 123');
|
|
||||||
// expect(root.elementRef.nativeElement.innerHTML).toContain('User bob');
|
|
||||||
// }));
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('`run` throwing an error', () => {
|
|
||||||
// @Injectable()
|
|
||||||
// class TodoEffects {
|
|
||||||
// @Effect()
|
|
||||||
// loadTodo = this.s.navigation(TodoComponent, {
|
|
||||||
// run: (a, state) => {
|
|
||||||
// if (a.params['id'] === '123') {
|
|
||||||
// throw new Error('boom');
|
|
||||||
// } else {
|
|
||||||
// return {
|
|
||||||
// type: 'TODO_LOADED',
|
|
||||||
// payload: { id: a.params['id'], user: state.user },
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// onError: (a, e) => ({ type: 'ERROR', payload: { error: e } }),
|
|
||||||
// });
|
|
||||||
// constructor(private s: DataPersistence<TodosState>) {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// beforeEach(() => {
|
|
||||||
// TestBed.configureTestingModule({
|
|
||||||
// imports: [EffectsModule.forRoot([TodoEffects])],
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work', fakeAsync(() => {
|
|
||||||
// const root = TestBed.createComponent(RootCmp);
|
|
||||||
//
|
|
||||||
// const router: Router = TestBed.get(Router);
|
|
||||||
// let actions: any[] = [];
|
|
||||||
// TestBed.get(Actions).subscribe((a: any) => actions.push(a));
|
|
||||||
//
|
|
||||||
// router.navigateByUrl('/todo/123');
|
|
||||||
// tick(0);
|
|
||||||
// root.detectChanges(false);
|
|
||||||
// expect(root.elementRef.nativeElement.innerHTML).not.toContain('ID 123');
|
|
||||||
// expect(actions.map((a) => a.type)).toContain('ERROR');
|
|
||||||
// expect(
|
|
||||||
// actions.find((a) => a.type === 'ERROR').payload.error.message
|
|
||||||
// ).toEqual('boom');
|
|
||||||
//
|
|
||||||
// // can recover after an error
|
|
||||||
// router.navigateByUrl('/todo/456');
|
|
||||||
// tick(0);
|
|
||||||
// root.detectChanges(false);
|
|
||||||
// expect(root.elementRef.nativeElement.innerHTML).toContain('ID 456');
|
|
||||||
// }));
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('`run` returning an error observable', () => {
|
|
||||||
// @Injectable()
|
|
||||||
// class TodoEffects {
|
|
||||||
// @Effect()
|
|
||||||
// loadTodo = this.s.navigation(TodoComponent, {
|
|
||||||
// run: (a, state) => {
|
|
||||||
// if (a.params['id'] === '123') {
|
|
||||||
// return throwError('boom');
|
|
||||||
// } else {
|
|
||||||
// return {
|
|
||||||
// type: 'TODO_LOADED',
|
|
||||||
// payload: { id: a.params['id'], user: state.user },
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// onError: (a, e) => ({ type: 'ERROR', payload: { error: e } }),
|
|
||||||
// });
|
|
||||||
// constructor(private s: DataPersistence<TodosState>) {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// beforeEach(() => {
|
|
||||||
// TestBed.configureTestingModule({
|
|
||||||
// imports: [EffectsModule.forRoot([TodoEffects])],
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work', fakeAsync(() => {
|
|
||||||
// const root = TestBed.createComponent(RootCmp);
|
|
||||||
//
|
|
||||||
// const router: Router = TestBed.get(Router);
|
|
||||||
// let actions: any[] = [];
|
|
||||||
// TestBed.get(Actions).subscribe((a: any) => actions.push(a));
|
|
||||||
//
|
|
||||||
// router.navigateByUrl('/todo/123');
|
|
||||||
// tick(0);
|
|
||||||
// root.detectChanges(false);
|
|
||||||
// expect(root.elementRef.nativeElement.innerHTML).not.toContain('ID 123');
|
|
||||||
// expect(actions.map((a) => a.type)).toContain('ERROR');
|
|
||||||
// expect(actions.find((a) => a.type === 'ERROR').payload.error).toEqual(
|
|
||||||
// 'boom'
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// router.navigateByUrl('/todo/456');
|
|
||||||
// tick(0);
|
|
||||||
// root.detectChanges(false);
|
|
||||||
// expect(root.elementRef.nativeElement.innerHTML).toContain('ID 456');
|
|
||||||
// }));
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('fetch', () => {
|
|
||||||
// beforeEach(() => {
|
|
||||||
// TestBed.configureTestingModule({ providers: [DataPersistence] });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('no id', () => {
|
|
||||||
// type GetTodos = {
|
|
||||||
// type: 'GET_TODOS';
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// @Injectable()
|
|
||||||
// class TodoEffects {
|
|
||||||
// @Effect()
|
|
||||||
// loadTodos = this.s.fetch<GetTodos>('GET_TODOS', {
|
|
||||||
// run: (a, state) => {
|
|
||||||
// // we need to introduce the delay to "enable" switchMap
|
|
||||||
// return of({
|
|
||||||
// type: 'TODOS',
|
|
||||||
// payload: { user: state.user, todos: 'some todos' },
|
|
||||||
// }).pipe(delay(1));
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// onError: () => null,
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// @Effect()
|
|
||||||
// loadTodosWithOperator = this.s.actions.pipe(
|
|
||||||
// ofType<GetTodos>('GET_TODOS'),
|
|
||||||
// withLatestFrom(this.s.store),
|
|
||||||
// fetch({
|
|
||||||
// run: (action, state) => {
|
|
||||||
// return of({
|
|
||||||
// type: 'TODOS',
|
|
||||||
// payload: { user: state.user, todos: 'some todos' },
|
|
||||||
// }).pipe(delay(1));
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// constructor(private s: DataPersistence<TodosState>) {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// function userReducer() {
|
|
||||||
// return 'bob';
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let actions: Observable<any>;
|
|
||||||
//
|
|
||||||
// beforeEach(() => {
|
|
||||||
// actions = new Subject<any>();
|
|
||||||
// TestBed.configureTestingModule({
|
|
||||||
// providers: [TodoEffects, provideMockActions(() => actions)],
|
|
||||||
// imports: [
|
|
||||||
// StoreModule.forRoot(
|
|
||||||
// { user: userReducer },
|
|
||||||
// {
|
|
||||||
// runtimeChecks: {
|
|
||||||
// strictStateImmutability: false,
|
|
||||||
// strictStateSerializability: false,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work', async (done) => {
|
|
||||||
// actions = of(
|
|
||||||
// { type: 'GET_TODOS', payload: {} },
|
|
||||||
// { type: 'GET_TODOS', payload: {} }
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// expect(await readAll(TestBed.get(TodoEffects).loadTodos)).toEqual([
|
|
||||||
// { type: 'TODOS', payload: { user: 'bob', todos: 'some todos' } },
|
|
||||||
// { type: 'TODOS', payload: { user: 'bob', todos: 'some todos' } },
|
|
||||||
// ]);
|
|
||||||
//
|
|
||||||
// done();
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work with an operator', async (done) => {
|
|
||||||
// actions = of(
|
|
||||||
// { type: 'GET_TODOS', payload: {} },
|
|
||||||
// { type: 'GET_TODOS', payload: {} }
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// expect(
|
|
||||||
// await readAll(TestBed.get(TodoEffects).loadTodosWithOperator)
|
|
||||||
// ).toEqual([
|
|
||||||
// { type: 'TODOS', payload: { user: 'bob', todos: 'some todos' } },
|
|
||||||
// { type: 'TODOS', payload: { user: 'bob', todos: 'some todos' } },
|
|
||||||
// ]);
|
|
||||||
//
|
|
||||||
// done();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('id', () => {
|
|
||||||
// type GetTodo = {
|
|
||||||
// type: 'GET_TODO';
|
|
||||||
// payload: { id: string };
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// @Injectable()
|
|
||||||
// class TodoEffects {
|
|
||||||
// @Effect()
|
|
||||||
// loadTodo = this.s.fetch<GetTodo>('GET_TODO', {
|
|
||||||
// id: (a) => a.payload.id,
|
|
||||||
// run: (a) => of({ type: 'TODO', payload: a.payload }).pipe(delay(1)),
|
|
||||||
// onError: () => null,
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// constructor(private s: DataPersistence<TodosState>) {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// function userReducer() {
|
|
||||||
// return 'bob';
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let actions: Observable<any>;
|
|
||||||
//
|
|
||||||
// beforeEach(() => {
|
|
||||||
// actions = new Subject<any>();
|
|
||||||
// TestBed.configureTestingModule({
|
|
||||||
// providers: [TodoEffects, provideMockActions(() => actions)],
|
|
||||||
// imports: [
|
|
||||||
// StoreModule.forRoot(
|
|
||||||
// { user: userReducer },
|
|
||||||
// {
|
|
||||||
// runtimeChecks: {
|
|
||||||
// strictStateImmutability: false,
|
|
||||||
// strictStateSerializability: false,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work', async (done) => {
|
|
||||||
// actions = of(
|
|
||||||
// { type: 'GET_TODO', payload: { id: 1, value: '1' } },
|
|
||||||
// { type: 'GET_TODO', payload: { id: 2, value: '2a' } },
|
|
||||||
// { type: 'GET_TODO', payload: { id: 2, value: '2b' } }
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// expect(await readAll(TestBed.get(TodoEffects).loadTodo)).toEqual([
|
|
||||||
// { type: 'TODO', payload: { id: 1, value: '1' } },
|
|
||||||
// { type: 'TODO', payload: { id: 2, value: '2b' } },
|
|
||||||
// ]);
|
|
||||||
//
|
|
||||||
// done();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('pessimisticUpdate', () => {
|
|
||||||
// beforeEach(() => {
|
|
||||||
// TestBed.configureTestingModule({ providers: [DataPersistence] });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('successful', () => {
|
|
||||||
// @Injectable()
|
|
||||||
// class TodoEffects {
|
|
||||||
// @Effect()
|
|
||||||
// loadTodo = this.s.pessimisticUpdate<UpdateTodo>('UPDATE_TODO', {
|
|
||||||
// run: (a, state) => ({
|
|
||||||
// type: 'TODO_UPDATED',
|
|
||||||
// payload: { user: state.user, newTitle: a.payload.newTitle },
|
|
||||||
// }),
|
|
||||||
// onError: () => null,
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// @Effect()
|
|
||||||
// loadTodoWithOperator = this.s.actions.pipe(
|
|
||||||
// ofType<UpdateTodo>('UPDATE_TODO'),
|
|
||||||
// withLatestFrom(this.s.store),
|
|
||||||
// pessimisticUpdate({
|
|
||||||
// run: (a, state) => ({
|
|
||||||
// type: 'TODO_UPDATED',
|
|
||||||
// payload: { user: state.user, newTitle: a.payload.newTitle },
|
|
||||||
// }),
|
|
||||||
// onError: () => null,
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// constructor(private s: DataPersistence<TodosState>) {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// function userReducer() {
|
|
||||||
// return 'bob';
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let actions: Observable<any>;
|
|
||||||
//
|
|
||||||
// beforeEach(() => {
|
|
||||||
// actions = new Subject<any>();
|
|
||||||
// TestBed.configureTestingModule({
|
|
||||||
// providers: [TodoEffects, provideMockActions(() => actions)],
|
|
||||||
// imports: [
|
|
||||||
// StoreModule.forRoot(
|
|
||||||
// { user: userReducer },
|
|
||||||
// {
|
|
||||||
// runtimeChecks: {
|
|
||||||
// strictStateImmutability: false,
|
|
||||||
// strictStateSerializability: false,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work', async (done) => {
|
|
||||||
// actions = of({
|
|
||||||
// type: 'UPDATE_TODO',
|
|
||||||
// payload: { newTitle: 'newTitle' },
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// expect(await readAll(TestBed.get(TodoEffects).loadTodo)).toEqual([
|
|
||||||
// {
|
|
||||||
// type: 'TODO_UPDATED',
|
|
||||||
// payload: { user: 'bob', newTitle: 'newTitle' },
|
|
||||||
// },
|
|
||||||
// ]);
|
|
||||||
//
|
|
||||||
// done();
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work with an operator', async (done) => {
|
|
||||||
// actions = of({
|
|
||||||
// type: 'UPDATE_TODO',
|
|
||||||
// payload: { newTitle: 'newTitle' },
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// expect(
|
|
||||||
// await readAll(TestBed.get(TodoEffects).loadTodoWithOperator)
|
|
||||||
// ).toEqual([
|
|
||||||
// {
|
|
||||||
// type: 'TODO_UPDATED',
|
|
||||||
// payload: { user: 'bob', newTitle: 'newTitle' },
|
|
||||||
// },
|
|
||||||
// ]);
|
|
||||||
//
|
|
||||||
// done();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('`run` throws an error', () => {
|
|
||||||
// @Injectable()
|
|
||||||
// class TodoEffects {
|
|
||||||
// @Effect()
|
|
||||||
// loadTodo = this.s.pessimisticUpdate<UpdateTodo>('UPDATE_TODO', {
|
|
||||||
// run: () => {
|
|
||||||
// throw new Error('boom');
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// onError: (a, e: any) => ({
|
|
||||||
// type: 'ERROR',
|
|
||||||
// payload: { error: e },
|
|
||||||
// }),
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// constructor(private s: DataPersistence<TodosState>) {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// function userReducer() {
|
|
||||||
// return 'bob';
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let actions: Observable<any>;
|
|
||||||
//
|
|
||||||
// beforeEach(() => {
|
|
||||||
// actions = new Subject<any>();
|
|
||||||
// TestBed.configureTestingModule({
|
|
||||||
// providers: [TodoEffects, provideMockActions(() => actions)],
|
|
||||||
// imports: [
|
|
||||||
// StoreModule.forRoot(
|
|
||||||
// { user: userReducer },
|
|
||||||
// {
|
|
||||||
// runtimeChecks: {
|
|
||||||
// strictStateImmutability: false,
|
|
||||||
// strictStateSerializability: false,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work', async (done) => {
|
|
||||||
// actions = of({
|
|
||||||
// type: 'UPDATE_TODO',
|
|
||||||
// payload: { newTitle: 'newTitle' },
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// const [a]: any = await readAll(TestBed.get(TodoEffects).loadTodo);
|
|
||||||
//
|
|
||||||
// expect(a.type).toEqual('ERROR');
|
|
||||||
// expect(a.payload.error.message).toEqual('boom');
|
|
||||||
//
|
|
||||||
// done();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('`run` returns an observable that errors', () => {
|
|
||||||
// @Injectable()
|
|
||||||
// class TodoEffects {
|
|
||||||
// @Effect()
|
|
||||||
// loadTodo = this.s.pessimisticUpdate<UpdateTodo>('UPDATE_TODO', {
|
|
||||||
// run: () => {
|
|
||||||
// return throwError('boom');
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// onError: (a, e: any) => ({
|
|
||||||
// type: 'ERROR',
|
|
||||||
// payload: { error: e },
|
|
||||||
// }),
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// constructor(private s: DataPersistence<TodosState>) {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// function userReducer() {
|
|
||||||
// return 'bob';
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let actions: Observable<any>;
|
|
||||||
//
|
|
||||||
// beforeEach(() => {
|
|
||||||
// actions = new Subject<any>();
|
|
||||||
// TestBed.configureTestingModule({
|
|
||||||
// providers: [TodoEffects, provideMockActions(() => actions)],
|
|
||||||
// imports: [
|
|
||||||
// StoreModule.forRoot(
|
|
||||||
// { user: userReducer },
|
|
||||||
// {
|
|
||||||
// runtimeChecks: {
|
|
||||||
// strictStateImmutability: false,
|
|
||||||
// strictStateSerializability: false,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work', async (done) => {
|
|
||||||
// actions = of({
|
|
||||||
// type: 'UPDATE_TODO',
|
|
||||||
// payload: { newTitle: 'newTitle' },
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// const [a]: any = await readAll(TestBed.get(TodoEffects).loadTodo);
|
|
||||||
//
|
|
||||||
// expect(a.type).toEqual('ERROR');
|
|
||||||
// expect(a.payload.error).toEqual('boom');
|
|
||||||
//
|
|
||||||
// done();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('optimisticUpdate', () => {
|
|
||||||
// beforeEach(() => {
|
|
||||||
// TestBed.configureTestingModule({ providers: [DataPersistence] });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('`run` throws an error', () => {
|
|
||||||
// @Injectable()
|
|
||||||
// class TodoEffects {
|
|
||||||
// @Effect()
|
|
||||||
// loadTodo = this.s.optimisticUpdate<UpdateTodo>('UPDATE_TODO', {
|
|
||||||
// run: () => {
|
|
||||||
// throw new Error('boom');
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// undoAction: (a) => ({
|
|
||||||
// type: 'UNDO_UPDATE_TODO',
|
|
||||||
// payload: a.payload,
|
|
||||||
// }),
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// @Effect()
|
|
||||||
// loadTodoWithOperator = this.s.actions.pipe(
|
|
||||||
// ofType<UpdateTodo>('UPDATE_TODO'),
|
|
||||||
// withLatestFrom(this.s.store),
|
|
||||||
// optimisticUpdate({
|
|
||||||
// run: () => {
|
|
||||||
// throw new Error('boom');
|
|
||||||
// },
|
|
||||||
//
|
|
||||||
// undoAction: (a) => ({
|
|
||||||
// type: 'UNDO_UPDATE_TODO',
|
|
||||||
// payload: a.payload,
|
|
||||||
// }),
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// constructor(private s: DataPersistence<TodosState>) {}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// function userReducer() {
|
|
||||||
// return 'bob';
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let actions: Observable<any>;
|
|
||||||
//
|
|
||||||
// beforeEach(() => {
|
|
||||||
// actions = new Subject<any>();
|
|
||||||
// TestBed.configureTestingModule({
|
|
||||||
// providers: [TodoEffects, provideMockActions(() => actions)],
|
|
||||||
// imports: [
|
|
||||||
// StoreModule.forRoot(
|
|
||||||
// { user: userReducer },
|
|
||||||
// {
|
|
||||||
// runtimeChecks: {
|
|
||||||
// strictStateImmutability: false,
|
|
||||||
// strictStateSerializability: false,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work', async (done) => {
|
|
||||||
// actions = of({
|
|
||||||
// type: 'UPDATE_TODO',
|
|
||||||
// payload: { newTitle: 'newTitle' },
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// const [a]: any = await readAll(TestBed.get(TodoEffects).loadTodo);
|
|
||||||
//
|
|
||||||
// expect(a.type).toEqual('UNDO_UPDATE_TODO');
|
|
||||||
// expect(a.payload.newTitle).toEqual('newTitle');
|
|
||||||
//
|
|
||||||
// done();
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should work with an operator', async (done) => {
|
|
||||||
// actions = of({
|
|
||||||
// type: 'UPDATE_TODO',
|
|
||||||
// payload: { newTitle: 'newTitle' },
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// const [a]: any = await readAll(
|
|
||||||
// TestBed.get(TodoEffects).loadTodoWithOperator
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// expect(a.type).toEqual('UNDO_UPDATE_TODO');
|
|
||||||
// expect(a.payload.newTitle).toEqual('newTitle');
|
|
||||||
//
|
|
||||||
// done();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
import { from } from 'rxjs';
|
|
||||||
import { readAll, readFirst } from '../testing/src/testing-utils';
|
|
||||||
|
|
||||||
describe('TestingUtils', () => {
|
|
||||||
describe('readAll', () => {
|
|
||||||
it('should transform Observable<T> to Promise<Array<T>>', async () => {
|
|
||||||
const obs = from([1, 2, 3]);
|
|
||||||
const result = await readAll(obs);
|
|
||||||
|
|
||||||
expect(result).toEqual([1, 2, 3]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('readFirst', () => {
|
|
||||||
it('should transform first item emitted from Observable<T> to Promise<T>', async () => {
|
|
||||||
const obs = from([1, 2, 3]);
|
|
||||||
const result = await readFirst(obs);
|
|
||||||
|
|
||||||
expect(result).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -25,6 +25,9 @@ interface BaseSchema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SchemaWithBrowserTarget = BaseSchema & {
|
export type SchemaWithBrowserTarget = BaseSchema & {
|
||||||
|
/**
|
||||||
|
* @deprecated Use `buildTarget` instead. It will be removed when Angular v20 is released.
|
||||||
|
*/
|
||||||
browserTarget: string;
|
browserTarget: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "A browser builder target to serve in the format of `project:target[:configuration]`. Ignored if `buildTarget` is set.",
|
"description": "A browser builder target to serve in the format of `project:target[:configuration]`. Ignored if `buildTarget` is set.",
|
||||||
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
|
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
|
||||||
"x-deprecated": "Use 'buildTarget' instead. It will be removed in Nx v20."
|
"x-deprecated": "Use 'buildTarget' instead. It will be removed when Angular v20 is released."
|
||||||
},
|
},
|
||||||
"buildTarget": {
|
"buildTarget": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@ -7,9 +7,4 @@ export type BrowserBuilderSchema = Schema & {
|
|||||||
indexHtmlTransformer?: string;
|
indexHtmlTransformer?: string;
|
||||||
buildLibsFromSource?: boolean;
|
buildLibsFromSource?: boolean;
|
||||||
watchDependencies?: boolean;
|
watchDependencies?: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use `indexHtmlTransformer` instead. It will be removed in Nx 21.
|
|
||||||
*/
|
|
||||||
indexFileTransformer?: string;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -57,7 +57,6 @@ export function executeWebpackBrowserBuilder(
|
|||||||
buildLibsFromSource,
|
buildLibsFromSource,
|
||||||
customWebpackConfig,
|
customWebpackConfig,
|
||||||
indexHtmlTransformer,
|
indexHtmlTransformer,
|
||||||
indexFileTransformer,
|
|
||||||
watchDependencies,
|
watchDependencies,
|
||||||
...delegateBuilderOptions
|
...delegateBuilderOptions
|
||||||
} = options;
|
} = options;
|
||||||
@ -74,11 +73,9 @@ export function executeWebpackBrowserBuilder(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedIndexHtmlTransformer =
|
|
||||||
indexHtmlTransformer ?? indexFileTransformer;
|
|
||||||
const pathToIndexFileTransformer =
|
const pathToIndexFileTransformer =
|
||||||
normalizedIndexHtmlTransformer &&
|
indexHtmlTransformer &&
|
||||||
joinPathFragments(context.workspaceRoot, normalizedIndexHtmlTransformer);
|
joinPathFragments(context.workspaceRoot, indexHtmlTransformer);
|
||||||
if (pathToIndexFileTransformer && !existsSync(pathToIndexFileTransformer)) {
|
if (pathToIndexFileTransformer && !existsSync(pathToIndexFileTransformer)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`File containing Index File Transformer function Not Found!\n Please ensure the path to the file containing the function is correct: \n${pathToIndexFileTransformer}`
|
`File containing Index File Transformer function Not Found!\n Please ensure the path to the file containing the function is correct: \n${pathToIndexFileTransformer}`
|
||||||
|
|||||||
@ -29,6 +29,9 @@ interface BaseSchema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SchemaWithBrowserTarget = BaseSchema & {
|
export type SchemaWithBrowserTarget = BaseSchema & {
|
||||||
|
/**
|
||||||
|
* @deprecated Use `buildTarget` instead. It will be removed when Angular v20 is released.
|
||||||
|
*/
|
||||||
browserTarget: string;
|
browserTarget: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "A browser builder target to serve in the format of `project:target[:configuration]`.",
|
"description": "A browser builder target to serve in the format of `project:target[:configuration]`.",
|
||||||
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
|
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
|
||||||
"x-deprecated": "Use 'buildTarget' instead. It will be removed in Nx v20."
|
"x-deprecated": "Use 'buildTarget' instead. It will be removed when Angular v20 is released."
|
||||||
},
|
},
|
||||||
"buildTarget": {
|
"buildTarget": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@ -9,7 +9,7 @@ type EslintExtensionSchema = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use tools from `@nx/eslint/src/generators/utils/eslint-file` instead
|
* @deprecated Use tools from `@nx/eslint/src/generators/utils/eslint-file` instead. It will be removed in Nx v22.
|
||||||
*/
|
*/
|
||||||
export const extendAngularEslintJson = (
|
export const extendAngularEslintJson = (
|
||||||
json: Linter.Config,
|
json: Linter.Config,
|
||||||
@ -61,7 +61,7 @@ export const extendAngularEslintJson = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link extendAngularEslintJson} instead
|
* @deprecated Use {@link extendAngularEslintJson} instead. It will be removed in Nx v22.
|
||||||
*/
|
*/
|
||||||
export function createEsLintConfiguration(
|
export function createEsLintConfiguration(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
|
|||||||
@ -2,6 +2,9 @@ import type { Tree } from '@nx/devkit';
|
|||||||
import { moveGenerator } from '@nx/workspace/src/generators/move/move';
|
import { moveGenerator } from '@nx/workspace/src/generators/move/move';
|
||||||
import type { Schema } from './schema';
|
import type { Schema } from './schema';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use the `@nx/workspace:move` generator instead. It will be removed in Nx v22.
|
||||||
|
*/
|
||||||
export async function angularMoveGenerator(
|
export async function angularMoveGenerator(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
schema: Schema
|
schema: Schema
|
||||||
|
|||||||
@ -10,6 +10,9 @@ import {
|
|||||||
} from './lib';
|
} from './lib';
|
||||||
import type { NgRxGeneratorOptions } from './schema';
|
import type { NgRxGeneratorOptions } from './schema';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use the 'ngrx-root-store' and 'ngrx-feature-store' generators instead. It will be removed in Nx v22.
|
||||||
|
*/
|
||||||
export async function ngrxGenerator(
|
export async function ngrxGenerator(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
schema: NgRxGeneratorOptions
|
schema: NgRxGeneratorOptions
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"$id": "NxNgrxGenerator",
|
"$id": "NxNgrxGenerator",
|
||||||
"title": "Add NgRx support to an application or library.",
|
"title": "Add NgRx support to an application or library.",
|
||||||
"description": "Adds NgRx support to an application or library.",
|
"description": "Adds NgRx support to an application or library.",
|
||||||
"x-deprecated": "This generator is deprecated and will be removed in a future version of Nx. Use the 'ngrx-root-store' and 'ngrx-feature-store' generators instead.",
|
"x-deprecated": "Use the 'ngrx-root-store' and 'ngrx-feature-store' generators instead. It will be removed in Nx v22.",
|
||||||
"cli": "nx",
|
"cli": "nx",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"examples": [
|
"examples": [
|
||||||
|
|||||||
@ -1,41 +0,0 @@
|
|||||||
import { Tree, readJson, updateJson } from '@nx/devkit';
|
|
||||||
import * as devkit from '@nx/devkit';
|
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
||||||
import replacePackage from './update-16-0-0-add-nx-packages';
|
|
||||||
|
|
||||||
describe('update-16-0-0-add-nx-packages', () => {
|
|
||||||
let tree: Tree;
|
|
||||||
beforeEach(() => {
|
|
||||||
tree = createTreeWithEmptyWorkspace();
|
|
||||||
jest
|
|
||||||
.spyOn(devkit, 'formatFiles')
|
|
||||||
.mockImplementation(() => Promise.resolve());
|
|
||||||
|
|
||||||
updateJson(tree, 'package.json', (json) => {
|
|
||||||
json.devDependencies['@nrwl/angular'] = '16.0.0';
|
|
||||||
return json;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove the dependency on @nrwl/angular', async () => {
|
|
||||||
await replacePackage(tree);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
readJson(tree, 'package.json').dependencies['@nrwl/angular']
|
|
||||||
).not.toBeDefined();
|
|
||||||
expect(
|
|
||||||
readJson(tree, 'package.json').devDependencies['@nrwl/angular']
|
|
||||||
).not.toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add a dependency on @nx/angular', async () => {
|
|
||||||
await replacePackage(tree);
|
|
||||||
|
|
||||||
const packageJson = readJson(tree, 'package.json');
|
|
||||||
const newDependencyVersion =
|
|
||||||
packageJson.devDependencies['@nx/angular'] ??
|
|
||||||
packageJson.dependencies['@nx/angular'];
|
|
||||||
|
|
||||||
expect(newDependencyVersion).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import { Tree, formatFiles } from '@nx/devkit';
|
|
||||||
import { replaceNrwlPackageWithNxPackage } from '@nx/devkit/src/utils/replace-package';
|
|
||||||
|
|
||||||
export default async function replacePackage(tree: Tree): Promise<void> {
|
|
||||||
await replaceNrwlPackageWithNxPackage(tree, '@nrwl/angular', '@nx/angular');
|
|
||||||
|
|
||||||
await formatFiles(tree);
|
|
||||||
}
|
|
||||||
@ -1,202 +0,0 @@
|
|||||||
import {
|
|
||||||
addProjectConfiguration,
|
|
||||||
readNxJson,
|
|
||||||
readProjectConfiguration,
|
|
||||||
updateNxJson,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import * as devkit from '@nx/devkit';
|
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
||||||
import removeKarmaDefaults from './remove-karma-defaults';
|
|
||||||
|
|
||||||
describe('removeKarmaDefaults', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest
|
|
||||||
.spyOn(devkit, 'formatFiles')
|
|
||||||
.mockImplementation(() => Promise.resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove karma as default unit test runner from nx.json when exists', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
nxJson.generators = {
|
|
||||||
'@nrwl/angular:application': {
|
|
||||||
unitTestRunner: 'karma',
|
|
||||||
},
|
|
||||||
'@nrwl/angular:library': {
|
|
||||||
unitTestRunner: 'karma',
|
|
||||||
},
|
|
||||||
'@nrwl/angular:host': {
|
|
||||||
unitTestRunner: 'karma',
|
|
||||||
},
|
|
||||||
'@nrwl/angular:remote': {
|
|
||||||
unitTestRunner: 'karma',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
updateNxJson(tree, nxJson);
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await removeKarmaDefaults(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(readNxJson(tree).generators).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"@nrwl/angular:application": {},
|
|
||||||
"@nrwl/angular:host": {},
|
|
||||||
"@nrwl/angular:library": {},
|
|
||||||
"@nrwl/angular:remote": {},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should only remove karma as default unit test runner from nx.json when set', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
nxJson.generators = {
|
|
||||||
'@nrwl/angular:application': {
|
|
||||||
style: 'scss',
|
|
||||||
unitTestRunner: 'karma',
|
|
||||||
},
|
|
||||||
'@nrwl/angular:library': {
|
|
||||||
unitTestRunner: 'jest',
|
|
||||||
},
|
|
||||||
'@nrwl/angular:host': {
|
|
||||||
style: 'scss',
|
|
||||||
unitTestRunner: 'karma',
|
|
||||||
},
|
|
||||||
'@nrwl/angular:remote': {
|
|
||||||
unitTestRunner: 'jest',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
updateNxJson(tree, nxJson);
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await removeKarmaDefaults(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(readNxJson(tree).generators).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"@nrwl/angular:application": {
|
|
||||||
"style": "scss",
|
|
||||||
},
|
|
||||||
"@nrwl/angular:host": {
|
|
||||||
"style": "scss",
|
|
||||||
},
|
|
||||||
"@nrwl/angular:library": {
|
|
||||||
"unitTestRunner": "jest",
|
|
||||||
},
|
|
||||||
"@nrwl/angular:remote": {
|
|
||||||
"unitTestRunner": "jest",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not remove karma as default unit test runner from unsupported generator', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
nxJson.generators = {
|
|
||||||
'@my/custom:plugin': {
|
|
||||||
style: 'scss',
|
|
||||||
unitTestRunner: 'karma',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
updateNxJson(tree, nxJson);
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await removeKarmaDefaults(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(readNxJson(tree).generators).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"@my/custom:plugin": {
|
|
||||||
"style": "scss",
|
|
||||||
"unitTestRunner": "karma",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove karma as default for project specific generators', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
addProjectConfiguration(tree, 'test', {
|
|
||||||
name: 'test',
|
|
||||||
root: '.',
|
|
||||||
sourceRoot: 'src',
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular:application': {
|
|
||||||
style: 'scss',
|
|
||||||
unitTestRunner: 'karma',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await removeKarmaDefaults(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(readProjectConfiguration(tree, 'test').generators)
|
|
||||||
.toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"@nrwl/angular:application": {
|
|
||||||
"style": "scss",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove karma when using nested generator default syntax', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
nxJson.generators = {
|
|
||||||
'@nrwl/angular:application': {
|
|
||||||
style: 'scss',
|
|
||||||
unitTestRunner: 'karma',
|
|
||||||
},
|
|
||||||
'@nrwl/angular:library': {
|
|
||||||
unitTestRunner: 'jest',
|
|
||||||
},
|
|
||||||
'@nrwl/angular': {
|
|
||||||
host: {
|
|
||||||
style: 'scss',
|
|
||||||
unitTestRunner: 'karma',
|
|
||||||
},
|
|
||||||
remote: {
|
|
||||||
unitTestRunner: 'jest',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
updateNxJson(tree, nxJson);
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await removeKarmaDefaults(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(readNxJson(tree).generators).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"@nrwl/angular": {
|
|
||||||
"host": {
|
|
||||||
"style": "scss",
|
|
||||||
},
|
|
||||||
"remote": {
|
|
||||||
"unitTestRunner": "jest",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"@nrwl/angular:application": {
|
|
||||||
"style": "scss",
|
|
||||||
},
|
|
||||||
"@nrwl/angular:library": {
|
|
||||||
"unitTestRunner": "jest",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
import type {
|
|
||||||
NxJsonConfiguration,
|
|
||||||
ProjectConfiguration,
|
|
||||||
Tree,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import {
|
|
||||||
formatFiles,
|
|
||||||
getProjects,
|
|
||||||
readNxJson,
|
|
||||||
updateNxJson,
|
|
||||||
updateProjectConfiguration,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
|
|
||||||
const GENERATORS = ['application', 'library', 'host', 'remote'];
|
|
||||||
const CANDIDATE_GENERATOR_COLLECTIONS = ['@nrwl/angular', '@nx/angular'];
|
|
||||||
|
|
||||||
export default async function removeKarmaDefaults(tree: Tree) {
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
|
|
||||||
if (nxJson.generators) {
|
|
||||||
const updatedConfig = updateUnitTestRunner(nxJson.generators);
|
|
||||||
|
|
||||||
if (updatedConfig) {
|
|
||||||
updateNxJson(tree, nxJson);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const projects = getProjects(tree);
|
|
||||||
|
|
||||||
for (const [projectName, projectConfig] of projects) {
|
|
||||||
if (projectConfig.generators) {
|
|
||||||
const updatedProject = updateUnitTestRunner(projectConfig.generators);
|
|
||||||
|
|
||||||
if (updatedProject) {
|
|
||||||
updateProjectConfiguration(tree, projectName, projectConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await formatFiles(tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateUnitTestRunner(
|
|
||||||
generatorsConfig:
|
|
||||||
| NxJsonConfiguration['generators']
|
|
||||||
| ProjectConfiguration['generators']
|
|
||||||
) {
|
|
||||||
const generators = Object.entries(generatorsConfig);
|
|
||||||
|
|
||||||
let updatedConfig = false;
|
|
||||||
for (const [generatorName, generatorDefaults] of generators) {
|
|
||||||
if (CANDIDATE_GENERATOR_COLLECTIONS.includes(generatorName)) {
|
|
||||||
for (const possibleGenerator of GENERATORS) {
|
|
||||||
if (
|
|
||||||
generatorDefaults[possibleGenerator] &&
|
|
||||||
generatorDefaults[possibleGenerator]['unitTestRunner'] &&
|
|
||||||
generatorDefaults[possibleGenerator]['unitTestRunner'] === 'karma'
|
|
||||||
) {
|
|
||||||
generatorsConfig[generatorName][possibleGenerator]['unitTestRunner'] =
|
|
||||||
undefined;
|
|
||||||
updatedConfig = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!GENERATORS.map((v) => `@nrwl/angular:${v}`).includes(generatorName) &&
|
|
||||||
!GENERATORS.map((v) => `@nx/angular:${v}`).includes(generatorName)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
generatorDefaults['unitTestRunner'] &&
|
|
||||||
generatorDefaults['unitTestRunner'] === 'karma'
|
|
||||||
) {
|
|
||||||
generatorsConfig[generatorName]['unitTestRunner'] = undefined;
|
|
||||||
updatedConfig = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedConfig;
|
|
||||||
}
|
|
||||||
@ -1,281 +0,0 @@
|
|||||||
import type { Tree } from '@nx/devkit';
|
|
||||||
import {
|
|
||||||
addProjectConfiguration,
|
|
||||||
readNxJson,
|
|
||||||
readProjectConfiguration,
|
|
||||||
updateNxJson,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import * as devkit from '@nx/devkit';
|
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
||||||
import removeLibraryGeneratorSimpleModuleNameOption from './remove-library-generator-simple-module-name-option';
|
|
||||||
|
|
||||||
describe('removeLibraryGeneratorSimpleModuleNameOption', () => {
|
|
||||||
let tree: Tree;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
tree = createTreeWithEmptyWorkspace();
|
|
||||||
jest
|
|
||||||
.spyOn(devkit, 'formatFiles')
|
|
||||||
.mockImplementation(() => Promise.resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('nx.json', () => {
|
|
||||||
it('should replace simpleModuleName with simpleName', async () => {
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
updateNxJson(tree, {
|
|
||||||
...nxJson,
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular:library': {
|
|
||||||
simpleModuleName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await removeLibraryGeneratorSimpleModuleNameOption(tree);
|
|
||||||
|
|
||||||
const updatedNxJson = readNxJson(tree);
|
|
||||||
expect(updatedNxJson.generators['@nrwl/angular:library']).toStrictEqual({
|
|
||||||
simpleName: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support nested library generator default', async () => {
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
updateNxJson(tree, {
|
|
||||||
...nxJson,
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular': {
|
|
||||||
library: {
|
|
||||||
simpleModuleName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await removeLibraryGeneratorSimpleModuleNameOption(tree);
|
|
||||||
|
|
||||||
const updatedNxJson = readNxJson(tree);
|
|
||||||
expect(updatedNxJson.generators['@nrwl/angular']).toStrictEqual({
|
|
||||||
library: {
|
|
||||||
simpleName: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should keep simpleName if defined and remove simpleModuleName', async () => {
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
updateNxJson(tree, {
|
|
||||||
...nxJson,
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular:library': {
|
|
||||||
simpleModuleName: true,
|
|
||||||
simpleName: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await removeLibraryGeneratorSimpleModuleNameOption(tree);
|
|
||||||
|
|
||||||
const updatedNxJson = readNxJson(tree);
|
|
||||||
expect(updatedNxJson.generators['@nrwl/angular:library']).toStrictEqual({
|
|
||||||
simpleName: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing if simpleModuleName is not set', async () => {
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
updateNxJson(tree, {
|
|
||||||
...nxJson,
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular:library': {
|
|
||||||
simpleName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await removeLibraryGeneratorSimpleModuleNameOption(tree);
|
|
||||||
|
|
||||||
const updatedNxJson = readNxJson(tree);
|
|
||||||
expect(updatedNxJson.generators['@nrwl/angular:library']).toStrictEqual({
|
|
||||||
simpleName: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw when library generator defaults are not set', async () => {
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
updateNxJson(tree, {
|
|
||||||
...nxJson,
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular:component': {
|
|
||||||
standalone: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
removeLibraryGeneratorSimpleModuleNameOption(tree)
|
|
||||||
).resolves.not.toThrow();
|
|
||||||
|
|
||||||
const updatedNxJson = readNxJson(tree);
|
|
||||||
expect(updatedNxJson.generators).toStrictEqual({
|
|
||||||
'@nrwl/angular:component': {
|
|
||||||
standalone: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw when generators defaults are not set', async () => {
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
updateNxJson(tree, { ...nxJson, generators: undefined });
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
removeLibraryGeneratorSimpleModuleNameOption(tree)
|
|
||||||
).resolves.not.toThrow();
|
|
||||||
|
|
||||||
const updatedNxJson = readNxJson(tree);
|
|
||||||
expect(updatedNxJson.generators).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw when nx.json does not exist', async () => {
|
|
||||||
tree.delete('nx.json');
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
removeLibraryGeneratorSimpleModuleNameOption(tree)
|
|
||||||
).resolves.not.toThrow();
|
|
||||||
|
|
||||||
expect(tree.exists('nx.json')).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('project configs', () => {
|
|
||||||
it('should replace simpleModuleName with simpleName', async () => {
|
|
||||||
const project = {
|
|
||||||
name: 'project',
|
|
||||||
root: '/',
|
|
||||||
targets: {},
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular:library': {
|
|
||||||
simpleModuleName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
addProjectConfiguration(tree, 'project', project);
|
|
||||||
|
|
||||||
await removeLibraryGeneratorSimpleModuleNameOption(tree);
|
|
||||||
|
|
||||||
const updatedProject = readProjectConfiguration(tree, 'project');
|
|
||||||
expect(updatedProject.generators['@nrwl/angular:library']).toStrictEqual({
|
|
||||||
simpleName: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support nested library generator default', async () => {
|
|
||||||
const project = {
|
|
||||||
name: 'project',
|
|
||||||
root: '/',
|
|
||||||
targets: {},
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular': {
|
|
||||||
library: {
|
|
||||||
simpleModuleName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
addProjectConfiguration(tree, 'project', project);
|
|
||||||
|
|
||||||
await removeLibraryGeneratorSimpleModuleNameOption(tree);
|
|
||||||
|
|
||||||
const updatedProject = readProjectConfiguration(tree, 'project');
|
|
||||||
expect(updatedProject.generators['@nrwl/angular']).toStrictEqual({
|
|
||||||
library: {
|
|
||||||
simpleName: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should keep simpleName if defined and remove simpleModuleName', async () => {
|
|
||||||
const project = {
|
|
||||||
name: 'project',
|
|
||||||
root: '/',
|
|
||||||
targets: {},
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular:library': {
|
|
||||||
simpleModuleName: true,
|
|
||||||
simpleName: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
addProjectConfiguration(tree, 'project', project);
|
|
||||||
|
|
||||||
await removeLibraryGeneratorSimpleModuleNameOption(tree);
|
|
||||||
|
|
||||||
const updatedProject = readProjectConfiguration(tree, 'project');
|
|
||||||
expect(updatedProject.generators['@nrwl/angular:library']).toStrictEqual({
|
|
||||||
simpleName: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing if simpleModuleName is not set', async () => {
|
|
||||||
const project = {
|
|
||||||
name: 'project',
|
|
||||||
root: '/',
|
|
||||||
targets: {},
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular:library': {
|
|
||||||
simpleName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
addProjectConfiguration(tree, 'project', project);
|
|
||||||
|
|
||||||
await removeLibraryGeneratorSimpleModuleNameOption(tree);
|
|
||||||
|
|
||||||
const updatedProject = readProjectConfiguration(tree, 'project');
|
|
||||||
expect(updatedProject.generators['@nrwl/angular:library']).toStrictEqual({
|
|
||||||
simpleName: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw when library generator defaults are not set', async () => {
|
|
||||||
const project = {
|
|
||||||
name: 'project',
|
|
||||||
root: '/',
|
|
||||||
targets: {},
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular:component': {
|
|
||||||
standalone: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
addProjectConfiguration(tree, 'project', project);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
removeLibraryGeneratorSimpleModuleNameOption(tree)
|
|
||||||
).resolves.not.toThrow();
|
|
||||||
|
|
||||||
const updatedProject = readProjectConfiguration(tree, 'project');
|
|
||||||
expect(updatedProject.generators).toStrictEqual({
|
|
||||||
'@nrwl/angular:component': {
|
|
||||||
standalone: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw when generators defaults are not set', async () => {
|
|
||||||
const project = {
|
|
||||||
name: 'project',
|
|
||||||
root: '/',
|
|
||||||
targets: {},
|
|
||||||
};
|
|
||||||
addProjectConfiguration(tree, 'project', project);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
removeLibraryGeneratorSimpleModuleNameOption(tree)
|
|
||||||
).resolves.not.toThrow();
|
|
||||||
|
|
||||||
const updatedProject = readProjectConfiguration(tree, 'project');
|
|
||||||
expect(updatedProject.generators).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
import type {
|
|
||||||
NxJsonConfiguration,
|
|
||||||
ProjectConfiguration,
|
|
||||||
Tree,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import {
|
|
||||||
formatFiles,
|
|
||||||
getProjects,
|
|
||||||
readNxJson,
|
|
||||||
updateNxJson,
|
|
||||||
updateProjectConfiguration,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
|
|
||||||
export default async function removeLibraryGeneratorSimpleModuleNameOption(
|
|
||||||
tree: Tree
|
|
||||||
): Promise<void> {
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
|
|
||||||
// update global config
|
|
||||||
const nxJsonUpdated = replaceSimpleModuleNameInConfig(nxJson);
|
|
||||||
if (nxJsonUpdated) {
|
|
||||||
updateNxJson(tree, nxJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
// update project configs
|
|
||||||
const projects = getProjects(tree);
|
|
||||||
for (const [name, project] of projects) {
|
|
||||||
const projectUpdated = replaceSimpleModuleNameInConfig(project);
|
|
||||||
if (projectUpdated) {
|
|
||||||
updateProjectConfiguration(tree, name, project);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await formatFiles(tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceSimpleModuleNameInConfig(
|
|
||||||
configObject: {
|
|
||||||
generators?:
|
|
||||||
| NxJsonConfiguration['generators']
|
|
||||||
| ProjectConfiguration['generators'];
|
|
||||||
} | null
|
|
||||||
): boolean {
|
|
||||||
if (!configObject?.generators) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let updated = false;
|
|
||||||
|
|
||||||
if (configObject.generators['@nrwl/angular']?.['library']?.simpleModuleName) {
|
|
||||||
configObject.generators['@nrwl/angular']['library'].simpleName ??=
|
|
||||||
configObject.generators['@nrwl/angular']['library'].simpleModuleName;
|
|
||||||
delete configObject.generators['@nrwl/angular']['library'].simpleModuleName;
|
|
||||||
updated = true;
|
|
||||||
} else if (
|
|
||||||
configObject.generators['@nrwl/angular:library']?.simpleModuleName
|
|
||||||
) {
|
|
||||||
configObject.generators['@nrwl/angular:library'].simpleName ??=
|
|
||||||
configObject.generators['@nrwl/angular:library'].simpleModuleName;
|
|
||||||
delete configObject.generators['@nrwl/angular:library'].simpleModuleName;
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return updated;
|
|
||||||
}
|
|
||||||
@ -1,186 +0,0 @@
|
|||||||
import {
|
|
||||||
addProjectConfiguration,
|
|
||||||
readNxJson,
|
|
||||||
readProjectConfiguration,
|
|
||||||
updateNxJson,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import * as devkit from '@nx/devkit';
|
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
||||||
import removeProtractorDefaults from './remove-protractor-defaults';
|
|
||||||
|
|
||||||
describe('removeProtractorDefaults', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest
|
|
||||||
.spyOn(devkit, 'formatFiles')
|
|
||||||
.mockImplementation(() => Promise.resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove protractor as default unit test runner from nx.json when exists', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
nxJson.generators = {
|
|
||||||
'@nrwl/angular:application': {
|
|
||||||
e2eTestRunner: 'protractor',
|
|
||||||
},
|
|
||||||
'@nrwl/angular:host': {
|
|
||||||
e2eTestRunner: 'protractor',
|
|
||||||
},
|
|
||||||
'@nrwl/angular:remote': {
|
|
||||||
e2eTestRunner: 'protractor',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
updateNxJson(tree, nxJson);
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await removeProtractorDefaults(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(readNxJson(tree).generators).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"@nrwl/angular:application": {},
|
|
||||||
"@nrwl/angular:host": {},
|
|
||||||
"@nrwl/angular:remote": {},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should only remove protractor as default unit test runner from nx.json when set', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
nxJson.generators = {
|
|
||||||
'@nrwl/angular:application': {
|
|
||||||
style: 'scss',
|
|
||||||
e2eTestRunner: 'protractor',
|
|
||||||
},
|
|
||||||
'@nrwl/angular:host': {
|
|
||||||
style: 'scss',
|
|
||||||
e2eTestRunner: 'protractor',
|
|
||||||
},
|
|
||||||
'@nrwl/angular:remote': {
|
|
||||||
e2eTestRunner: 'cypress',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
updateNxJson(tree, nxJson);
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await removeProtractorDefaults(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(readNxJson(tree).generators).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"@nrwl/angular:application": {
|
|
||||||
"style": "scss",
|
|
||||||
},
|
|
||||||
"@nrwl/angular:host": {
|
|
||||||
"style": "scss",
|
|
||||||
},
|
|
||||||
"@nrwl/angular:remote": {
|
|
||||||
"e2eTestRunner": "cypress",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not remove protractor as default e2e test runner from unsupported generator', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
nxJson.generators = {
|
|
||||||
'@my/custom:plugin': {
|
|
||||||
style: 'scss',
|
|
||||||
e2eTestRunner: 'protractor',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
updateNxJson(tree, nxJson);
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await removeProtractorDefaults(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(readNxJson(tree).generators).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"@my/custom:plugin": {
|
|
||||||
"e2eTestRunner": "protractor",
|
|
||||||
"style": "scss",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove protractor as default for project specific generators', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
addProjectConfiguration(tree, 'test', {
|
|
||||||
name: 'test',
|
|
||||||
root: '.',
|
|
||||||
sourceRoot: 'src',
|
|
||||||
generators: {
|
|
||||||
'@nrwl/angular:application': {
|
|
||||||
style: 'scss',
|
|
||||||
e2eTestRunner: 'protractor',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await removeProtractorDefaults(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(readProjectConfiguration(tree, 'test').generators)
|
|
||||||
.toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"@nrwl/angular:application": {
|
|
||||||
"style": "scss",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove protractor when using nested generator default syntax', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
nxJson.generators = {
|
|
||||||
'@nrwl/angular:application': {
|
|
||||||
style: 'scss',
|
|
||||||
e2eTestRunner: 'protractor',
|
|
||||||
},
|
|
||||||
'@nrwl/angular': {
|
|
||||||
host: {
|
|
||||||
style: 'scss',
|
|
||||||
e2eTestRunner: 'protractor',
|
|
||||||
},
|
|
||||||
remote: {
|
|
||||||
e2eTestRunner: 'cypress',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
updateNxJson(tree, nxJson);
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await removeProtractorDefaults(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(readNxJson(tree).generators).toMatchInlineSnapshot(`
|
|
||||||
{
|
|
||||||
"@nrwl/angular": {
|
|
||||||
"host": {
|
|
||||||
"style": "scss",
|
|
||||||
},
|
|
||||||
"remote": {
|
|
||||||
"e2eTestRunner": "cypress",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"@nrwl/angular:application": {
|
|
||||||
"style": "scss",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
import type {
|
|
||||||
NxJsonConfiguration,
|
|
||||||
ProjectConfiguration,
|
|
||||||
Tree,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import {
|
|
||||||
formatFiles,
|
|
||||||
getProjects,
|
|
||||||
readNxJson,
|
|
||||||
updateNxJson,
|
|
||||||
updateProjectConfiguration,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
|
|
||||||
const GENERATORS = ['application', 'host', 'remote'];
|
|
||||||
const CANDIDATE_GENERATOR_COLLECTIONS = ['@nrwl/angular', '@nx/angular'];
|
|
||||||
|
|
||||||
export default async function removeProtractorDefaults(tree: Tree) {
|
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
|
|
||||||
if (nxJson.generators) {
|
|
||||||
const updatedConfig = updateE2ETestRunner(nxJson.generators);
|
|
||||||
|
|
||||||
if (updatedConfig) {
|
|
||||||
updateNxJson(tree, nxJson);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const projects = getProjects(tree);
|
|
||||||
|
|
||||||
for (const [projectName, projectConfig] of projects) {
|
|
||||||
if (projectConfig.generators) {
|
|
||||||
const updatedProject = updateE2ETestRunner(projectConfig.generators);
|
|
||||||
|
|
||||||
if (updatedProject) {
|
|
||||||
updateProjectConfiguration(tree, projectName, projectConfig);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await formatFiles(tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateE2ETestRunner(
|
|
||||||
generatorsConfig:
|
|
||||||
| NxJsonConfiguration['generators']
|
|
||||||
| ProjectConfiguration['generators']
|
|
||||||
) {
|
|
||||||
const generators = Object.entries(generatorsConfig);
|
|
||||||
|
|
||||||
let updatedConfig = false;
|
|
||||||
for (const [generatorName, generatorDefaults] of generators) {
|
|
||||||
if (CANDIDATE_GENERATOR_COLLECTIONS.includes(generatorName)) {
|
|
||||||
for (const possibleGenerator of GENERATORS) {
|
|
||||||
if (
|
|
||||||
generatorDefaults[possibleGenerator] &&
|
|
||||||
generatorDefaults[possibleGenerator]['e2eTestRunner'] &&
|
|
||||||
generatorDefaults[possibleGenerator]['e2eTestRunner'] === 'protractor'
|
|
||||||
) {
|
|
||||||
generatorsConfig[generatorName][possibleGenerator]['e2eTestRunner'] =
|
|
||||||
undefined;
|
|
||||||
updatedConfig = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!GENERATORS.map((v) => `@nrwl/angular:${v}`).includes(generatorName) &&
|
|
||||||
!GENERATORS.map((v) => `@nx/angular:${v}`).includes(generatorName)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
generatorDefaults['e2eTestRunner'] &&
|
|
||||||
generatorDefaults['e2eTestRunner'] === 'protractor'
|
|
||||||
) {
|
|
||||||
generatorsConfig[generatorName]['e2eTestRunner'] = undefined;
|
|
||||||
updatedConfig = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return updatedConfig;
|
|
||||||
}
|
|
||||||
@ -1,196 +0,0 @@
|
|||||||
import {
|
|
||||||
ProjectConfiguration,
|
|
||||||
ProjectGraph,
|
|
||||||
Tree,
|
|
||||||
addProjectConfiguration,
|
|
||||||
formatFiles,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
||||||
import extractStandaloneConfig from './extract-standalone-config-from-bootstrap';
|
|
||||||
|
|
||||||
let projectGraph: ProjectGraph;
|
|
||||||
jest.mock('@nx/devkit', () => ({
|
|
||||||
...jest.requireActual('@nx/devkit'),
|
|
||||||
createProjectGraphAsync: () => Promise.resolve(projectGraph),
|
|
||||||
formatFiles: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const TEST_MAIN_FILE = `import { bootstrapApplication } from '@angular/platform-browser';
|
|
||||||
import {
|
|
||||||
provideRouter,
|
|
||||||
withEnabledBlockingInitialNavigation,
|
|
||||||
} from '@angular/router';
|
|
||||||
import { appRoutes } from './app/app.routes';
|
|
||||||
import { AppComponent } from './app/app.component';
|
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, {
|
|
||||||
providers: [provideRouter(appRoutes, withEnabledBlockingInitialNavigation())],
|
|
||||||
}).catch((err) => console.error(err));`;
|
|
||||||
|
|
||||||
describe('extractStandaloneConfigFromBootstrap', () => {
|
|
||||||
it('should extract the config correctly from a standard main.ts file', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
addProject(
|
|
||||||
tree,
|
|
||||||
'app1',
|
|
||||||
{
|
|
||||||
name: 'app1',
|
|
||||||
root: 'apps/app1',
|
|
||||||
sourceRoot: 'apps/app1/src',
|
|
||||||
projectType: 'application',
|
|
||||||
targets: {
|
|
||||||
build: {
|
|
||||||
options: { main: 'apps/app1/src/main.ts' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
['npm:@angular/core']
|
|
||||||
);
|
|
||||||
|
|
||||||
tree.write('apps/app1/src/main.ts', TEST_MAIN_FILE);
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await extractStandaloneConfig(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(tree.read('apps/app1/src/main.ts', 'utf-8')).toMatchInlineSnapshot(`
|
|
||||||
"import { appConfig } from './app/app.config';
|
|
||||||
import { bootstrapApplication } from '@angular/platform-browser';
|
|
||||||
|
|
||||||
|
|
||||||
import { AppComponent } from './app/app.component';
|
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));"
|
|
||||||
`);
|
|
||||||
expect(tree.exists('apps/app1/src/app/app.config.ts')).toBeTruthy();
|
|
||||||
expect(tree.read('apps/app1/src/app/app.config.ts', 'utf-8'))
|
|
||||||
.toMatchInlineSnapshot(`
|
|
||||||
"import { ApplicationConfig } from '@angular/core';import {
|
|
||||||
provideRouter,
|
|
||||||
withEnabledBlockingInitialNavigation,
|
|
||||||
} from '@angular/router';
|
|
||||||
import { appRoutes } from './app.routes';
|
|
||||||
export const appConfig: ApplicationConfig = {
|
|
||||||
providers: [provideRouter(appRoutes, withEnabledBlockingInitialNavigation())],
|
|
||||||
}"
|
|
||||||
`);
|
|
||||||
expect(formatFiles).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should extract the config correctly when the main.ts imports bootstrap from bootstrap.ts file', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
addProject(
|
|
||||||
tree,
|
|
||||||
'app1',
|
|
||||||
{
|
|
||||||
name: 'app1',
|
|
||||||
root: 'apps/app1',
|
|
||||||
sourceRoot: 'apps/app1/src',
|
|
||||||
projectType: 'application',
|
|
||||||
targets: {
|
|
||||||
build: {
|
|
||||||
options: { main: 'apps/app1/src/main.ts' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
['npm:@angular/core']
|
|
||||||
);
|
|
||||||
|
|
||||||
tree.write('apps/app1/src/main.ts', `import('./bootstrap');`);
|
|
||||||
tree.write('apps/app1/src/bootstrap.ts', TEST_MAIN_FILE);
|
|
||||||
|
|
||||||
// ACT
|
|
||||||
await extractStandaloneConfig(tree);
|
|
||||||
|
|
||||||
// ASSERT
|
|
||||||
expect(tree.read('apps/app1/src/main.ts', 'utf-8')).toMatchInlineSnapshot(
|
|
||||||
`"import('./bootstrap');"`
|
|
||||||
);
|
|
||||||
expect(tree.read('apps/app1/src/bootstrap.ts', 'utf-8'))
|
|
||||||
.toMatchInlineSnapshot(`
|
|
||||||
"import { appConfig } from './app/app.config';
|
|
||||||
import { bootstrapApplication } from '@angular/platform-browser';
|
|
||||||
|
|
||||||
|
|
||||||
import { AppComponent } from './app/app.component';
|
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));"
|
|
||||||
`);
|
|
||||||
expect(tree.exists('apps/app1/src/app/app.config.ts')).toBeTruthy();
|
|
||||||
expect(tree.read('apps/app1/src/app/app.config.ts', 'utf-8'))
|
|
||||||
.toMatchInlineSnapshot(`
|
|
||||||
"import { ApplicationConfig } from '@angular/core';import {
|
|
||||||
provideRouter,
|
|
||||||
withEnabledBlockingInitialNavigation,
|
|
||||||
} from '@angular/router';
|
|
||||||
import { appRoutes } from './app.routes';
|
|
||||||
export const appConfig: ApplicationConfig = {
|
|
||||||
providers: [provideRouter(appRoutes, withEnabledBlockingInitialNavigation())],
|
|
||||||
}"
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw with non-angular projects', async () => {
|
|
||||||
// ARRANGE
|
|
||||||
const tree = createTreeWithEmptyWorkspace();
|
|
||||||
addProject(
|
|
||||||
tree,
|
|
||||||
'app1',
|
|
||||||
{
|
|
||||||
name: 'app1',
|
|
||||||
projectType: 'application',
|
|
||||||
root: 'apps/app1',
|
|
||||||
sourceRoot: 'apps/app1',
|
|
||||||
targets: {
|
|
||||||
build: {
|
|
||||||
executor: '@nx-go/nx-go:build',
|
|
||||||
options: { main: 'apps/app1' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
tree.write(
|
|
||||||
'apps/app1/main.go',
|
|
||||||
`package main
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func Hello(name string) string {
|
|
||||||
result := "Hello " + name
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println(Hello("app1"))
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// ACT && ASSERT
|
|
||||||
await expect(extractStandaloneConfig(tree)).resolves.not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function addProject(
|
|
||||||
tree: Tree,
|
|
||||||
projectName: string,
|
|
||||||
config: ProjectConfiguration,
|
|
||||||
dependencies: string[]
|
|
||||||
): void {
|
|
||||||
projectGraph = {
|
|
||||||
dependencies: {
|
|
||||||
[projectName]: dependencies.map((d) => ({
|
|
||||||
source: projectName,
|
|
||||||
target: d,
|
|
||||||
type: 'static',
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
nodes: {
|
|
||||||
[projectName]: { data: config, name: projectName, type: 'app' },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
addProjectConfiguration(tree, projectName, config);
|
|
||||||
}
|
|
||||||
@ -1,245 +0,0 @@
|
|||||||
import type { ProjectConfiguration, Tree } from '@nx/devkit';
|
|
||||||
import { formatFiles, joinPathFragments } from '@nx/devkit';
|
|
||||||
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
|
||||||
import { dirname, relative, resolve } from 'path';
|
|
||||||
import type { Identifier, Node, SourceFile, StringLiteral } from 'typescript';
|
|
||||||
import { getProjectsFilteredByDependencies } from '../utils/projects';
|
|
||||||
|
|
||||||
let tsModule: typeof import('typescript');
|
|
||||||
let tsquery: typeof import('@phenomnomnominal/tsquery').tsquery;
|
|
||||||
|
|
||||||
function getBootstrapCallFileInfo(
|
|
||||||
tree: Tree,
|
|
||||||
project: ProjectConfiguration,
|
|
||||||
mainFilePath: string
|
|
||||||
) {
|
|
||||||
const IMPORT_BOOTSTRAP_FILE =
|
|
||||||
'CallExpression:has(ImportKeyword) > StringLiteral';
|
|
||||||
|
|
||||||
let bootstrapCallFilePath = mainFilePath;
|
|
||||||
let bootstrapCallFileContents = tree.read(bootstrapCallFilePath, 'utf-8');
|
|
||||||
|
|
||||||
const ast = tsquery.ast(bootstrapCallFileContents);
|
|
||||||
const importBootstrapNodes = tsquery(ast, IMPORT_BOOTSTRAP_FILE, {
|
|
||||||
visitAllChildren: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
importBootstrapNodes.length > 0 &&
|
|
||||||
importBootstrapNodes[0].getText().includes('./bootstrap')
|
|
||||||
) {
|
|
||||||
bootstrapCallFilePath = joinPathFragments(
|
|
||||||
project.sourceRoot,
|
|
||||||
'bootstrap.ts'
|
|
||||||
);
|
|
||||||
bootstrapCallFileContents = tree.read(bootstrapCallFilePath, 'utf-8');
|
|
||||||
}
|
|
||||||
return { bootstrapCallFilePath, bootstrapCallFileContents };
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImportTokenMap(bootstrapCallFileContentsAst: SourceFile) {
|
|
||||||
const importTokenMap = new Map<string, string>();
|
|
||||||
const importedTokensNodes = tsquery(
|
|
||||||
bootstrapCallFileContentsAst,
|
|
||||||
'ImportDeclaration > ImportClause',
|
|
||||||
{ visitAllChildren: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const node of importedTokensNodes) {
|
|
||||||
importTokenMap.set(node.getText(), node.parent.getText());
|
|
||||||
}
|
|
||||||
return importTokenMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImportsRequiredForAppConfig(
|
|
||||||
importTokenMap: Map<string, string>,
|
|
||||||
appConfigNode: Node,
|
|
||||||
oldSourceFilePath: string,
|
|
||||||
newSourceFilePath: string
|
|
||||||
): { appConfigImports: string[]; importsToRemoveFromSource: string[] } {
|
|
||||||
const identifiers = tsquery.query<Identifier>(
|
|
||||||
appConfigNode,
|
|
||||||
'Identifier:not(PropertyAssignment > Identifier)',
|
|
||||||
{ visitAllChildren: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
const appConfigImports = new Set<string>();
|
|
||||||
const originalImportsToRemove = new Set<string>();
|
|
||||||
for (const identifier of identifiers) {
|
|
||||||
for (const key of importTokenMap.keys()) {
|
|
||||||
if (!key.includes(identifier.getText())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let importText = importTokenMap.get(key);
|
|
||||||
originalImportsToRemove.add(importText);
|
|
||||||
|
|
||||||
if (
|
|
||||||
oldSourceFilePath === newSourceFilePath ||
|
|
||||||
oldSourceFilePath.split('/').length ===
|
|
||||||
newSourceFilePath.split('/').length
|
|
||||||
) {
|
|
||||||
appConfigImports.add(importText);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const importPath = tsquery
|
|
||||||
.query<StringLiteral>(importText, 'StringLiteral', {
|
|
||||||
visitAllChildren: true,
|
|
||||||
})[0]
|
|
||||||
.getText()
|
|
||||||
.replace(/'/g, '')
|
|
||||||
.replace(/"/g, '');
|
|
||||||
if (importPath.startsWith('.')) {
|
|
||||||
const resolvedImportPath = resolve(
|
|
||||||
dirname(oldSourceFilePath),
|
|
||||||
importPath
|
|
||||||
);
|
|
||||||
const newRelativeImportPath = relative(
|
|
||||||
dirname(newSourceFilePath),
|
|
||||||
resolvedImportPath
|
|
||||||
);
|
|
||||||
importText = importText.replace(
|
|
||||||
importPath,
|
|
||||||
newRelativeImportPath.startsWith('.')
|
|
||||||
? newRelativeImportPath
|
|
||||||
: `./${newRelativeImportPath}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
appConfigImports.add(importText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
appConfigImports: Array.from(appConfigImports),
|
|
||||||
importsToRemoveFromSource: Array.from(originalImportsToRemove),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAppConfigFileContents(
|
|
||||||
importsRequiredForAppConfig: string[],
|
|
||||||
appConfigText: string
|
|
||||||
) {
|
|
||||||
const buildAppConfigFileContents = (
|
|
||||||
importStatements: string[],
|
|
||||||
appConfig: string
|
|
||||||
) => `import { ApplicationConfig } from '@angular/core';${importStatements.join(
|
|
||||||
'\n'
|
|
||||||
)}
|
|
||||||
export const appConfig: ApplicationConfig = ${appConfig}`;
|
|
||||||
|
|
||||||
const appConfigFileContents = buildAppConfigFileContents(
|
|
||||||
importsRequiredForAppConfig,
|
|
||||||
appConfigText
|
|
||||||
);
|
|
||||||
return appConfigFileContents;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBootstrapCallFileContents(
|
|
||||||
bootstrapCallFileContents: string,
|
|
||||||
appConfigNode: Node,
|
|
||||||
importsRequiredForAppConfig: string[]
|
|
||||||
) {
|
|
||||||
let newBootstrapCallFileContents = `import { appConfig } from './app/app.config';
|
|
||||||
${bootstrapCallFileContents.slice(
|
|
||||||
0,
|
|
||||||
appConfigNode.getStart()
|
|
||||||
)}appConfig${bootstrapCallFileContents.slice(appConfigNode.getEnd())}`;
|
|
||||||
|
|
||||||
for (const importStatement of importsRequiredForAppConfig) {
|
|
||||||
newBootstrapCallFileContents = newBootstrapCallFileContents.replace(
|
|
||||||
importStatement,
|
|
||||||
''
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return newBootstrapCallFileContents;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function extractStandaloneConfig(tree: Tree) {
|
|
||||||
if (!tsModule) {
|
|
||||||
tsModule = ensureTypescript();
|
|
||||||
}
|
|
||||||
if (!tsquery) {
|
|
||||||
tsquery = require('@phenomnomnominal/tsquery').tsquery;
|
|
||||||
}
|
|
||||||
|
|
||||||
const projects = await getProjectsFilteredByDependencies(tree, [
|
|
||||||
'npm:@angular/core',
|
|
||||||
]);
|
|
||||||
|
|
||||||
const BOOTSTRAP_APPLICATION_CALL_SELECTOR =
|
|
||||||
'CallExpression:has(Identifier[name=bootstrapApplication])';
|
|
||||||
const BOOTSTRAP_APPLICATION_CALL_CONFIG_SELECTOR =
|
|
||||||
'CallExpression:has(Identifier[name=bootstrapApplication]) > ObjectLiteralExpression';
|
|
||||||
|
|
||||||
for (const { project } of projects) {
|
|
||||||
if (project.projectType !== 'application') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (project.targets?.build?.options?.main === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { bootstrapCallFilePath, bootstrapCallFileContents } =
|
|
||||||
getBootstrapCallFileInfo(
|
|
||||||
tree,
|
|
||||||
project,
|
|
||||||
project.targets.build.options.main
|
|
||||||
);
|
|
||||||
|
|
||||||
const bootstrapCallFileContentsAst = tsquery.ast(bootstrapCallFileContents);
|
|
||||||
const nodes: Node[] = tsquery(
|
|
||||||
bootstrapCallFileContentsAst,
|
|
||||||
BOOTSTRAP_APPLICATION_CALL_SELECTOR,
|
|
||||||
{ visitAllChildren: true }
|
|
||||||
);
|
|
||||||
if (nodes.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const importTokenMap = getImportTokenMap(bootstrapCallFileContentsAst);
|
|
||||||
|
|
||||||
const bootstrapCallNode = nodes[0];
|
|
||||||
const appConfigNodes = tsquery(
|
|
||||||
bootstrapCallNode,
|
|
||||||
BOOTSTRAP_APPLICATION_CALL_CONFIG_SELECTOR,
|
|
||||||
{ visitAllChildren: true }
|
|
||||||
);
|
|
||||||
if (appConfigNodes.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const appConfigNode = appConfigNodes[0];
|
|
||||||
const appConfigText = appConfigNode.getText();
|
|
||||||
const appConfigFilePath = joinPathFragments(
|
|
||||||
project.sourceRoot,
|
|
||||||
'app/app.config.ts'
|
|
||||||
);
|
|
||||||
|
|
||||||
const { appConfigImports, importsToRemoveFromSource } =
|
|
||||||
getImportsRequiredForAppConfig(
|
|
||||||
importTokenMap,
|
|
||||||
appConfigNode,
|
|
||||||
bootstrapCallFilePath,
|
|
||||||
appConfigFilePath
|
|
||||||
);
|
|
||||||
|
|
||||||
const appConfigFileContents = getAppConfigFileContents(
|
|
||||||
appConfigImports,
|
|
||||||
appConfigText
|
|
||||||
);
|
|
||||||
|
|
||||||
tree.write(appConfigFilePath, appConfigFileContents);
|
|
||||||
|
|
||||||
let newBootstrapCallFileContents = getBootstrapCallFileContents(
|
|
||||||
bootstrapCallFileContents,
|
|
||||||
appConfigNode,
|
|
||||||
importsToRemoveFromSource
|
|
||||||
);
|
|
||||||
|
|
||||||
tree.write(bootstrapCallFilePath, newBootstrapCallFileContents);
|
|
||||||
}
|
|
||||||
|
|
||||||
await formatFiles(tree);
|
|
||||||
}
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
import type { Tree } from '@nx/devkit';
|
|
||||||
import { readJson, updateJson } from '@nx/devkit';
|
|
||||||
import * as devkit from '@nx/devkit';
|
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
||||||
import removeNgccInvocation from './remove-ngcc-invocation';
|
|
||||||
|
|
||||||
describe('remove-ngcc-invocation migration', () => {
|
|
||||||
let tree: Tree;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
tree = createTreeWithEmptyWorkspace();
|
|
||||||
jest
|
|
||||||
.spyOn(devkit, 'formatFiles')
|
|
||||||
.mockImplementation(() => Promise.resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw when there is no scripts entry', async () => {
|
|
||||||
updateJson(tree, 'package.json', (json) => {
|
|
||||||
delete json.scripts;
|
|
||||||
return json;
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(removeNgccInvocation(tree)).resolves.not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw when there is no postinstall script', async () => {
|
|
||||||
updateJson(tree, 'package.json', (json) => {
|
|
||||||
json.scripts = {};
|
|
||||||
return json;
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(removeNgccInvocation(tree)).resolves.not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle postinstall script without ngcc invocation', async () => {
|
|
||||||
updateJson(tree, 'package.json', (json) => ({
|
|
||||||
...json,
|
|
||||||
scripts: {
|
|
||||||
postinstall:
|
|
||||||
'node ./some-awesome-script.js && node ./another-awesome-script.js',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
await removeNgccInvocation(tree);
|
|
||||||
|
|
||||||
const { scripts } = readJson(tree, 'package.json');
|
|
||||||
expect(scripts.postinstall).toBe(
|
|
||||||
'node ./some-awesome-script.js && node ./another-awesome-script.js'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle postinstall script with only ngcc invocation', async () => {
|
|
||||||
updateJson(tree, 'package.json', (json) => ({
|
|
||||||
...json,
|
|
||||||
scripts: {
|
|
||||||
postinstall: 'ngcc --properties es2020 browser module main',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
await removeNgccInvocation(tree);
|
|
||||||
|
|
||||||
const { scripts } = readJson(tree, 'package.json');
|
|
||||||
expect(scripts.postinstall).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle postinstall script with extra leading command', async () => {
|
|
||||||
updateJson(tree, 'package.json', (json) => ({
|
|
||||||
...json,
|
|
||||||
scripts: {
|
|
||||||
postinstall:
|
|
||||||
'node ./some-awesome-script.js && ngcc --properties es2020 browser module main',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
await removeNgccInvocation(tree);
|
|
||||||
|
|
||||||
const { scripts } = readJson(tree, 'package.json');
|
|
||||||
expect(scripts.postinstall).toBe('node ./some-awesome-script.js');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle postinstall script with extra trailing command', async () => {
|
|
||||||
updateJson(tree, 'package.json', (json) => ({
|
|
||||||
...json,
|
|
||||||
scripts: {
|
|
||||||
postinstall:
|
|
||||||
'ngcc --properties es2020 browser module main && node ./some-awesome-script.js',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
await removeNgccInvocation(tree);
|
|
||||||
|
|
||||||
const { scripts } = readJson(tree, 'package.json');
|
|
||||||
expect(scripts.postinstall).toBe('node ./some-awesome-script.js');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle postinstall script with extra leading and trailing commands', async () => {
|
|
||||||
updateJson(tree, 'package.json', (json) => ({
|
|
||||||
...json,
|
|
||||||
scripts: {
|
|
||||||
postinstall:
|
|
||||||
'node ./some-awesome-script.js && ngcc --properties es2020 browser module main && node ./another-awesome-script.js',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
await removeNgccInvocation(tree);
|
|
||||||
|
|
||||||
const { scripts } = readJson(tree, 'package.json');
|
|
||||||
expect(scripts.postinstall).toBe(
|
|
||||||
'node ./some-awesome-script.js && node ./another-awesome-script.js'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove ngcc invocation with an arbitrary amount of spaces around "&&"', async () => {
|
|
||||||
updateJson(tree, 'package.json', (json) => ({
|
|
||||||
...json,
|
|
||||||
scripts: {
|
|
||||||
postinstall:
|
|
||||||
'node ./some-awesome-script.js && ngcc --properties es2020 browser module main &&node ./another-awesome-script.js',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
await removeNgccInvocation(tree);
|
|
||||||
|
|
||||||
const { scripts } = readJson(tree, 'package.json');
|
|
||||||
expect(scripts.postinstall).toBe(
|
|
||||||
'node ./some-awesome-script.js &&node ./another-awesome-script.js'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import type { Tree } from '@nx/devkit';
|
|
||||||
import { formatFiles, updateJson } from '@nx/devkit';
|
|
||||||
|
|
||||||
export default async function (tree: Tree) {
|
|
||||||
updateJson(tree, 'package.json', (json) => {
|
|
||||||
if (!json.scripts?.postinstall?.includes('ngcc ')) {
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
json.scripts.postinstall = json.scripts.postinstall
|
|
||||||
// special case when ngcc is at the start so we remove the && as well
|
|
||||||
.replace(/^(ngcc.*?&& *)(.*)/, '$2')
|
|
||||||
// everything else
|
|
||||||
.replace(/(.*?)((&& *)?ngcc.*?)((?=&)|$)(.*)/, '$1$5')
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
if (json.scripts.postinstall === '') {
|
|
||||||
json.scripts.postinstall = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
});
|
|
||||||
|
|
||||||
await formatFiles(tree);
|
|
||||||
}
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
import type { Tree } from '@nx/devkit';
|
|
||||||
import * as devkit from '@nx/devkit';
|
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
||||||
import migration from './remove-render-module-platform-server-exports';
|
|
||||||
|
|
||||||
describe('remove-render-module-platform-server-exports migration', () => {
|
|
||||||
let tree: Tree;
|
|
||||||
const testTypeScriptFilePath = 'test.ts';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
tree = createTreeWithEmptyWorkspace();
|
|
||||||
jest
|
|
||||||
.spyOn(devkit, 'formatFiles')
|
|
||||||
.mockImplementation(() => Promise.resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
describe(`Migration to remove '@angular/platform-server' exports`, () => {
|
|
||||||
it(`should delete '@angular/platform-server' export when 'renderModule' is the only exported symbol`, async () => {
|
|
||||||
tree.write(
|
|
||||||
testTypeScriptFilePath,
|
|
||||||
`
|
|
||||||
import { Path, join } from '@angular-devkit/core';
|
|
||||||
export { renderModule } from '@angular/platform-server';
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
await migration(tree);
|
|
||||||
|
|
||||||
const content = tree.read(testTypeScriptFilePath, 'utf-8');
|
|
||||||
expect(content).not.toContain('@angular/platform-server');
|
|
||||||
expect(content).toContain(
|
|
||||||
`import { Path, join } from '@angular-devkit/core';`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should delete only 'renderModule' when there are additional exports`, async () => {
|
|
||||||
tree.write(
|
|
||||||
testTypeScriptFilePath,
|
|
||||||
`
|
|
||||||
import { Path, join } from '@angular-devkit/core';
|
|
||||||
export { renderModule, ServerModule } from '@angular/platform-server';
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
await migration(tree);
|
|
||||||
|
|
||||||
const content = tree.read(testTypeScriptFilePath, 'utf-8');
|
|
||||||
|
|
||||||
expect(content).toContain(
|
|
||||||
`import { Path, join } from '@angular-devkit/core';`
|
|
||||||
);
|
|
||||||
expect(content).toContain(
|
|
||||||
`export { ServerModule } from '@angular/platform-server';`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should not delete 'renderModule' when it's exported from another module`, async () => {
|
|
||||||
tree.write(
|
|
||||||
testTypeScriptFilePath,
|
|
||||||
`
|
|
||||||
export { renderModule } from '@angular/core';
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
await migration(tree);
|
|
||||||
|
|
||||||
const content = tree.read(testTypeScriptFilePath, 'utf-8');
|
|
||||||
expect(content).toContain(
|
|
||||||
`export { renderModule } from '@angular/core';`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should not delete 'renderModule' when it's imported from '@angular/platform-server'`, async () => {
|
|
||||||
tree.write(
|
|
||||||
testTypeScriptFilePath,
|
|
||||||
`
|
|
||||||
import { renderModule } from '@angular/platform-server';
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
await migration(tree);
|
|
||||||
|
|
||||||
const content = tree.read(testTypeScriptFilePath, 'utf-8');
|
|
||||||
expect(content).toContain(
|
|
||||||
`import { renderModule } from '@angular/platform-server'`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
import type { Tree } from '@nx/devkit';
|
|
||||||
import { formatFiles, visitNotIgnoredFiles } from '@nx/devkit';
|
|
||||||
import * as ts from 'typescript';
|
|
||||||
import { FileChangeRecorder } from '../../utils/file-change-recorder';
|
|
||||||
|
|
||||||
export default async function (tree: Tree) {
|
|
||||||
visitNotIgnoredFiles(tree, '/', (path) => {
|
|
||||||
if (path.endsWith('.ts') && !path.endsWith('.d.ts')) {
|
|
||||||
const content = tree.read(path, 'utf8');
|
|
||||||
if (
|
|
||||||
content.includes('@angular/platform-server') &&
|
|
||||||
content.includes('renderModule')
|
|
||||||
) {
|
|
||||||
const source = ts.createSourceFile(
|
|
||||||
path,
|
|
||||||
content.toString().replace(/^\uFEFF/, ''),
|
|
||||||
ts.ScriptTarget.Latest,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
let recorder: FileChangeRecorder | undefined;
|
|
||||||
let printer: ts.Printer | undefined;
|
|
||||||
|
|
||||||
ts.forEachChild(source, function analyze(node) {
|
|
||||||
if (
|
|
||||||
!(
|
|
||||||
ts.isExportDeclaration(node) &&
|
|
||||||
node.moduleSpecifier &&
|
|
||||||
ts.isStringLiteral(node.moduleSpecifier) &&
|
|
||||||
node.moduleSpecifier.text === '@angular/platform-server' &&
|
|
||||||
node.exportClause &&
|
|
||||||
ts.isNamedExports(node.exportClause)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// Not a @angular/platform-server named export.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const exportClause = node.exportClause;
|
|
||||||
const newElements: ts.ExportSpecifier[] = [];
|
|
||||||
for (const element of exportClause.elements) {
|
|
||||||
if (element.name.text !== 'renderModule') {
|
|
||||||
newElements.push(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newElements.length === exportClause.elements.length) {
|
|
||||||
// No changes
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
recorder ??= new FileChangeRecorder(tree, path);
|
|
||||||
|
|
||||||
if (newElements.length) {
|
|
||||||
// Update named exports as there are leftovers.
|
|
||||||
const newExportClause = ts.factory.updateNamedExports(
|
|
||||||
exportClause,
|
|
||||||
newElements
|
|
||||||
);
|
|
||||||
printer ??= ts.createPrinter();
|
|
||||||
const fix = printer.printNode(
|
|
||||||
ts.EmitHint.Unspecified,
|
|
||||||
newExportClause,
|
|
||||||
source
|
|
||||||
);
|
|
||||||
|
|
||||||
const index = exportClause.getStart();
|
|
||||||
const length = exportClause.getWidth();
|
|
||||||
recorder.remove(index, index + length);
|
|
||||||
recorder.insertLeft(index, fix);
|
|
||||||
} else {
|
|
||||||
// Delete export as no exports remain.
|
|
||||||
recorder.remove(node.getStart(), node.getStart() + node.getWidth());
|
|
||||||
}
|
|
||||||
|
|
||||||
ts.forEachChild(node, analyze);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (recorder) {
|
|
||||||
recorder.applyChanges();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await formatFiles(tree);
|
|
||||||
}
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
import { readJson, Tree, writeJson } from '@nx/devkit';
|
|
||||||
import * as devkit from '@nx/devkit';
|
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
||||||
import updateAngularCli, { angularCliVersion } from './update-angular-cli';
|
|
||||||
|
|
||||||
describe('update-angular-cli migration', () => {
|
|
||||||
let tree: Tree;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
||||||
jest
|
|
||||||
.spyOn(devkit, 'formatFiles')
|
|
||||||
.mockImplementation(() => Promise.resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update @angular/cli version when defined as a dev dependency', async () => {
|
|
||||||
writeJson(tree, 'package.json', {
|
|
||||||
devDependencies: { '@angular/cli': '~13.3.0' },
|
|
||||||
});
|
|
||||||
|
|
||||||
await updateAngularCli(tree);
|
|
||||||
|
|
||||||
const { devDependencies } = readJson(tree, 'package.json');
|
|
||||||
expect(devDependencies['@angular/cli']).toEqual(angularCliVersion);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should update @angular/cli version when defined as a dependency', async () => {
|
|
||||||
writeJson(tree, 'package.json', {
|
|
||||||
dependencies: { '@angular/cli': '~13.3.0' },
|
|
||||||
});
|
|
||||||
|
|
||||||
await updateAngularCli(tree);
|
|
||||||
|
|
||||||
const { dependencies } = readJson(tree, 'package.json');
|
|
||||||
expect(dependencies['@angular/cli']).toEqual(angularCliVersion);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not add @angular/cli to package.json when it is not set', async () => {
|
|
||||||
const initialPackageJson = readJson(tree, 'package.json');
|
|
||||||
|
|
||||||
await updateAngularCli(tree);
|
|
||||||
|
|
||||||
const packageJson = readJson(tree, 'package.json');
|
|
||||||
expect(packageJson).toStrictEqual(initialPackageJson);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { formatFiles, Tree, updateJson } from '@nx/devkit';
|
|
||||||
|
|
||||||
export const angularCliVersion = '~16.0.0';
|
|
||||||
|
|
||||||
export default async function (tree: Tree) {
|
|
||||||
let shouldFormat = false;
|
|
||||||
|
|
||||||
updateJson(tree, 'package.json', (json) => {
|
|
||||||
if (json.devDependencies?.['@angular/cli']) {
|
|
||||||
json.devDependencies['@angular/cli'] = angularCliVersion;
|
|
||||||
shouldFormat = true;
|
|
||||||
} else if (json.dependencies?.['@angular/cli']) {
|
|
||||||
json.dependencies['@angular/cli'] = angularCliVersion;
|
|
||||||
shouldFormat = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (shouldFormat) {
|
|
||||||
await formatFiles(tree);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
import type { Tree } from '@nx/devkit';
|
|
||||||
import { addProjectConfiguration, readProjectConfiguration } from '@nx/devkit';
|
|
||||||
import * as devkit from '@nx/devkit';
|
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
||||||
import migration from './update-server-executor-config';
|
|
||||||
|
|
||||||
describe.each([
|
|
||||||
'@angular-devkit/build-angular:server',
|
|
||||||
'@nx/angular:server',
|
|
||||||
'@nrwl/angular:server',
|
|
||||||
])('update-server-executor-config migration', (executor) => {
|
|
||||||
let tree: Tree;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
tree = createTreeWithEmptyWorkspace();
|
|
||||||
jest
|
|
||||||
.spyOn(devkit, 'formatFiles')
|
|
||||||
.mockImplementation(() => Promise.resolve());
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should add 'buildOptimizer: false' to config with 'optimization: false' (${executor})`, async () => {
|
|
||||||
addProjectConfiguration(tree, 'app1', {
|
|
||||||
root: 'apps/app1',
|
|
||||||
targets: {
|
|
||||||
server: {
|
|
||||||
executor,
|
|
||||||
configurations: {
|
|
||||||
development: { optimization: false },
|
|
||||||
production: { optimization: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await migration(tree);
|
|
||||||
|
|
||||||
const project = readProjectConfiguration(tree, 'app1');
|
|
||||||
expect(
|
|
||||||
project.targets.server.configurations.development.buildOptimizer
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should not add 'buildOptimizer' option to config when 'optimization' is not defined (${executor})`, async () => {
|
|
||||||
addProjectConfiguration(tree, 'app1', {
|
|
||||||
root: 'apps/app1',
|
|
||||||
targets: {
|
|
||||||
server: {
|
|
||||||
executor,
|
|
||||||
options: {},
|
|
||||||
configurations: {
|
|
||||||
development: { optimization: false },
|
|
||||||
production: { optimization: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await migration(tree);
|
|
||||||
|
|
||||||
const project = readProjectConfiguration(tree, 'app1');
|
|
||||||
expect(project.targets.server.options.buildOptimizer).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should add 'buildOptimizer: true' to config with 'optimization: true' (${executor})`, async () => {
|
|
||||||
addProjectConfiguration(tree, 'app1', {
|
|
||||||
root: 'apps/app1',
|
|
||||||
targets: {
|
|
||||||
server: {
|
|
||||||
executor,
|
|
||||||
options: {},
|
|
||||||
configurations: {
|
|
||||||
development: { optimization: false },
|
|
||||||
production: { optimization: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await migration(tree);
|
|
||||||
|
|
||||||
const project = readProjectConfiguration(tree, 'app1');
|
|
||||||
expect(
|
|
||||||
project.targets.server.configurations.production.buildOptimizer
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`should not change 'buildOptimizer' if already set (${executor})`, async () => {
|
|
||||||
addProjectConfiguration(tree, 'app1', {
|
|
||||||
root: 'apps/app1',
|
|
||||||
targets: {
|
|
||||||
server: {
|
|
||||||
executor,
|
|
||||||
options: {},
|
|
||||||
configurations: {
|
|
||||||
development: {
|
|
||||||
optimization: false,
|
|
||||||
buildOptimizer: true,
|
|
||||||
},
|
|
||||||
production: { optimization: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await migration(tree);
|
|
||||||
|
|
||||||
const project = readProjectConfiguration(tree, 'app1');
|
|
||||||
expect(
|
|
||||||
project.targets.server.configurations.development.buildOptimizer
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import type { ServerBuilderOptions } from '@angular-devkit/build-angular';
|
|
||||||
import type { Tree } from '@nx/devkit';
|
|
||||||
import {
|
|
||||||
formatFiles,
|
|
||||||
readProjectConfiguration,
|
|
||||||
updateProjectConfiguration,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
|
|
||||||
|
|
||||||
const executors = [
|
|
||||||
'@angular-devkit/build-angular:server',
|
|
||||||
'@nx/angular:server',
|
|
||||||
'@nrwl/angular:server',
|
|
||||||
];
|
|
||||||
|
|
||||||
export default async function (tree: Tree) {
|
|
||||||
executors.forEach((executor) => {
|
|
||||||
forEachExecutorOptions<ServerBuilderOptions>(
|
|
||||||
tree,
|
|
||||||
executor,
|
|
||||||
(_options, projectName, targetName, configurationName) => {
|
|
||||||
const projectConfiguration = readProjectConfiguration(
|
|
||||||
tree,
|
|
||||||
projectName
|
|
||||||
);
|
|
||||||
const configToUpdate: ServerBuilderOptions = configurationName
|
|
||||||
? projectConfiguration.targets[targetName].configurations[
|
|
||||||
configurationName
|
|
||||||
]
|
|
||||||
: projectConfiguration.targets[targetName].options;
|
|
||||||
|
|
||||||
if (
|
|
||||||
configToUpdate?.buildOptimizer === undefined &&
|
|
||||||
configToUpdate?.optimization !== undefined
|
|
||||||
) {
|
|
||||||
configToUpdate.buildOptimizer = !!configToUpdate.optimization;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProjectConfiguration(tree, projectName, projectConfiguration);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await formatFiles(tree);
|
|
||||||
}
|
|
||||||
@ -5,12 +5,12 @@ import {
|
|||||||
readJson,
|
readJson,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||||
import type { ImportDeclaration, ImportSpecifier, Node } from 'typescript';
|
|
||||||
import { FileChangeRecorder } from '../../utils/file-change-recorder';
|
|
||||||
import { ngrxVersion } from '../../utils/versions';
|
|
||||||
import { getProjectsFilteredByDependencies } from '../utils/projects';
|
|
||||||
import { readFileMapCache } from 'nx/src/project-graph/nx-deps-cache';
|
|
||||||
import { fileDataDepTarget } from 'nx/src/config/project-graph';
|
import { fileDataDepTarget } from 'nx/src/config/project-graph';
|
||||||
|
import { readFileMapCache } from 'nx/src/project-graph/nx-deps-cache';
|
||||||
|
import type { ImportDeclaration, ImportSpecifier, Node } from 'typescript';
|
||||||
|
import { versions } from '../../generators/utils/version-utils';
|
||||||
|
import { FileChangeRecorder } from '../../utils/file-change-recorder';
|
||||||
|
import { getProjectsFilteredByDependencies } from '../utils/projects';
|
||||||
|
|
||||||
let tsquery: typeof import('@phenomnomnominal/tsquery').tsquery;
|
let tsquery: typeof import('@phenomnomnominal/tsquery').tsquery;
|
||||||
|
|
||||||
@ -169,7 +169,11 @@ function addNgrxRouterStoreIfNotInstalled(tree: Tree): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addDependenciesToPackageJson(tree, { '@ngrx/router-store': ngrxVersion }, {});
|
addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{ '@ngrx/router-store': versions(tree).ngrxVersion },
|
||||||
|
{}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterFilesWithNxAngularDep(files: FileData[]): FileData[] {
|
function filterFilesWithNxAngularDep(files: FileData[]): FileData[] {
|
||||||
|
|||||||
@ -0,0 +1,139 @@
|
|||||||
|
#### Change the Data Persistence Operator Imports from `@nx/angular` to `@ngrx/router-store/data-persistence`
|
||||||
|
|
||||||
|
The data persistence operators (`fetch`, `navigation`, `optimisticUpdate`, and `pessimisticUpdate`) have been deprecated for a while and are now removed from the `@nx/angular` package. This migration automatically updates your import statements to use the `@ngrx/router-store/data-persistence` module and adds `@ngrx/router-store` to your dependencies if needed.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
If you import only data persistence operators from `@nx/angular`, the migration will update the import path to `@ngrx/router-store/data-persistence`.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/src/app/users/users.effects.ts" highlightLines=[2] %}
|
||||||
|
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UsersEffects {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/src/app/users/users.effects.ts" highlightLines=[2] %}
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { fetch } from '@ngrx/router-store/data-persistence';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UsersEffects {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
If you import multiple data persistence operators from `@nx/angular`, the migration will update the import path for all of them.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/src/app/users/users.effects.ts" highlightLines=[2] %}
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { fetch, navigation } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UsersEffects {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/src/app/users/users.effects.ts" highlightLines=[2] %}
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { fetch, navigation } from '@ngrx/router-store/data-persistence';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UsersEffects {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
If your imports mix data persistence operators with other utilities from `@nx/angular`, the migration will split them into separate import statements.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/src/app/users/users.effects.ts" highlightLines=[2] %}
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { fetch, someExtraUtility, navigation } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UsersEffects {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```ts {% fileName="apps/app1/src/app/users/users.effects.ts" highlightLines=[2,3] %}
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { fetch, navigation } from '@ngrx/router-store/data-persistence';
|
||||||
|
import { someExtraUtility } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UsersEffects {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
|
|
||||||
|
If you don't already have `@ngrx/router-store` in your dependencies, the migration will add it to your package.json.
|
||||||
|
|
||||||
|
{% tabs %}
|
||||||
|
{% tab label="Before" %}
|
||||||
|
|
||||||
|
```jsonc {% fileName="package.json" %}
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@nx/angular": "^21.0.0",
|
||||||
|
"@ngrx/store": "^19.1.0",
|
||||||
|
"@ngrx/effects": "^19.1.0"
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
|
||||||
|
{% tab label="After" %}
|
||||||
|
|
||||||
|
```jsonc {% fileName="package.json" highlightLines=[6] %}
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@nx/angular": "^21.0.0",
|
||||||
|
"@ngrx/store": "^19.1.0",
|
||||||
|
"@ngrx/effects": "^19.1.0",
|
||||||
|
"@ngrx/router-store": "^19.1.0"
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{% /tab %}
|
||||||
|
{% /tabs %}
|
||||||
@ -0,0 +1,318 @@
|
|||||||
|
import {
|
||||||
|
addProjectConfiguration,
|
||||||
|
DependencyType,
|
||||||
|
type ProjectGraph,
|
||||||
|
type Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import type { FileMapCache } from 'nx/src/project-graph/nx-deps-cache';
|
||||||
|
import migration from './change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence';
|
||||||
|
|
||||||
|
let projectGraph: ProjectGraph;
|
||||||
|
jest.mock('@nx/devkit', () => ({
|
||||||
|
...jest.requireActual('@nx/devkit'),
|
||||||
|
createProjectGraphAsync: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => Promise.resolve(projectGraph)),
|
||||||
|
}));
|
||||||
|
let fileMapCache: FileMapCache;
|
||||||
|
jest.mock('nx/src/project-graph/nx-deps-cache', () => ({
|
||||||
|
...jest.requireActual('nx/src/project-graph/nx-deps-cache'),
|
||||||
|
readFileMapCache: jest.fn().mockImplementation(() => fileMapCache),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('change-data-persistence-operators-imports-to-ngrx-router-store-data-persistence migration', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
const file = 'apps/app1/src/app/+state/users.effects.ts';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
addProjectConfiguration(tree, 'app1', { root: 'apps/app1' });
|
||||||
|
projectGraph = {
|
||||||
|
dependencies: {
|
||||||
|
app1: [{ source: 'app1', target: 'npm:@nx/angular', type: 'static' }],
|
||||||
|
},
|
||||||
|
nodes: {
|
||||||
|
app1: {
|
||||||
|
data: { root: 'apps/app1' },
|
||||||
|
name: 'app1',
|
||||||
|
type: 'app',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
fileMapCache = {
|
||||||
|
fileMap: {
|
||||||
|
projectFileMap: {
|
||||||
|
app1: [
|
||||||
|
{
|
||||||
|
file,
|
||||||
|
hash: '',
|
||||||
|
deps: [['app1', 'npm:@nx/angular', DependencyType.static]],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as FileMapCache;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing when there are no imports from the angular plugin', async () => {
|
||||||
|
tree.write(
|
||||||
|
file,
|
||||||
|
`import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read(file, 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not replace the import path when no operator is imported', async () => {
|
||||||
|
tree.write(
|
||||||
|
file,
|
||||||
|
`import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { foo } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read(file, 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { foo } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not match imports from angular plugin secondary entry points', async () => {
|
||||||
|
tree.write(
|
||||||
|
file,
|
||||||
|
`import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch } from '@nx/angular/mf';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read(file, 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch } from '@nx/angular/mf';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace the import path in-place when it is importing an operator', async () => {
|
||||||
|
tree.write(
|
||||||
|
file,
|
||||||
|
`import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read(file, 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch } from '@ngrx/router-store/data-persistence';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match imports using @nrwl/angular', async () => {
|
||||||
|
tree.write(
|
||||||
|
file,
|
||||||
|
`import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch } from '@nrwl/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read(file, 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch } from '@ngrx/router-store/data-persistence';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support multiple operators imports', async () => {
|
||||||
|
tree.write(
|
||||||
|
file,
|
||||||
|
`import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch, navigation } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read(file, 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch, navigation } from '@ngrx/router-store/data-persistence';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a separate import statement when there are operator and non-operator imports', async () => {
|
||||||
|
tree.write(
|
||||||
|
file,
|
||||||
|
`import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch, foo, navigation } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read(file, 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch, navigation } from '@ngrx/router-store/data-persistence';
|
||||||
|
import { foo } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support multiple import statements and import paths', async () => {
|
||||||
|
tree.write(
|
||||||
|
file,
|
||||||
|
`import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch } from '@nx/angular';
|
||||||
|
import { navigation } from '@nrwl/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read(file, 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch } from '@ngrx/router-store/data-persistence';
|
||||||
|
import { navigation } from '@ngrx/router-store/data-persistence';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support renamed import symbols', async () => {
|
||||||
|
tree.write(
|
||||||
|
file,
|
||||||
|
`import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch as customFetch } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read(file, 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch as customFetch } from '@ngrx/router-store/data-persistence';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support multiple imports with renamed and non-renamed symbols', async () => {
|
||||||
|
tree.write(
|
||||||
|
file,
|
||||||
|
`import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch as customFetch, navigation } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read(file, 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import {
|
||||||
|
fetch as customFetch,
|
||||||
|
navigation,
|
||||||
|
} from '@ngrx/router-store/data-persistence';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a separate import statement even with renamed symbols', async () => {
|
||||||
|
tree.write(
|
||||||
|
file,
|
||||||
|
`import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import { fetch as customFetch, foo, navigation } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
await migration(tree);
|
||||||
|
|
||||||
|
expect(tree.read(file, 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"import { Actions, createEffect, ofType } from '@ngrx/effects';
|
||||||
|
import {
|
||||||
|
fetch as customFetch,
|
||||||
|
navigation,
|
||||||
|
} from '@ngrx/router-store/data-persistence';
|
||||||
|
import { foo } from '@nx/angular';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class UsersEffects {}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,194 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
formatFiles,
|
||||||
|
readJson,
|
||||||
|
type FileData,
|
||||||
|
type Tree,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript';
|
||||||
|
import { fileDataDepTarget } from 'nx/src/config/project-graph';
|
||||||
|
import { readFileMapCache } from 'nx/src/project-graph/nx-deps-cache';
|
||||||
|
import type { ImportDeclaration, ImportSpecifier, Node } from 'typescript';
|
||||||
|
import { versions } from '../../generators/utils/version-utils';
|
||||||
|
import { FileChangeRecorder } from '../../utils/file-change-recorder';
|
||||||
|
import { getProjectsFilteredByDependencies } from '../utils/projects';
|
||||||
|
|
||||||
|
let tsquery: typeof import('@phenomnomnominal/tsquery').tsquery;
|
||||||
|
|
||||||
|
const angularPluginTargetNames = ['npm:@nx/angular', 'npm:@nrwl/angular'];
|
||||||
|
const dataPersistenceOperators = [
|
||||||
|
'fetch',
|
||||||
|
'navigation',
|
||||||
|
'optimisticUpdate',
|
||||||
|
'pessimisticUpdate',
|
||||||
|
];
|
||||||
|
const newImportPath = '@ngrx/router-store/data-persistence';
|
||||||
|
|
||||||
|
export default async function (tree: Tree): Promise<void> {
|
||||||
|
const projects = await getProjectsFilteredByDependencies(
|
||||||
|
tree,
|
||||||
|
angularPluginTargetNames
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!projects.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureTypescript();
|
||||||
|
tsquery = require('@phenomnomnominal/tsquery').tsquery;
|
||||||
|
const cachedFileMap = readFileMapCache().fileMap.projectFileMap;
|
||||||
|
|
||||||
|
const filesWithNxAngularImports: FileData[] = [];
|
||||||
|
for (const { graphNode } of projects) {
|
||||||
|
const files = filterFilesWithNxAngularDep(
|
||||||
|
cachedFileMap[graphNode.name] || []
|
||||||
|
);
|
||||||
|
filesWithNxAngularImports.push(...files);
|
||||||
|
}
|
||||||
|
|
||||||
|
let isAnyFileUsingDataPersistence = false;
|
||||||
|
for (const { file } of filesWithNxAngularImports) {
|
||||||
|
const updated = replaceDataPersistenceInFile(tree, file);
|
||||||
|
isAnyFileUsingDataPersistence ||= updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAnyFileUsingDataPersistence) {
|
||||||
|
addNgrxRouterStoreIfNotInstalled(tree);
|
||||||
|
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceDataPersistenceInFile(tree: Tree, file: string): boolean {
|
||||||
|
const fileContents = tree.read(file, 'utf-8');
|
||||||
|
const fileAst = tsquery.ast(fileContents);
|
||||||
|
|
||||||
|
// "\\u002F" is the unicode code for "/", there's an issue with the query parser
|
||||||
|
// that prevents using "/" directly in regex queries
|
||||||
|
// https://github.com/estools/esquery/issues/68#issuecomment-415597670
|
||||||
|
const NX_ANGULAR_IMPORT_SELECTOR =
|
||||||
|
'ImportDeclaration:has(StringLiteral[value=/@(nx|nrwl)\\u002Fangular$/])';
|
||||||
|
const nxAngularImports = tsquery<ImportDeclaration>(
|
||||||
|
fileAst,
|
||||||
|
NX_ANGULAR_IMPORT_SELECTOR,
|
||||||
|
{ visitAllChildren: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!nxAngularImports.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recorder = new FileChangeRecorder(tree, file);
|
||||||
|
|
||||||
|
const IMPORT_SPECIFIERS_SELECTOR =
|
||||||
|
'ImportClause NamedImports ImportSpecifier';
|
||||||
|
for (const importDeclaration of nxAngularImports) {
|
||||||
|
const importSpecifiers = tsquery<ImportSpecifier>(
|
||||||
|
importDeclaration,
|
||||||
|
IMPORT_SPECIFIERS_SELECTOR,
|
||||||
|
{ visitAllChildren: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!importSpecifiers.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no imported symbol is a data persistence operator, skip
|
||||||
|
if (importSpecifiers.every((i) => !isOperatorImport(i))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all imported symbols are data persistence operators, change import path
|
||||||
|
if (importSpecifiers.every((i) => isOperatorImport(i))) {
|
||||||
|
const IMPORT_PATH_SELECTOR = `${NX_ANGULAR_IMPORT_SELECTOR} > StringLiteral`;
|
||||||
|
const importPathNode = tsquery(importDeclaration, IMPORT_PATH_SELECTOR, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
});
|
||||||
|
recorder.replace(importPathNode[0], `'${newImportPath}'`);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mixed imports, split data persistence operators to a separate import
|
||||||
|
const operatorImportSpecifiers: string[] = [];
|
||||||
|
for (const importSpecifier of importSpecifiers) {
|
||||||
|
if (isOperatorImport(importSpecifier)) {
|
||||||
|
operatorImportSpecifiers.push(importSpecifier.getText());
|
||||||
|
recorder.remove(
|
||||||
|
importSpecifier.getStart(),
|
||||||
|
importSpecifier.getEnd() +
|
||||||
|
(hasTrailingComma(recorder.originalContent, importSpecifier)
|
||||||
|
? 1
|
||||||
|
: 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder.insertLeft(
|
||||||
|
importDeclaration.getStart(),
|
||||||
|
`import { ${operatorImportSpecifiers.join(
|
||||||
|
', '
|
||||||
|
)} } from '${newImportPath}';`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recorder.hasChanged()) {
|
||||||
|
recorder.applyChanges();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasTrailingComma(content: string, node: Node): boolean {
|
||||||
|
return content[node.getEnd()] === ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOperatorImport(importSpecifier: ImportSpecifier): boolean {
|
||||||
|
return dataPersistenceOperators.includes(
|
||||||
|
getOriginalIdentifierTextFromImportSpecifier(importSpecifier)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOriginalIdentifierTextFromImportSpecifier(
|
||||||
|
importSpecifier: ImportSpecifier
|
||||||
|
): string {
|
||||||
|
const children = importSpecifier.getChildren();
|
||||||
|
if (!children.length) {
|
||||||
|
return importSpecifier.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
return children[0].getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addNgrxRouterStoreIfNotInstalled(tree: Tree): void {
|
||||||
|
const { dependencies, devDependencies } = readJson(tree, 'package.json');
|
||||||
|
if (
|
||||||
|
dependencies?.['@ngrx/router-store'] ||
|
||||||
|
devDependencies?.['@ngrx/router-store']
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{ '@ngrx/router-store': versions(tree).ngrxVersion },
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterFilesWithNxAngularDep(files: FileData[]): FileData[] {
|
||||||
|
const filteredFiles: FileData[] = [];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (
|
||||||
|
file.deps?.some((dep) =>
|
||||||
|
angularPluginTargetNames.includes(fileDataDepTarget(dep))
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
filteredFiles.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredFiles;
|
||||||
|
}
|
||||||
@ -1,453 +0,0 @@
|
|||||||
import type { Type } from '@angular/core';
|
|
||||||
import type {
|
|
||||||
ActivatedRouteSnapshot,
|
|
||||||
RouterStateSnapshot,
|
|
||||||
} from '@angular/router';
|
|
||||||
import type { RouterNavigationAction } from '@ngrx/router-store';
|
|
||||||
import { ROUTER_NAVIGATION } from '@ngrx/router-store';
|
|
||||||
import type { Action } from '@ngrx/store';
|
|
||||||
import type { Observable } from 'rxjs';
|
|
||||||
import { isObservable, of } from 'rxjs';
|
|
||||||
import {
|
|
||||||
catchError,
|
|
||||||
concatMap,
|
|
||||||
filter,
|
|
||||||
groupBy,
|
|
||||||
map,
|
|
||||||
mergeMap,
|
|
||||||
switchMap,
|
|
||||||
} from 'rxjs/operators';
|
|
||||||
|
|
||||||
export interface PessimisticUpdateOpts<T extends Array<unknown>, A> {
|
|
||||||
run(a: A, ...slices: [...T]): Observable<Action> | Action | void;
|
|
||||||
onError(a: A, e: any): Observable<any> | any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OptimisticUpdateOpts<T extends Array<unknown>, A> {
|
|
||||||
run(a: A, ...slices: [...T]): Observable<Action> | Action | void;
|
|
||||||
undoAction(a: A, e: any): Observable<Action> | Action;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FetchOpts<T extends Array<unknown>, A> {
|
|
||||||
id?(a: A, ...slices: [...T]): any;
|
|
||||||
run(a: A, ...slices: [...T]): Observable<Action> | Action | void;
|
|
||||||
onError?(a: A, e: any): Observable<any> | any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HandleNavigationOpts<T extends Array<unknown>> {
|
|
||||||
run(
|
|
||||||
a: ActivatedRouteSnapshot,
|
|
||||||
...slices: [...T]
|
|
||||||
): Observable<Action> | Action | void;
|
|
||||||
onError?(a: ActivatedRouteSnapshot, e: any): Observable<any> | any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ActionOrActionWithStates<T extends Array<unknown>, A> =
|
|
||||||
| A
|
|
||||||
| [A, ...T];
|
|
||||||
export type ActionOrActionWithState<T, A> = ActionOrActionWithStates<[T], A>;
|
|
||||||
export type ActionStatesStream<T extends Array<unknown>, A> = Observable<
|
|
||||||
ActionOrActionWithStates<T, A>
|
|
||||||
>;
|
|
||||||
export type ActionStateStream<T, A> = Observable<
|
|
||||||
ActionOrActionWithStates<[T], A>
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @whatItDoes Handles pessimistic updates (updating the server first).
|
|
||||||
*
|
|
||||||
* Updating the server, when implemented naively, suffers from race conditions and poor error handling.
|
|
||||||
*
|
|
||||||
* `pessimisticUpdate` addresses these problems. It runs all fetches in order, which removes race conditions
|
|
||||||
* and forces the developer to handle errors.
|
|
||||||
*
|
|
||||||
* ## Example:
|
|
||||||
*
|
|
||||||
* ```typescript
|
|
||||||
* @Injectable()
|
|
||||||
* class TodoEffects {
|
|
||||||
* updateTodo$ = createEffect(() =>
|
|
||||||
* this.actions$.pipe(
|
|
||||||
* ofType('UPDATE_TODO'),
|
|
||||||
* pessimisticUpdate({
|
|
||||||
* // provides an action
|
|
||||||
* run: (action: UpdateTodo) => {
|
|
||||||
* // update the backend first, and then dispatch an action that will
|
|
||||||
* // update the client side
|
|
||||||
* return this.backend.updateTodo(action.todo.id, action.todo).pipe(
|
|
||||||
* map((updated) => ({
|
|
||||||
* type: 'UPDATE_TODO_SUCCESS',
|
|
||||||
* todo: updated,
|
|
||||||
* }))
|
|
||||||
* );
|
|
||||||
* },
|
|
||||||
* onError: (action: UpdateTodo, error: any) => {
|
|
||||||
* // we don't need to undo the changes on the client side.
|
|
||||||
* // we can dispatch an error, or simply log the error here and return `null`
|
|
||||||
* return null;
|
|
||||||
* },
|
|
||||||
* })
|
|
||||||
* )
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* constructor(private actions$: Actions, private backend: Backend) {}
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Note that if you don't return a new action from the run callback, you must set the dispatch property
|
|
||||||
* of the effect to false, like this:
|
|
||||||
*
|
|
||||||
* ```typescript
|
|
||||||
* class TodoEffects {
|
|
||||||
* updateTodo$ = createEffect(() =>
|
|
||||||
* this.actions$.pipe(
|
|
||||||
* //...
|
|
||||||
* ), { dispatch: false }
|
|
||||||
* );
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param opts
|
|
||||||
*
|
|
||||||
* @deprecated This will be removed in Nx v21. Import `pessimisticUpdate` from `@ngrx/router-store/data-persistence` instead.
|
|
||||||
*/
|
|
||||||
export function pessimisticUpdate<T extends Array<unknown>, A extends Action>(
|
|
||||||
opts: PessimisticUpdateOpts<T, A>
|
|
||||||
) {
|
|
||||||
return (source: ActionStatesStream<T, A>): Observable<Action> => {
|
|
||||||
return source.pipe(
|
|
||||||
mapActionAndState(),
|
|
||||||
concatMap(runWithErrorHandling(opts.run, opts.onError))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @whatItDoes Handles optimistic updates (updating the client first).
|
|
||||||
*
|
|
||||||
* It runs all fetches in order, which removes race conditions and forces the developer to handle errors.
|
|
||||||
*
|
|
||||||
* When using `optimisticUpdate`, in case of a failure, the developer has already updated the state locally,
|
|
||||||
* so the developer must provide an undo action.
|
|
||||||
*
|
|
||||||
* The error handling must be done in the callback, or by means of the undo action.
|
|
||||||
*
|
|
||||||
* ## Example:
|
|
||||||
*
|
|
||||||
* ```typescript
|
|
||||||
* @Injectable()
|
|
||||||
* class TodoEffects {
|
|
||||||
* updateTodo$ = createEffect(() =>
|
|
||||||
* this.actions$.pipe(
|
|
||||||
* ofType('UPDATE_TODO'),
|
|
||||||
* optimisticUpdate({
|
|
||||||
* // provides an action
|
|
||||||
* run: (action: UpdateTodo) => {
|
|
||||||
* return this.backend.updateTodo(action.todo.id, action.todo).pipe(
|
|
||||||
* mapTo({
|
|
||||||
* type: 'UPDATE_TODO_SUCCESS',
|
|
||||||
* })
|
|
||||||
* );
|
|
||||||
* },
|
|
||||||
* undoAction: (action: UpdateTodo, error: any) => {
|
|
||||||
* // dispatch an undo action to undo the changes in the client state
|
|
||||||
* return {
|
|
||||||
* type: 'UNDO_TODO_UPDATE',
|
|
||||||
* todo: action.todo,
|
|
||||||
* };
|
|
||||||
* },
|
|
||||||
* })
|
|
||||||
* )
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* constructor(private actions$: Actions, private backend: Backend) {}
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* Note that if you don't return a new action from the run callback, you must set the dispatch property
|
|
||||||
* of the effect to false, like this:
|
|
||||||
*
|
|
||||||
* ```typescript
|
|
||||||
* class TodoEffects {
|
|
||||||
* updateTodo$ = createEffect(() =>
|
|
||||||
* this.actions$.pipe(
|
|
||||||
* //...
|
|
||||||
* ), { dispatch: false }
|
|
||||||
* );
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param opts
|
|
||||||
*
|
|
||||||
* @deprecated This will be removed in Nx v21. Import `optimisticUpdate` from `@ngrx/router-store/data-persistence` instead.
|
|
||||||
*/
|
|
||||||
export function optimisticUpdate<T extends Array<unknown>, A extends Action>(
|
|
||||||
opts: OptimisticUpdateOpts<T, A>
|
|
||||||
) {
|
|
||||||
return (source: ActionStatesStream<T, A>): Observable<Action> => {
|
|
||||||
return source.pipe(
|
|
||||||
mapActionAndState(),
|
|
||||||
concatMap(runWithErrorHandling(opts.run, opts.undoAction))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @whatItDoes Handles data fetching.
|
|
||||||
*
|
|
||||||
* Data fetching implemented naively suffers from race conditions and poor error handling.
|
|
||||||
*
|
|
||||||
* `fetch` addresses these problems. It runs all fetches in order, which removes race conditions
|
|
||||||
* and forces the developer to handle errors.
|
|
||||||
*
|
|
||||||
* ## Example:
|
|
||||||
*
|
|
||||||
* ```typescript
|
|
||||||
* @Injectable()
|
|
||||||
* class TodoEffects {
|
|
||||||
* loadTodos$ = createEffect(() =>
|
|
||||||
* this.actions$.pipe(
|
|
||||||
* ofType('GET_TODOS'),
|
|
||||||
* fetch({
|
|
||||||
* // provides an action
|
|
||||||
* run: (a: GetTodos) => {
|
|
||||||
* return this.backend.getAll().pipe(
|
|
||||||
* map((response) => ({
|
|
||||||
* type: 'TODOS',
|
|
||||||
* todos: response.todos,
|
|
||||||
* }))
|
|
||||||
* );
|
|
||||||
* },
|
|
||||||
* onError: (action: GetTodos, error: any) => {
|
|
||||||
* // dispatch an undo action to undo the changes in the client state
|
|
||||||
* return null;
|
|
||||||
* },
|
|
||||||
* })
|
|
||||||
* )
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* constructor(private actions$: Actions, private backend: Backend) {}
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This is correct, but because it set the concurrency to 1, it may not be performant.
|
|
||||||
*
|
|
||||||
* To fix that, you can provide the `id` function, like this:
|
|
||||||
*
|
|
||||||
* ```typescript
|
|
||||||
* @Injectable()
|
|
||||||
* class TodoEffects {
|
|
||||||
* loadTodo$ = createEffect(() =>
|
|
||||||
* this.actions$.pipe(
|
|
||||||
* ofType('GET_TODO'),
|
|
||||||
* fetch({
|
|
||||||
* id: (todo: GetTodo) => {
|
|
||||||
* return todo.id;
|
|
||||||
* },
|
|
||||||
* // provides an action
|
|
||||||
* run: (todo: GetTodo) => {
|
|
||||||
* return this.backend.getTodo(todo.id).map((response) => ({
|
|
||||||
* type: 'LOAD_TODO_SUCCESS',
|
|
||||||
* todo: response.todo,
|
|
||||||
* }));
|
|
||||||
* },
|
|
||||||
* onError: (action: GetTodo, error: any) => {
|
|
||||||
* // dispatch an undo action to undo the changes in the client state
|
|
||||||
* return null;
|
|
||||||
* },
|
|
||||||
* })
|
|
||||||
* )
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* constructor(private actions$: Actions, private backend: Backend) {}
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* With this setup, the requests for Todo 1 will run concurrently with the requests for Todo 2.
|
|
||||||
*
|
|
||||||
* In addition, if there are multiple requests for Todo 1 scheduled, it will only run the last one.
|
|
||||||
*
|
|
||||||
* @param opts
|
|
||||||
*
|
|
||||||
* @deprecated This will be removed in Nx v21. Import `fetch` from `@ngrx/router-store/data-persistence` instead.
|
|
||||||
*/
|
|
||||||
export function fetch<T extends Array<unknown>, A extends Action>(
|
|
||||||
opts: FetchOpts<T, A>
|
|
||||||
) {
|
|
||||||
return (source: ActionStatesStream<T, A>): Observable<Action> => {
|
|
||||||
if (opts.id) {
|
|
||||||
const groupedFetches = source.pipe(
|
|
||||||
mapActionAndState(),
|
|
||||||
groupBy(([action, ...store]) => {
|
|
||||||
return opts.id(action, ...store);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return groupedFetches.pipe(
|
|
||||||
mergeMap((pairs) =>
|
|
||||||
pairs.pipe(switchMap(runWithErrorHandling(opts.run, opts.onError)))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return source.pipe(
|
|
||||||
mapActionAndState(),
|
|
||||||
concatMap(runWithErrorHandling(opts.run, opts.onError))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @whatItDoes Handles data fetching as part of router navigation.
|
|
||||||
*
|
|
||||||
* Data fetching implemented naively suffers from race conditions and poor error handling.
|
|
||||||
*
|
|
||||||
* `navigation` addresses these problems.
|
|
||||||
*
|
|
||||||
* It checks if an activated router state contains the passed in component type, and, if it does, runs the `run`
|
|
||||||
* callback. It provides the activated snapshot associated with the component and the current state. And it only runs
|
|
||||||
* the last request.
|
|
||||||
*
|
|
||||||
* ## Example:
|
|
||||||
*
|
|
||||||
* ```typescript
|
|
||||||
* @Injectable()
|
|
||||||
* class TodoEffects {
|
|
||||||
* loadTodo$ = createEffect(() =>
|
|
||||||
* this.actions$.pipe(
|
|
||||||
* // listens for the routerNavigation action from @ngrx/router-store
|
|
||||||
* navigation(TodoComponent, {
|
|
||||||
* run: (activatedRouteSnapshot: ActivatedRouteSnapshot) => {
|
|
||||||
* return this.backend
|
|
||||||
* .fetchTodo(activatedRouteSnapshot.params['id'])
|
|
||||||
* .pipe(
|
|
||||||
* map((todo) => ({
|
|
||||||
* type: 'LOAD_TODO_SUCCESS',
|
|
||||||
* todo: todo,
|
|
||||||
* }))
|
|
||||||
* );
|
|
||||||
* },
|
|
||||||
* onError: (
|
|
||||||
* activatedRouteSnapshot: ActivatedRouteSnapshot,
|
|
||||||
* error: any
|
|
||||||
* ) => {
|
|
||||||
* // we can log and error here and return null
|
|
||||||
* // we can also navigate back
|
|
||||||
* return null;
|
|
||||||
* },
|
|
||||||
* })
|
|
||||||
* )
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* constructor(private actions$: Actions, private backend: Backend) {}
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param component
|
|
||||||
* @param opts
|
|
||||||
*
|
|
||||||
* @deprecated This will be removed in Nx v21. Import `navigation` from `@ngrx/router-store/data-persistence` instead.
|
|
||||||
*/
|
|
||||||
export function navigation<T extends Array<unknown>, A extends Action>(
|
|
||||||
component: Type<any>,
|
|
||||||
opts: HandleNavigationOpts<T>
|
|
||||||
) {
|
|
||||||
return (source: ActionStatesStream<T, A>) => {
|
|
||||||
const nav = source.pipe(
|
|
||||||
mapActionAndState(),
|
|
||||||
filter(([action]) => isStateSnapshot(action)),
|
|
||||||
map(([action, ...slices]) => {
|
|
||||||
if (!isStateSnapshot(action)) {
|
|
||||||
// Because of the above filter we'll never get here,
|
|
||||||
// but this properly type narrows `action`
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
findSnapshot(component, action.payload.routerState.root),
|
|
||||||
...slices,
|
|
||||||
] as [ActivatedRouteSnapshot, ...T];
|
|
||||||
}),
|
|
||||||
filter(([snapshot]) => !!snapshot)
|
|
||||||
);
|
|
||||||
|
|
||||||
return nav.pipe(switchMap(runWithErrorHandling(opts.run, opts.onError)));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function isStateSnapshot(
|
|
||||||
action: any
|
|
||||||
): action is RouterNavigationAction<RouterStateSnapshot> {
|
|
||||||
return action.type === ROUTER_NAVIGATION;
|
|
||||||
}
|
|
||||||
|
|
||||||
function runWithErrorHandling<T extends Array<unknown>, A, R>(
|
|
||||||
run: (a: A, ...slices: [...T]) => Observable<R> | R | void,
|
|
||||||
onError: any
|
|
||||||
) {
|
|
||||||
return ([action, ...slices]: [A, ...T]): Observable<R> => {
|
|
||||||
try {
|
|
||||||
const r = wrapIntoObservable(run(action, ...slices));
|
|
||||||
return r.pipe(catchError((e) => wrapIntoObservable(onError(action, e))));
|
|
||||||
} catch (e) {
|
|
||||||
return wrapIntoObservable(onError(action, e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @whatItDoes maps Observable<Action | [Action, State]> to
|
|
||||||
* Observable<[Action, State]>
|
|
||||||
*/
|
|
||||||
function mapActionAndState<T extends Array<unknown>, A>() {
|
|
||||||
return (source: Observable<ActionOrActionWithStates<T, A>>) => {
|
|
||||||
return source.pipe(
|
|
||||||
map((value) => normalizeActionAndState(value) as [A, ...T])
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @whatItDoes Normalizes either a bare action or an array of action and slices
|
|
||||||
* into an array of action and slices (or undefined)
|
|
||||||
*/
|
|
||||||
function normalizeActionAndState<T extends Array<unknown>, A>(
|
|
||||||
args: ActionOrActionWithStates<T, A>
|
|
||||||
): [A, ...T] {
|
|
||||||
let action: A, slices: T;
|
|
||||||
|
|
||||||
if (args instanceof Array) {
|
|
||||||
[action, ...slices] = args;
|
|
||||||
} else {
|
|
||||||
slices = [] as T;
|
|
||||||
action = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [action, ...slices];
|
|
||||||
}
|
|
||||||
|
|
||||||
function findSnapshot(
|
|
||||||
component: Type<any>,
|
|
||||||
s: ActivatedRouteSnapshot
|
|
||||||
): ActivatedRouteSnapshot {
|
|
||||||
if (s.routeConfig && s.routeConfig.component === component) {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
for (const c of s.children) {
|
|
||||||
const ss = findSnapshot(component, c);
|
|
||||||
if (ss) {
|
|
||||||
return ss;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function wrapIntoObservable<O>(obj: Observable<O> | O | void): Observable<O> {
|
|
||||||
if (isObservable(obj)) {
|
|
||||||
return obj;
|
|
||||||
} else if (!obj) {
|
|
||||||
return of();
|
|
||||||
} else {
|
|
||||||
return of(obj as O);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -429,24 +429,6 @@ describe('Angular AST Utils', () => {
|
|||||||
|
|
||||||
expect(isStandalone(tree, tsSourceFile, 'Pipe')).toBe(false);
|
expect(isStandalone(tree, tsSourceFile, 'Pipe')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support the legacy signature without a Tree', () => {
|
|
||||||
const componentSourceText = `import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
standalone: true
|
|
||||||
})
|
|
||||||
export class MyComponent {}
|
|
||||||
`;
|
|
||||||
const tsSourceFile = createSourceFile(
|
|
||||||
'my.component.ts',
|
|
||||||
componentSourceText,
|
|
||||||
ScriptTarget.Latest,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(isStandalone(tsSourceFile, 'Component')).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add a provider to the bootstrapApplication call', () => {
|
it('should add a provider to the bootstrapApplication call', () => {
|
||||||
|
|||||||
@ -72,17 +72,6 @@ function _angularImportsFromNode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the Component, Directive or Pipe is standalone
|
|
||||||
* @param sourceFile TS Source File containing the token to check
|
|
||||||
* @param decoratorName The type of decorator to check (Component, Directive, Pipe)
|
|
||||||
*
|
|
||||||
* @deprecated Use the function signature with a Tree. This signature will be removed in v21.
|
|
||||||
*/
|
|
||||||
export function isStandalone(
|
|
||||||
sourceFile: ts.SourceFile,
|
|
||||||
decoratorName: DecoratorName
|
|
||||||
): boolean;
|
|
||||||
/**
|
/**
|
||||||
* Check if the Component, Directive or Pipe is standalone
|
* Check if the Component, Directive or Pipe is standalone
|
||||||
* @param tree The file system tree
|
* @param tree The file system tree
|
||||||
@ -93,23 +82,7 @@ export function isStandalone(
|
|||||||
tree: Tree,
|
tree: Tree,
|
||||||
sourceFile: ts.SourceFile,
|
sourceFile: ts.SourceFile,
|
||||||
decoratorName: DecoratorName
|
decoratorName: DecoratorName
|
||||||
): boolean;
|
|
||||||
export function isStandalone(
|
|
||||||
treeOrSourceFile: Tree | ts.SourceFile,
|
|
||||||
sourceFileOrDecoratorName: ts.SourceFile | DecoratorName,
|
|
||||||
decoratorName?: DecoratorName
|
|
||||||
): boolean {
|
): boolean {
|
||||||
let tree: Tree;
|
|
||||||
let sourceFile: ts.SourceFile;
|
|
||||||
if (decoratorName === undefined) {
|
|
||||||
sourceFile = treeOrSourceFile as ts.SourceFile;
|
|
||||||
decoratorName = sourceFileOrDecoratorName as DecoratorName;
|
|
||||||
} else {
|
|
||||||
tree = treeOrSourceFile as Tree;
|
|
||||||
sourceFile = sourceFileOrDecoratorName as ts.SourceFile;
|
|
||||||
decoratorName = decoratorName as DecoratorName;
|
|
||||||
}
|
|
||||||
|
|
||||||
const decoratorMetadata = getDecoratorMetadata(
|
const decoratorMetadata = getDecoratorMetadata(
|
||||||
sourceFile,
|
sourceFile,
|
||||||
decoratorName,
|
decoratorName,
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
export { readAll, readFirst } from './src/testing-utils';
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
|
|
||||||
"lib": {
|
|
||||||
"entryFile": "index.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import type { Observable } from 'rxjs';
|
|
||||||
import { first, toArray } from 'rxjs/operators';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated This will be removed in Nx v21. If using RxJS 7, use `firstValueFrom(obs$.pipe(toArray()))`
|
|
||||||
* or `lastValueFrom(obs$.pipe(toArray()))`. If using RxJS 6, use `obs$.pipe(toArray()).toPromise()`.
|
|
||||||
*
|
|
||||||
* @whatItDoes reads all the values from an observable and returns a promise
|
|
||||||
* with an array of all values. This should be used in combination with async/await.
|
|
||||||
*
|
|
||||||
* ## Example
|
|
||||||
*
|
|
||||||
* ```typescript
|
|
||||||
* const obs = of(1, 2, 3, 4);
|
|
||||||
* const res = await readAll(obs)
|
|
||||||
* expect(res).toEqual([1, 2, 3, 4]);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function readAll<T>(o: Observable<T>): Promise<T[]> {
|
|
||||||
return o.pipe(toArray()).toPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated This will be removed in Nx v21. Since RxJS 7, use `firstValueFrom(obs$)`. If using RxJS 6,
|
|
||||||
* use `obs$.pipe(first()).toPromise()`.
|
|
||||||
*
|
|
||||||
* @whatItDoes reads the first value from an observable and returns a promise
|
|
||||||
* with it. This should be used in combination with async/await.
|
|
||||||
*
|
|
||||||
* ## Example
|
|
||||||
*
|
|
||||||
* ```typescript
|
|
||||||
* const obs = of(1, 2, 3, 4);
|
|
||||||
* const res = await readFirst(obs)
|
|
||||||
* expect(res).toEqual(1);
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function readFirst<T>(o: Observable<T>): Promise<T> {
|
|
||||||
return o.pipe(first()).toPromise();
|
|
||||||
}
|
|
||||||
36
pnpm-lock.yaml
generated
36
pnpm-lock.yaml
generated
@ -298,12 +298,6 @@ importers:
|
|||||||
'@nestjs/testing':
|
'@nestjs/testing':
|
||||||
specifier: ^9.0.0
|
specifier: ^9.0.0
|
||||||
version: 9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3))
|
version: 9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3))
|
||||||
'@ngrx/router-store':
|
|
||||||
specifier: 19.0.0
|
|
||||||
version: 19.0.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/router@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.5(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1))(@ngrx/store@19.0.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(rxjs@7.8.1)
|
|
||||||
'@ngrx/store':
|
|
||||||
specifier: 19.0.0
|
|
||||||
version: 19.0.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1)
|
|
||||||
'@notionhq/client':
|
'@notionhq/client':
|
||||||
specifier: ^2.2.15
|
specifier: ^2.2.15
|
||||||
version: 2.2.15(encoding@0.1.13)
|
version: 2.2.15(encoding@0.1.13)
|
||||||
@ -5605,21 +5599,6 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@ngrx/router-store@19.0.0':
|
|
||||||
resolution: {integrity: sha512-kkgxXPK2xYEh5HRk323dFbsF0LSAsNiEWUg0oH5WLwy2cgnlmyoJ1QRToTgH+B76Bbd1NRMTITZJtNIOakj1Pg==}
|
|
||||||
peerDependencies:
|
|
||||||
'@angular/common': ^19.0.0
|
|
||||||
'@angular/core': ^19.0.0
|
|
||||||
'@angular/router': ^19.0.0
|
|
||||||
'@ngrx/store': 19.0.0
|
|
||||||
rxjs: ^6.5.3 || ^7.5.0
|
|
||||||
|
|
||||||
'@ngrx/store@19.0.0':
|
|
||||||
resolution: {integrity: sha512-AaryTJF1DsXUVWFhCl833LhvjyPjDOAMX9tqGBDfYGhaNOWWX3q/3z5HQ0XCrJ8kDJN3EHiNQa3XHxFaFQUo9A==}
|
|
||||||
peerDependencies:
|
|
||||||
'@angular/core': ^19.0.0
|
|
||||||
rxjs: ^6.5.3 || ^7.5.0
|
|
||||||
|
|
||||||
'@ngtools/webpack@19.2.0':
|
'@ngtools/webpack@19.2.0':
|
||||||
resolution: {integrity: sha512-63/8ys3bNK2h7Py/dKHZ4ZClxQz6xuz3skUgLZIMs9O076KPsHTKDKEDG2oicmwe/nOXjVt6n9Z4wprFaRLbvw==}
|
resolution: {integrity: sha512-63/8ys3bNK2h7Py/dKHZ4ZClxQz6xuz3skUgLZIMs9O076KPsHTKDKEDG2oicmwe/nOXjVt6n9Z4wprFaRLbvw==}
|
||||||
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
||||||
@ -25623,21 +25602,6 @@ snapshots:
|
|||||||
'@next/swc-win32-x64-msvc@14.2.28':
|
'@next/swc-win32-x64-msvc@14.2.28':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@ngrx/router-store@19.0.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/router@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.5(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1))(@ngrx/store@19.0.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(rxjs@7.8.1)':
|
|
||||||
dependencies:
|
|
||||||
'@angular/common': 19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1)
|
|
||||||
'@angular/core': 19.2.0(rxjs@7.8.1)(zone.js@0.14.10)
|
|
||||||
'@angular/router': 19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.5(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1)
|
|
||||||
'@ngrx/store': 19.0.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1)
|
|
||||||
rxjs: 7.8.1
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@ngrx/store@19.0.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1)':
|
|
||||||
dependencies:
|
|
||||||
'@angular/core': 19.2.0(rxjs@7.8.1)(zone.js@0.14.10)
|
|
||||||
rxjs: 7.8.1
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@ngtools/webpack@19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3))(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))':
|
'@ngtools/webpack@19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3))(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3)
|
'@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user