@nx/angular 23.1.0-pr.36127.8b9019b → 23.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. package/dist/src/generators/application/lib/add-e2e.js +4 -6
  2. package/dist/src/generators/cypress-component-configuration/cypress-component-configuration.js +2 -2
  3. package/dist/src/generators/ng-add/migrators/migrator.js +10 -5
  4. package/dist/src/generators/setup-ssr/lib/update-project-config.js +11 -7
  5. package/dist/src/generators/utils/add-mf-env-to-inputs.js +15 -9
  6. package/dist/src/generators/utils/add-vitest.js +13 -9
  7. package/dist/src/migrations/update-19-6-1/ensure-depends-on-for-mf.js +24 -18
  8. package/dist/src/migrations/update-20-2-0/remove-tailwind-config-from-ng-packagr-executors.js +18 -8
  9. package/dist/src/migrations/update-23-1-0/add-angular-build.d.ts +2 -0
  10. package/dist/src/migrations/update-23-1-0/add-angular-build.js +53 -0
  11. package/dist/src/migrations/update-23-1-0/add-angular-build.md +61 -0
  12. package/dist/src/migrations/update-23-1-0/add-istanbul-instrumenter.js +7 -3
  13. package/dist/src/migrations/update-23-1-0/remove-conflicting-extended-diagnostics.d.ts +2 -0
  14. package/dist/src/migrations/update-23-1-0/remove-conflicting-extended-diagnostics.js +119 -0
  15. package/dist/src/migrations/update-23-1-0/remove-conflicting-extended-diagnostics.md +37 -0
  16. package/dist/src/migrations/update-23-1-0/rename-ssr-experimental-platform.js +6 -1
  17. package/dist/src/migrations/update-23-1-0/rename-ssr-experimental-platform.md +8 -10
  18. package/dist/src/utils/versions.d.ts +2 -2
  19. package/dist/src/utils/versions.js +2 -2
  20. package/migrations.json +18 -0
  21. package/package.json +12 -11
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.addE2e = addE2e;
4
+ const internal_1 = require("@nx/devkit/internal");
4
5
  const devkit_1 = require("@nx/devkit");
5
6
  const versions_1 = require("../../../utils/versions");
6
7
  async function addE2e(tree, options) {
@@ -65,12 +66,9 @@ async function addE2e(tree, options) {
65
66
  function getAngularE2EWebServerInfo(tree, projectName, portOverride) {
66
67
  const nxJson = (0, devkit_1.readNxJson)(tree);
67
68
  let e2ePort = portOverride ?? 4200;
68
- if (nxJson.targetDefaults?.['serve'] &&
69
- (nxJson.targetDefaults?.['serve'].options?.port ||
70
- nxJson.targetDefaults?.['serve'].options?.env?.PORT)) {
71
- e2ePort =
72
- nxJson.targetDefaults?.['serve'].options?.port ||
73
- nxJson.targetDefaults?.['serve'].options?.env?.PORT;
69
+ const serveTargetOptions = (0, internal_1.readTargetDefaultsForTarget)('serve', nxJson.targetDefaults)?.options;
70
+ if (serveTargetOptions?.port || serveTargetOptions?.env?.PORT) {
71
+ e2ePort = serveTargetOptions.port || serveTargetOptions.env.PORT;
74
72
  }
75
73
  const pm = (0, devkit_1.getPackageManagerCommand)();
76
74
  return {
@@ -148,9 +148,9 @@ async function configureCypressCT(tree, options) {
148
148
  else {
149
149
  ctConfigOptions.buildTarget = found.target;
150
150
  }
151
- const { addDefaultCTConfig, getProjectCypressConfigPath } = require('@nx/cypress/internal');
151
+ const { addDefaultCTConfig, getProjectCypressConfigPath, getInstalledCypressMajorVersion, } = require('@nx/cypress/internal');
152
152
  const cypressConfigPath = getProjectCypressConfigPath(tree, projectConfig.root);
153
- const updatedCyConfig = await addDefaultCTConfig(tree.read(cypressConfigPath, 'utf-8'), ctConfigOptions, '@nx/angular/plugins/component-testing');
153
+ const updatedCyConfig = await addDefaultCTConfig(tree.read(cypressConfigPath, 'utf-8'), ctConfigOptions, '@nx/angular/plugins/component-testing', getInstalledCypressMajorVersion(tree));
154
154
  tree.write(cypressConfigPath, updatedCyConfig);
155
155
  }
156
156
  exports.default = cypressComponentConfiguration;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Migrator = void 0;
4
4
  const devkit_1 = require("@nx/devkit");
5
+ const internal_1 = require("@nx/devkit/internal");
5
6
  const path_1 = require("path");
6
7
  class Migrator {
7
8
  constructor(tree, projectConfig, logger) {
@@ -66,11 +67,15 @@ class Migrator {
66
67
  if (!targetNames.length) {
67
68
  return;
68
69
  }
69
- const nxJson = (0, devkit_1.readNxJson)(this.tree);
70
- nxJson.targetDefaults ??= {};
71
- for (const target of targetNames) {
72
- nxJson.targetDefaults[target] ??= {};
73
- nxJson.targetDefaults[target].cache ??= true;
70
+ const nxJson = (0, devkit_1.readNxJson)(this.tree) ?? {};
71
+ for (const name of targetNames) {
72
+ const existing = (0, internal_1.findTargetDefault)(nxJson.targetDefaults, {
73
+ target: name,
74
+ });
75
+ (0, internal_1.upsertTargetDefault)(this.tree, nxJson, {
76
+ target: name,
77
+ cache: existing?.cache ?? true,
78
+ });
74
79
  }
75
80
  (0, devkit_1.updateNxJson)(this.tree, nxJson);
76
81
  }
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.updateProjectConfigForApplicationBuilder = updateProjectConfigForApplicationBuilder;
4
4
  exports.updateProjectConfigForBrowserBuilder = updateProjectConfigForBrowserBuilder;
5
5
  const devkit_1 = require("@nx/devkit");
6
- const internal_1 = require("@nx/js/internal");
6
+ const internal_1 = require("@nx/devkit/internal");
7
+ const internal_2 = require("@nx/js/internal");
7
8
  const constants_1 = require("./constants");
8
9
  function updateProjectConfigForApplicationBuilder(tree, options) {
9
10
  const project = (0, devkit_1.readProjectConfiguration)(tree, options.project);
@@ -28,7 +29,7 @@ function updateProjectConfigForApplicationBuilder(tree, options) {
28
29
  }
29
30
  }
30
31
  }
31
- const sourceRoot = (0, internal_1.getProjectSourceRoot)(project, tree);
32
+ const sourceRoot = (0, internal_2.getProjectSourceRoot)(project, tree);
32
33
  buildTarget.options ??= {};
33
34
  buildTarget.options.outputPath = outputPath;
34
35
  buildTarget.options.server = (0, devkit_1.joinPathFragments)(sourceRoot, options.main);
@@ -50,7 +51,7 @@ function updateProjectConfigForBrowserBuilder(tree, options) {
50
51
  configurations[key] = getServerOptions(options);
51
52
  }
52
53
  }
53
- const sourceRoot = (0, internal_1.getProjectSourceRoot)(projectConfig, tree);
54
+ const sourceRoot = (0, internal_2.getProjectSourceRoot)(projectConfig, tree);
54
55
  projectConfig.targets.server = {
55
56
  dependsOn: ['build'],
56
57
  executor: buildTarget.executor.startsWith('@angular-devkit/build-angular:')
@@ -98,14 +99,17 @@ function updateProjectConfigForBrowserBuilder(tree, options) {
98
99
  defaultConfiguration: 'production',
99
100
  };
100
101
  (0, devkit_1.updateProjectConfiguration)(tree, options.project, projectConfig);
101
- const nxJson = (0, devkit_1.readNxJson)(tree);
102
+ const nxJson = (0, devkit_1.readNxJson)(tree) ?? {};
102
103
  if (nxJson.tasksRunnerOptions?.default?.options?.cacheableOperations &&
103
104
  !nxJson.tasksRunnerOptions.default.options.cacheableOperations.includes('server')) {
104
105
  nxJson.tasksRunnerOptions.default.options.cacheableOperations.push('server');
105
106
  }
106
- nxJson.targetDefaults ??= {};
107
- nxJson.targetDefaults.server ??= {};
108
- nxJson.targetDefaults.server.cache ??= true;
107
+ const existing = (0, internal_1.findTargetDefault)(nxJson.targetDefaults, {
108
+ target: 'server',
109
+ });
110
+ if (!existing || existing.cache === undefined) {
111
+ (0, internal_1.upsertTargetDefault)(tree, nxJson, { target: 'server', cache: true });
112
+ }
109
113
  (0, devkit_1.updateNxJson)(tree, nxJson);
110
114
  }
111
115
  function getServerOptions(options = {}) {
@@ -2,29 +2,35 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.addMfEnvToTargetDefaultInputs = addMfEnvToTargetDefaultInputs;
4
4
  const devkit_1 = require("@nx/devkit");
5
+ const internal_1 = require("@nx/devkit/internal");
5
6
  function addMfEnvToTargetDefaultInputs(tree) {
6
- const nxJson = (0, devkit_1.readNxJson)(tree);
7
+ const nxJson = (0, devkit_1.readNxJson)(tree) ?? {};
7
8
  const webpackExecutor = '@nx/angular:webpack-browser';
8
9
  const mfEnvVar = 'NX_MF_DEV_REMOTES';
9
- const inputs = [
10
+ const defaultInputs = [
10
11
  ...(nxJson.namedInputs && 'production' in nxJson.namedInputs
11
12
  ? ['production', '^production']
12
13
  : ['default', '^default']),
13
14
  ];
14
- nxJson.targetDefaults ??= {};
15
- nxJson.targetDefaults[webpackExecutor] ??= {};
16
- nxJson.targetDefaults[webpackExecutor].inputs ??= inputs;
17
- nxJson.targetDefaults[webpackExecutor].dependsOn ??= ['^build'];
15
+ const existing = (0, internal_1.findTargetDefault)(nxJson.targetDefaults, {
16
+ executor: webpackExecutor,
17
+ });
18
+ const inputs = [...(existing?.inputs ?? defaultInputs)];
18
19
  let mfEnvVarExists = false;
19
- for (const input of nxJson.targetDefaults[webpackExecutor].inputs) {
20
+ for (const input of inputs) {
20
21
  if (typeof input === 'object' && input['env'] === mfEnvVar) {
21
22
  mfEnvVarExists = true;
22
23
  break;
23
24
  }
24
25
  }
25
26
  if (!mfEnvVarExists) {
26
- nxJson.targetDefaults[webpackExecutor].inputs.push({ env: mfEnvVar });
27
+ inputs.push({ env: mfEnvVar });
27
28
  }
28
- nxJson.targetDefaults[webpackExecutor].cache = true;
29
+ (0, internal_1.upsertTargetDefault)(tree, nxJson, {
30
+ executor: webpackExecutor,
31
+ cache: true,
32
+ inputs,
33
+ dependsOn: existing?.dependsOn ?? ['^build'],
34
+ });
29
35
  (0, devkit_1.updateNxJson)(tree, nxJson);
30
36
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.addVitestAngular = addVitestAngular;
4
4
  exports.addVitestAnalog = addVitestAnalog;
5
5
  const devkit_1 = require("@nx/devkit");
6
+ const internal_1 = require("@nx/devkit/internal");
6
7
  const devkit_internals_1 = require("nx/src/devkit-internals");
7
8
  const semver_1 = require("semver");
8
9
  const versions_1 = require("../../utils/versions");
@@ -16,15 +17,18 @@ async function addVitestAngular(tree, options) {
16
17
  project.targets ??= {};
17
18
  project.targets.test = { executor, options: { watch: false } };
18
19
  (0, devkit_1.updateProjectConfiguration)(tree, options.name, project);
19
- const nxJson = (0, devkit_1.readNxJson)(tree);
20
- nxJson.targetDefaults ??= {};
21
- nxJson.targetDefaults[executor] ??= {
22
- cache: true,
23
- inputs: nxJson.namedInputs && 'production' in nxJson.namedInputs
24
- ? ['default', '^production']
25
- : ['default', '^default'],
26
- };
27
- (0, devkit_1.updateNxJson)(tree, nxJson);
20
+ const nxJson = (0, devkit_1.readNxJson)(tree) ?? {};
21
+ const existing = (0, internal_1.findTargetDefault)(nxJson.targetDefaults, { executor });
22
+ if (!existing) {
23
+ (0, internal_1.upsertTargetDefault)(tree, nxJson, {
24
+ executor,
25
+ cache: true,
26
+ inputs: nxJson.namedInputs && 'production' in nxJson.namedInputs
27
+ ? ['default', '^production']
28
+ : ['default', '^default'],
29
+ });
30
+ (0, devkit_1.updateNxJson)(tree, nxJson);
31
+ }
28
32
  configureTypeScriptForVitest(tree, options.projectRoot);
29
33
  addVitestScreenshotsToGitIgnore(tree);
30
34
  if (options.skipPackageJson) {
@@ -5,7 +5,7 @@ const devkit_1 = require("@nx/devkit");
5
5
  const internal_1 = require("@nx/devkit/internal");
6
6
  async function default_1(tree) {
7
7
  let usesModuleFederation = false;
8
- (0, internal_1.forEachExecutorOptions)(tree, '@nx/angular:webpack-browser', (options, projectName, targetName) => {
8
+ (0, internal_1.forEachExecutorOptions)(tree, '@nx/angular:webpack-browser', (options) => {
9
9
  const webpackConfig = options.webpackConfig;
10
10
  if (!webpackConfig) {
11
11
  return;
@@ -18,34 +18,40 @@ async function default_1(tree) {
18
18
  if (!usesModuleFederation) {
19
19
  return;
20
20
  }
21
- const nxJson = (0, devkit_1.readNxJson)(tree);
21
+ const nxJson = (0, devkit_1.readNxJson)(tree) ?? {};
22
22
  const nxMFDevRemotesEnvVar = 'NX_MF_DEV_REMOTES';
23
- const inputs = [
23
+ const webpackExecutor = '@nx/angular:webpack-browser';
24
+ const defaultInputs = [
24
25
  ...(nxJson.namedInputs && 'production' in nxJson.namedInputs
25
26
  ? ['production', '^production']
26
27
  : ['default', '^default']),
27
28
  { env: nxMFDevRemotesEnvVar },
28
29
  ];
29
- if (!nxJson.targetDefaults ||
30
- !nxJson.targetDefaults?.['@nx/angular:webpack-browser']) {
31
- nxJson.targetDefaults ??= {};
32
- nxJson.targetDefaults['@nx/angular:webpack-browser'] = {
30
+ const existing = (0, internal_1.findTargetDefault)(nxJson.targetDefaults, {
31
+ executor: webpackExecutor,
32
+ });
33
+ if (!existing) {
34
+ (0, internal_1.upsertTargetDefault)(tree, nxJson, {
35
+ executor: webpackExecutor,
33
36
  cache: true,
34
- inputs,
37
+ inputs: defaultInputs,
35
38
  dependsOn: ['^build'],
36
- };
39
+ });
37
40
  }
38
41
  else {
39
- nxJson.targetDefaults['@nx/angular:webpack-browser'].dependsOn ??= [];
40
- if (!nxJson.targetDefaults['@nx/angular:webpack-browser'].dependsOn.includes('^build')) {
41
- nxJson.targetDefaults['@nx/angular:webpack-browser'].dependsOn.push('^build');
42
- }
43
- nxJson.targetDefaults['@nx/angular:webpack-browser'].inputs ??= [];
44
- if (!nxJson.targetDefaults['@nx/angular:webpack-browser'].inputs.find((i) => typeof i === 'string' ? false : i['env'] === nxMFDevRemotesEnvVar)) {
45
- nxJson.targetDefaults['@nx/angular:webpack-browser'].inputs.push({
46
- env: nxMFDevRemotesEnvVar,
47
- });
42
+ const dependsOn = [...(existing.dependsOn ?? [])];
43
+ if (!dependsOn.includes('^build'))
44
+ dependsOn.push('^build');
45
+ const inputs = [...(existing.inputs ?? [])];
46
+ if (!inputs.find((i) => typeof i === 'string' ? false : i['env'] === nxMFDevRemotesEnvVar)) {
47
+ inputs.push({ env: nxMFDevRemotesEnvVar });
48
48
  }
49
+ (0, internal_1.upsertTargetDefault)(tree, nxJson, {
50
+ executor: webpackExecutor,
51
+ ...(existing.cache !== undefined ? { cache: existing.cache } : {}),
52
+ inputs,
53
+ dependsOn,
54
+ });
49
55
  }
50
56
  (0, devkit_1.updateNxJson)(tree, nxJson);
51
57
  await (0, devkit_1.formatFiles)(tree);
@@ -39,11 +39,7 @@ async function default_1(tree) {
39
39
  if (!nxJson.targetDefaults) {
40
40
  return;
41
41
  }
42
- for (const [targetOrExecutor, targetConfig] of Object.entries(nxJson.targetDefaults)) {
43
- if (!exports.executors.includes(targetOrExecutor) &&
44
- !exports.executors.includes(targetConfig.executor)) {
45
- continue;
46
- }
42
+ const cleanEntry = (targetConfig) => {
47
43
  if (targetConfig.options) {
48
44
  delete targetConfig.options.tailwindConfig;
49
45
  if (!Object.keys(targetConfig.options).length) {
@@ -59,9 +55,23 @@ async function default_1(tree) {
59
55
  if (!Object.keys(targetConfig.configurations ?? {}).length) {
60
56
  delete targetConfig.configurations;
61
57
  }
62
- if (!Object.keys(targetConfig).length ||
63
- (Object.keys(targetConfig).length === 1 &&
64
- Object.keys(targetConfig)[0] === 'executor')) {
58
+ return {
59
+ empty: !Object.keys(targetConfig).length ||
60
+ (Object.keys(targetConfig).length === 1 &&
61
+ Object.keys(targetConfig)[0] === 'executor'),
62
+ };
63
+ };
64
+ for (const [targetOrExecutor, targetConfig] of Object.entries(nxJson.targetDefaults)) {
65
+ if (Array.isArray(targetConfig)) {
66
+ // This migration predates the filtered array value form; values are plain objects here.
67
+ continue;
68
+ }
69
+ if (!exports.executors.includes(targetOrExecutor) &&
70
+ !exports.executors.includes(targetConfig.executor)) {
71
+ continue;
72
+ }
73
+ const { empty } = cleanEntry(targetConfig);
74
+ if (empty) {
65
75
  delete nxJson.targetDefaults[targetOrExecutor];
66
76
  }
67
77
  if (!Object.keys(nxJson.targetDefaults).length) {
@@ -0,0 +1,2 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export default function (tree: Tree): Promise<import("@nx/devkit").GeneratorCallback>;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = default_1;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const versions_1 = require("../../utils/versions");
6
+ // The `@nx/angular:application` and `@nx/angular:unit-test` executors import
7
+ // `@angular/build` directly to run the Angular builders, and any
8
+ // `@angular/build:*` executor is provided by that package; it is a required
9
+ // runtime dependency for these targets. Nothing declares it as a direct
10
+ // dependency, though: it has only ever been pulled in transitively via
11
+ // `@angular-devkit/build-angular`, which isn't reliable (it can be absent from
12
+ // node_modules, e.g. under Yarn Berry), so `require.resolve('@angular/build')`
13
+ // fails and the build breaks. Add it as a direct dependency when an executor
14
+ // needs it, at the version the application generator installs.
15
+ const angularBuildExecutors = [
16
+ '@nx/angular:application',
17
+ '@nx/angular:unit-test',
18
+ ];
19
+ function usesAngularBuild(executors) {
20
+ return executors.some((e) => e != null &&
21
+ (angularBuildExecutors.includes(e) || e.startsWith('@angular/build:')));
22
+ }
23
+ async function default_1(tree) {
24
+ // Already a direct dependency -> nothing to add.
25
+ const { dependencies, devDependencies } = (0, devkit_1.readJson)(tree, 'package.json');
26
+ if (dependencies?.['@angular/build'] || devDependencies?.['@angular/build']) {
27
+ return;
28
+ }
29
+ let needsAngularBuild = false;
30
+ // project.json targets
31
+ for (const [, project] of (0, devkit_1.getProjects)(tree)) {
32
+ needsAngularBuild = Object.values(project.targets ?? {}).some((target) => usesAngularBuild([target.executor]));
33
+ if (needsAngularBuild) {
34
+ break;
35
+ }
36
+ }
37
+ // nx.json targetDefaults (keyed by target name or executor): a project's empty
38
+ // target can inherit the executor from a default.
39
+ if (!needsAngularBuild) {
40
+ const targetDefaults = (0, devkit_1.readNxJson)(tree)?.targetDefaults;
41
+ if (targetDefaults) {
42
+ needsAngularBuild = Object.entries(targetDefaults).some(([targetOrExecutor, config]) =>
43
+ // Mirror `add-istanbul-instrumenter`: the filtered array value form is
44
+ // out of scope; only plain object defaults carry an inheritable executor.
45
+ !Array.isArray(config) &&
46
+ usesAngularBuild([targetOrExecutor, config.executor]));
47
+ }
48
+ }
49
+ if (!needsAngularBuild) {
50
+ return;
51
+ }
52
+ return (0, devkit_1.addDependenciesToPackageJson)(tree, {}, { '@angular/build': versions_1.angularDevkitVersion }, undefined, true);
53
+ }
@@ -0,0 +1,61 @@
1
+ #### Add `@angular/build` When Used by an Executor
2
+
3
+ The `@nx/angular:application` and `@nx/angular:unit-test` executors load the Angular builders by importing `@angular/build` directly, and any `@angular/build:*` executor is provided by that package, so `@angular/build` is a required runtime dependency for workspaces using those targets. Nothing declares it as a direct dependency, though: it has only ever been available transitively as a dependency of `@angular-devkit/build-angular`. That is not reliable (for example, under Yarn Berry it can be missing from `node_modules` even when `@angular-devkit/build-angular` is installed), and when `@angular/build` cannot be resolved the build fails with "This executor requires the package @angular/build to be installed". This migration adds `@angular/build` to `devDependencies` when a project uses one of those executors and it is not already installed, at the version the application generator installs. An existing version is preserved.
4
+
5
+ Executor usage is detected on any target using `@nx/angular:application`, `@nx/angular:unit-test`, or an `@angular/build:*` executor. Targets that inherit their executor from an `nx.json` `targetDefaults` entry are detected too.
6
+
7
+ #### Examples
8
+
9
+ ##### `project.json`
10
+
11
+ Before:
12
+
13
+ ```jsonc {5}
14
+ // project.json
15
+ {
16
+ "targets": {
17
+ "build": {
18
+ "executor": "@nx/angular:application",
19
+ "options": {},
20
+ },
21
+ },
22
+ }
23
+ ```
24
+
25
+ After (`package.json`):
26
+
27
+ ```jsonc {4}
28
+ // package.json
29
+ {
30
+ "devDependencies": {
31
+ "@angular/build": "~22.0.4",
32
+ },
33
+ }
34
+ ```
35
+
36
+ ##### `nx.json` (`targetDefaults`)
37
+
38
+ A project whose `build` target inherits its executor from an `nx.json` `targetDefaults` entry is also detected:
39
+
40
+ ```jsonc {5}
41
+ // nx.json
42
+ {
43
+ "targetDefaults": {
44
+ "build": {
45
+ "executor": "@nx/angular:application",
46
+ "options": {},
47
+ },
48
+ },
49
+ }
50
+ ```
51
+
52
+ ```jsonc
53
+ // apps/app1/project.json
54
+ {
55
+ "targets": {
56
+ "build": {},
57
+ },
58
+ }
59
+ ```
60
+
61
+ `@angular/build` is added to `package.json` as shown above.
@@ -41,12 +41,16 @@ async function default_1(tree) {
41
41
  break;
42
42
  }
43
43
  }
44
- // nx.json targetDefaults: a project's empty `test` target can inherit a Karma
45
- // executor/runner from a default.
44
+ // nx.json targetDefaults (keyed by target name or executor): a project's empty
45
+ // `test` target can inherit a Karma executor/runner from a default.
46
46
  if (!needsInstrumenter) {
47
47
  const targetDefaults = (0, devkit_1.readNxJson)(tree)?.targetDefaults;
48
48
  if (targetDefaults) {
49
- needsInstrumenter = Object.entries(targetDefaults).some(([targetOrExecutor, config]) => usesKarma([targetOrExecutor, config.executor], config));
49
+ needsInstrumenter = Object.entries(targetDefaults).some(([targetOrExecutor, config]) =>
50
+ // This migration predates the filtered array value form; values are
51
+ // plain objects here.
52
+ !Array.isArray(config) &&
53
+ usesKarma([targetOrExecutor, config.executor], config));
50
54
  }
51
55
  }
52
56
  if (!needsInstrumenter) {
@@ -0,0 +1,2 @@
1
+ import { type Tree } from '@nx/devkit';
2
+ export default function (tree: Tree): Promise<void>;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = default_1;
4
+ const devkit_1 = require("@nx/devkit");
5
+ const node_path_1 = require("node:path");
6
+ const jsonc_parser_1 = require("jsonc-parser");
7
+ const FORMATTING_OPTIONS = {
8
+ formattingOptions: { keepLines: true, insertSpaces: true, tabSize: 2 },
9
+ };
10
+ // Angular v22's `strict-safe-navigation-narrow` migration adds an
11
+ // `extendedDiagnostics` block to project tsconfigs unconditionally, while its
12
+ // `strict-templates-default` migration pins `strictTemplates: false` on
13
+ // workspaces that had not enabled strict templates. The Angular compiler rejects
14
+ // that combination (it errors when `extendedDiagnostics` is set and
15
+ // `strictTemplates` is `false`), so those projects fail to build. `@nx/angular`
16
+ // migrations run after Angular's, so we reconcile it here: drop the
17
+ // `extendedDiagnostics` block wherever `strictTemplates` resolves to `false`,
18
+ // preserving the intended `strictTemplates: false` behavior (enabling strict
19
+ // templates instead would change behavior and surface new template errors).
20
+ async function default_1(tree) {
21
+ let removedCount = 0;
22
+ (0, devkit_1.visitNotIgnoredFiles)(tree, '.', (filePath) => {
23
+ const name = (0, node_path_1.basename)(filePath);
24
+ if (!name.startsWith('tsconfig') || !name.endsWith('.json')) {
25
+ return;
26
+ }
27
+ if (removeConflictingExtendedDiagnostics(tree, filePath)) {
28
+ removedCount += 1;
29
+ }
30
+ });
31
+ if (removedCount > 0) {
32
+ devkit_1.logger.info(`Removed the "extendedDiagnostics" Angular compiler option from ${removedCount} tsconfig file(s) where "strictTemplates" is disabled, resolving the conflict introduced by the Angular v22 template migrations.`);
33
+ }
34
+ await (0, devkit_1.formatFiles)(tree);
35
+ }
36
+ function removeConflictingExtendedDiagnostics(tree, tsconfigPath) {
37
+ const original = tree.read(tsconfigPath, 'utf-8');
38
+ if (!original) {
39
+ return false;
40
+ }
41
+ const root = (0, jsonc_parser_1.parseTree)(original);
42
+ if (!root || root.type !== 'object') {
43
+ return false;
44
+ }
45
+ // Only touch files that locally declare `extendedDiagnostics`; the Angular
46
+ // migration adds it to project tsconfigs, never to a shared base.
47
+ const extendedDiagnosticsNode = (0, jsonc_parser_1.findNodeAtLocation)(root, [
48
+ 'angularCompilerOptions',
49
+ 'extendedDiagnostics',
50
+ ]);
51
+ if (!extendedDiagnosticsNode) {
52
+ return false;
53
+ }
54
+ // The compiler errors only when the resolved `strictTemplates` is explicitly
55
+ // `false` (an unset value doesn't trigger it), so mirror that exact condition.
56
+ if (resolveStrictTemplates(tree, tsconfigPath) !== false) {
57
+ return false;
58
+ }
59
+ const edits = (0, jsonc_parser_1.modify)(original, ['angularCompilerOptions', 'extendedDiagnostics'], undefined, FORMATTING_OPTIONS);
60
+ tree.write(tsconfigPath, (0, jsonc_parser_1.applyEdits)(original, edits));
61
+ return true;
62
+ }
63
+ // Resolves `angularCompilerOptions.strictTemplates` for a tsconfig, following
64
+ // the `extends` chain (a local value wins over an inherited one). TypeScript
65
+ // doesn't merge the non-standard `angularCompilerOptions`, so the chain is
66
+ // traversed manually, matching how the Angular compiler resolves it.
67
+ function resolveStrictTemplates(tree, tsconfigPath, seen = new Set()) {
68
+ if (seen.has(tsconfigPath)) {
69
+ return undefined;
70
+ }
71
+ seen.add(tsconfigPath);
72
+ const content = tree.read(tsconfigPath, 'utf-8');
73
+ if (!content) {
74
+ return undefined;
75
+ }
76
+ const root = (0, jsonc_parser_1.parseTree)(content);
77
+ if (!root || root.type !== 'object') {
78
+ return undefined;
79
+ }
80
+ const localNode = (0, jsonc_parser_1.findNodeAtLocation)(root, [
81
+ 'angularCompilerOptions',
82
+ 'strictTemplates',
83
+ ]);
84
+ if (localNode) {
85
+ const value = (0, jsonc_parser_1.getNodeValue)(localNode);
86
+ return typeof value === 'boolean' ? value : undefined;
87
+ }
88
+ const extendsNode = (0, jsonc_parser_1.findNodeAtLocation)(root, ['extends']);
89
+ if (!extendsNode) {
90
+ return undefined;
91
+ }
92
+ const extendsValue = (0, jsonc_parser_1.getNodeValue)(extendsNode);
93
+ // `extends` can be a single path or an ordered array (later entries win).
94
+ const parents = Array.isArray(extendsValue)
95
+ ? [...extendsValue].reverse()
96
+ : [extendsValue];
97
+ for (const parent of parents) {
98
+ const parentPath = resolveExtendsPath(tree, tsconfigPath, parent);
99
+ if (!parentPath) {
100
+ continue;
101
+ }
102
+ const resolved = resolveStrictTemplates(tree, parentPath, seen);
103
+ if (resolved !== undefined) {
104
+ return resolved;
105
+ }
106
+ }
107
+ return undefined;
108
+ }
109
+ // Resolves a relative `extends` target to a workspace path within the tree.
110
+ // Package/`node_modules` bases (which never carry the migration's output) are
111
+ // skipped by returning null.
112
+ function resolveExtendsPath(tree, tsconfigPath, extendsValue) {
113
+ if (typeof extendsValue !== 'string' || !extendsValue.startsWith('.')) {
114
+ return null;
115
+ }
116
+ const base = node_path_1.posix.join(node_path_1.posix.dirname(tsconfigPath), extendsValue);
117
+ const candidates = base.endsWith('.json') ? [base] : [base, `${base}.json`];
118
+ return candidates.find((candidate) => tree.exists(candidate)) ?? null;
119
+ }
@@ -0,0 +1,37 @@
1
+ #### Remove `extendedDiagnostics` When `strictTemplates` Is Disabled
2
+
3
+ Angular v22 ships two `ng update` migrations that can leave a project's `tsconfig` in a state the Angular compiler rejects. `strict-templates-default` adds `"strictTemplates": false` to projects that had not enabled strict templates, and `strict-safe-navigation-narrow` adds an `extendedDiagnostics` block to every project `tsconfig`. The compiler requires `strictTemplates` to be enabled whenever `extendedDiagnostics` is configured, so the two together fail the build with "Using extendedDiagnostics requires that strictTemplates is also enabled".
4
+
5
+ Because `@nx/angular` migrations run after Angular's, this migration reconciles the result: for any `tsconfig` where `strictTemplates` resolves to `false` (following the `extends` chain), it removes the `extendedDiagnostics` block. This keeps the intended `strictTemplates: false` behavior rather than enabling strict templates, which would change behavior and surface new template errors. Projects that keep `strictTemplates` enabled are left untouched.
6
+
7
+ #### Examples
8
+
9
+ ##### `tsconfig.app.json`
10
+
11
+ Before:
12
+
13
+ ```jsonc {3,10}
14
+ // tsconfig.app.json
15
+ {
16
+ "angularCompilerOptions": {
17
+ "extendedDiagnostics": {
18
+ "checks": {
19
+ "nullishCoalescingNotNullable": "suppress",
20
+ "optionalChainNotNullable": "suppress",
21
+ },
22
+ },
23
+ "strictTemplates": false,
24
+ },
25
+ }
26
+ ```
27
+
28
+ After:
29
+
30
+ ```jsonc {3}
31
+ // tsconfig.app.json
32
+ {
33
+ "angularCompilerOptions": {
34
+ "strictTemplates": false,
35
+ },
36
+ }
37
+ ```
@@ -49,11 +49,16 @@ async function default_1(tree) {
49
49
  (0, devkit_1.updateProjectConfiguration)(tree, projectName, project);
50
50
  }
51
51
  }
52
- // nx.json targetDefaults
52
+ // nx.json targetDefaults (keyed by target name or executor)
53
53
  const nxJson = (0, devkit_1.readNxJson)(tree);
54
54
  if (nxJson?.targetDefaults) {
55
55
  let isUpdated = false;
56
56
  for (const [targetOrExecutor, targetConfig] of Object.entries(nxJson.targetDefaults)) {
57
+ // This migration predates the filtered array value form; values are
58
+ // plain objects here.
59
+ if (Array.isArray(targetConfig)) {
60
+ continue;
61
+ }
57
62
  if (!ssrApplicationExecutors.includes(targetOrExecutor) &&
58
63
  !ssrApplicationExecutors.includes(targetConfig.executor)) {
59
64
  continue;
@@ -48,12 +48,11 @@ After:
48
48
 
49
49
  Before:
50
50
 
51
- ```jsonc {9}
51
+ ```jsonc {8}
52
52
  // nx.json
53
53
  {
54
- "targetDefaults": [
55
- {
56
- "executor": "@nx/angular:application",
54
+ "targetDefaults": {
55
+ "@nx/angular:application": {
57
56
  "options": {
58
57
  "ssr": {
59
58
  "entry": "src/server.ts",
@@ -61,18 +60,17 @@ Before:
61
60
  },
62
61
  },
63
62
  },
64
- ],
63
+ },
65
64
  }
66
65
  ```
67
66
 
68
67
  After:
69
68
 
70
- ```jsonc {9}
69
+ ```jsonc {8}
71
70
  // nx.json
72
71
  {
73
- "targetDefaults": [
74
- {
75
- "executor": "@nx/angular:application",
72
+ "targetDefaults": {
73
+ "@nx/angular:application": {
76
74
  "options": {
77
75
  "ssr": {
78
76
  "entry": "src/server.ts",
@@ -80,6 +78,6 @@ After:
80
78
  },
81
79
  },
82
80
  },
83
- ],
81
+ },
84
82
  }
85
83
  ```
@@ -1,6 +1,6 @@
1
1
  export declare const nxVersion: any;
2
- export declare const angularVersion = "~22.0.0";
3
- export declare const angularDevkitVersion = "~22.0.0";
2
+ export declare const angularVersion = "~22.0.4";
3
+ export declare const angularDevkitVersion = "~22.0.4";
4
4
  export declare const ngPackagrVersion = "~22.0.0";
5
5
  export declare const ngrxVersion = "^21.0.0";
6
6
  export declare const rxjsVersion = "~7.8.0";
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.jsoncEslintParserVersion = exports.oxcProjectRuntimeVersion = exports.jsdomVersion = exports.vitestVersion = exports.jasmineMarblesVersion = exports.typesNodeVersion = exports.jestPresetAngularVersion = exports.lessVersion = exports.tsNodeVersion = exports.autoprefixerVersion = exports.postcssUrlVersion = exports.postcssVersion = exports.typescriptEslintVersion = exports.angularEslintVersion = exports.webpackMergeVersion = exports.moduleFederationEnhancedVersion = exports.moduleFederationNodeVersion = exports.browserSyncVersion = exports.typesExpressVersion = exports.expressVersion = exports.typesCorsVersion = exports.corsVersion = exports.tsLibVersion = exports.zoneJsVersion = exports.rxjsVersion = exports.ngrxVersion = exports.ngPackagrVersion = exports.angularDevkitVersion = exports.angularVersion = exports.nxVersion = void 0;
4
4
  const path_1 = require("path");
5
5
  exports.nxVersion = require((0, path_1.join)('@nx/angular', 'package.json')).version;
6
- exports.angularVersion = '~22.0.0';
7
- exports.angularDevkitVersion = '~22.0.0';
6
+ exports.angularVersion = '~22.0.4';
7
+ exports.angularDevkitVersion = '~22.0.4';
8
8
  exports.ngPackagrVersion = '~22.0.0';
9
9
  exports.ngrxVersion = '^21.0.0';
10
10
  exports.rxjsVersion = '~7.8.0';
package/migrations.json CHANGED
@@ -348,6 +348,24 @@
348
348
  "description": "Add the `trustProxyHeaders` option to `AngularNodeAppEngine` and `AngularAppEngine` instantiations in SSR server files, matching the Angular v22 `trust-proxy-headers` migration.",
349
349
  "factory": "./dist/src/migrations/update-23-1-0/add-trust-proxy-headers",
350
350
  "documentation": "./dist/src/migrations/update-23-1-0/add-trust-proxy-headers.md"
351
+ },
352
+ "update-23-1-0-add-angular-build": {
353
+ "version": "23.1.0-beta.0",
354
+ "requires": {
355
+ "@angular/core": ">=22.0.0"
356
+ },
357
+ "description": "Add `@angular/build` to `devDependencies` when a project uses the `@nx/angular:application` or `@nx/angular:unit-test` executor, or an `@angular/build:*` executor, and it is not already installed. These executors import `@angular/build` directly at runtime, but it has only ever been available transitively via `@angular-devkit/build-angular`, which is not reliable across package managers.",
358
+ "factory": "./dist/src/migrations/update-23-1-0/add-angular-build",
359
+ "documentation": "./dist/src/migrations/update-23-1-0/add-angular-build.md"
360
+ },
361
+ "update-23-1-0-remove-conflicting-extended-diagnostics": {
362
+ "version": "23.1.0-beta.0",
363
+ "requires": {
364
+ "@angular/core": ">=22.0.0"
365
+ },
366
+ "description": "Remove the `extendedDiagnostics` Angular compiler option from tsconfig files where `strictTemplates` resolves to `false`. Angular v22's `strict-templates-default` and `strict-safe-navigation-narrow` migrations can together produce this combination, which the Angular compiler rejects (`extendedDiagnostics` requires `strictTemplates` to be enabled).",
367
+ "factory": "./dist/src/migrations/update-23-1-0/remove-conflicting-extended-diagnostics",
368
+ "documentation": "./dist/src/migrations/update-23-1-0/remove-conflicting-extended-diagnostics.md"
351
369
  }
352
370
  },
353
371
  "packageJsonUpdates": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/angular",
3
- "version": "23.1.0-pr.36127.8b9019b",
3
+ "version": "23.1.0-rc.0",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "description": "The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: \n\n- Integration with libraries such as Storybook, Jest, ESLint, Playwright and Cypress. \n\n- Generators to help scaffold code quickly (like: Micro Frontends, Libraries, both internal to your codebase and publishable to npm) \n\n- Single Component Application Modules (SCAMs) \n\n- NgRx helpers. \n\n- Utilities for automatic workspace refactoring.",
@@ -74,24 +74,25 @@
74
74
  "@phenomnomnominal/tsquery": "~6.2.0",
75
75
  "@typescript-eslint/type-utils": "^8.0.0",
76
76
  "enquirer": "~2.3.6",
77
+ "jsonc-parser": "^3.2.0",
77
78
  "magic-string": "~0.30.2",
78
79
  "picocolors": "^1.1.0",
79
80
  "picomatch": "4.0.4",
80
81
  "semver": "^7.6.3",
81
82
  "tslib": "^2.3.0",
82
83
  "webpack-merge": "^5.8.0",
83
- "@nx/eslint": "23.1.0-pr.36127.8b9019b",
84
- "@nx/module-federation": "23.1.0-pr.36127.8b9019b",
85
- "@nx/js": "23.1.0-pr.36127.8b9019b",
86
- "@nx/rspack": "23.1.0-pr.36127.8b9019b",
87
- "@nx/devkit": "23.1.0-pr.36127.8b9019b",
88
- "@nx/web": "23.1.0-pr.36127.8b9019b",
89
- "@nx/webpack": "23.1.0-pr.36127.8b9019b"
84
+ "@nx/devkit": "23.1.0-rc.0",
85
+ "@nx/eslint": "23.1.0-rc.0",
86
+ "@nx/js": "23.1.0-rc.0",
87
+ "@nx/rspack": "23.1.0-rc.0",
88
+ "@nx/module-federation": "23.1.0-rc.0",
89
+ "@nx/web": "23.1.0-rc.0",
90
+ "@nx/webpack": "23.1.0-rc.0"
90
91
  },
91
92
  "devDependencies": {
92
- "@nx/vitest": "23.1.0-pr.36127.8b9019b",
93
- "@nx/cypress": "23.1.0-pr.36127.8b9019b",
94
- "nx": "23.1.0-pr.36127.8b9019b"
93
+ "@nx/cypress": "23.1.0-rc.0",
94
+ "@nx/vitest": "23.1.0-rc.0",
95
+ "nx": "23.1.0-rc.0"
95
96
  },
96
97
  "peerDependencies": {
97
98
  "@angular/build": ">= 20.0.0 < 23.0.0",