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:
Leosvel Pérez Espinosa 2025-04-17 15:12:32 +02:00 committed by GitHub
parent 5c30d1b95a
commit 3eb9f6a822
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 733 additions and 3664 deletions

View File

@ -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"

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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,

View File

@ -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"
}

View File

@ -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": ""
}

View File

@ -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": ""
}

View File

@ -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": ""
}

View File

@ -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": ""
}

View File

@ -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": ""
}

View File

@ -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": ""
}

View File

@ -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": ""
}

View File

@ -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": ""
}

View File

@ -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": ""
}

View File

@ -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",

View File

@ -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",

View File

@ -1,6 +1 @@
export { export {};
fetch,
navigation,
optimisticUpdate,
pessimisticUpdate,
} from './src/runtime/nx/data-persistence';

View File

@ -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": {

View File

@ -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",

View File

@ -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();
// });
// });
// });
// });

View File

@ -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);
});
});
});

View File

@ -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;
}; };

View File

@ -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",

View File

@ -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;
}; };

View File

@ -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}`

View File

@ -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;
}; };

View File

@ -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",

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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": [

View File

@ -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();
});
});

View File

@ -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);
}

View File

@ -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",
},
}
`);
});
});

View File

@ -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;
}

View File

@ -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();
});
});
});

View File

@ -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;
}

View File

@ -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",
},
}
`);
});
});

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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'
);
});
});

View File

@ -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);
}

View File

@ -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'`
);
});
});
});

View File

@ -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);
}

View File

@ -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);
});
});

View File

@ -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);
}
}

View File

@ -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);
});
});

View File

@ -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);
}

View File

@ -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[] {

View File

@ -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 %}

View File

@ -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 {}
"
`);
});
});

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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', () => {

View File

@ -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,

View File

@ -1 +0,0 @@
export { readAll, readFirst } from './src/testing-utils';

View File

@ -1,6 +0,0 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "index.ts"
}
}

View File

@ -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
View File

@ -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)