Skip to content

feat: upgrade to Angular 22 RC and Nx 22.7.5#2357

Merged
brandonroberts merged 1 commit into
betafrom
feat/angular-22-nx-migration
Jun 1, 2026
Merged

feat: upgrade to Angular 22 RC and Nx 22.7.5#2357
brandonroberts merged 1 commit into
betafrom
feat/angular-22-nx-migration

Conversation

@brandonroberts

Copy link
Copy Markdown
Member

PR Checklist

Upgrades the workspace to Angular 22 RC and Nx 22.7.5, regenerates the lockfile, applies the nx migrate config/formatting updates, and fixes the build/test fallout from the bump. Also widens package peer ranges to admit Angular 22 RC and drops the unsupported <17 floor.

Closes #

Affected scope

  • Primary scope: repo (workspace-wide dependency + config upgrade — nx.json, tsconfig.base.json, root package.json, pnpm-lock.yaml)
  • Secondary scopes: nx-plugin, vite-plugin-angular, storybook-angular, vitest-angular, content, router, astro-angular

Recommended merge strategy for maintainer [optional]

  • Squash merge
  • Rebase merge
  • Other

What is the new behavior?

  • Bumps @angular/* and @angular-devkit/* to ^22.0.0-rc.0, @nx/* to 22.7.5, and regenerates the pnpm lockfile.
  • Applies the nx migrate updates (nx.json, tsconfig.base.json, plugin builders) and ignores the new .nx/polygraph and .nx/self-healing caches.
  • Widens published peer ranges to admit Angular 22 RC and removes the unsupported Angular <17 floor:
    • astro-angular, content, router, vite-plugin-angular (@angular/build + @angular-devkit/build-angular), and vitest-angular (@angular-devkit/architect + @angular-devkit/schematics).
    • @nx/* peer ranges left untouched.
  • Fixes migration build/test fallout:
    • nx-plugin — resolves tightened-type errors surfaced by the toolchain bump (projectConfig possibly-undefined guards, nullable coerce/clean/version-util returns, tree.read null, narrowed index/delete types).
    • storybook-angular — adds ambient declarations for @storybook/angular subpath exports that no longer ship .d.ts, and types the now-untyped preset-hook params.
    • blog-app — ignores the dangling @img/sharp-wasm32 optional symlink in nitro's external file trace (was throwing ENOENT on realpath).
    • docs-app — pins webpack to 5.105.4 so Docusaurus' webpackbar ProgressPlugin options pass webpack's schema validation (5.106 tightened it).

Test plan

  • nx format:check
  • pnpm build
  • pnpm test
  • Manual verification

pnpm build is green for all 20 projects; pnpm test is green for all 18 projects. Builds and tests were run locally on darwin-arm64.

Does this PR introduce a breaking change?

  • Yes
  • No

Drops support for Angular versions below 17 in the published peer ranges (astro-angular, content, router, vite-plugin-angular, vitest-angular). Consumers on Angular 15/16 should remain on the previous Analog release. Angular 17–21 and 22 RC are supported.

Other information

The branch is a single squashable commit on top of beta.

🤖 Generated with Claude Code

Bump @angular/* and @angular-devkit/* to ^22.0.0-rc.0, @nx/* to 22.7.5,
regenerate the pnpm lockfile, and apply the nx migrate formatting/config
updates (nx.json, tsconfig.base.json, plugin builders). Ignore the new
.nx/polygraph and .nx/self-healing nx caches.

Widen package peer ranges to admit Angular 22 RC and drop the
unsupported <17 floor, and fix migration build fallout (nx-plugin and
storybook-angular type errors, blog-app sharp trace, docs-app webpack
pin).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@netlify

netlify Bot commented Jun 1, 2026

Copy link
Copy Markdown

Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit adfbebe
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/6a1de669f2852100080def03
😎 Deploy Preview https://deploy-preview-2357--analog-blog.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Jun 1, 2026

Copy link
Copy Markdown

Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit adfbebe
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/6a1de6699c4df20008307770
😎 Deploy Preview https://deploy-preview-2357--analog-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Jun 1, 2026

Copy link
Copy Markdown

Deploy Preview for analog-docs ready!

Name Link
🔨 Latest commit adfbebe
🔍 Latest deploy log https://app.netlify.com/projects/analog-docs/deploys/6a1de6692fb944000884b2f5
😎 Deploy Preview https://deploy-preview-2357--analog-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR upgrades the analog monorepo to Angular 22.0.0-rc.2 and TypeScript ~6.0.0, with corresponding NX 22.7.5 and supporting tooling updates. The core workspace dependencies in package.json and all public package peerDependencies are aligned to accept Angular 22 RC. Simultaneously, the NX plugin generators are hardened with explicit non-null assertions and conditional guards throughout project configuration lookups, version parsing, and file I/O operations to enforce stricter TypeScript assumptions. Storybook Angular preset functions receive explicit parameter typing, a new ambient module declaration file resolves deep-import type issues, and Vite configuration is updated to exclude sharp-wasm traces and handle builder metadata more safely.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows Conventional Commit style without a scope, which is appropriate for repo-wide dependency and configuration updates.
Description check ✅ Passed The PR description is comprehensive and directly related to the changeset, covering the upgrade objectives, affected scopes, migration details, test results, and breaking change implications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added scope:astro-angular Changes in @analogjs/astro-angular scope:ci GitHub workflow changes scope:content Changes in @analogjs/content scope:nx-plugin Changes in @analogjs/nx-plugin scope:repo Repository metadata and tooling scope:router Changes in @analogjs/router scope:storybook-angular Changes in @analogjs/storybook-angular scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular scope:vitest-angular Changes in @analogjs/vitest-angular labels Jun 1, 2026
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

This PR touches multiple package scopes: astro-angular, content, nx-plugin, router, storybook-angular, vite-plugin-angular, vitest-angular.

Please confirm the changes are closely related. Squash merge is highly preferred. If you recommend a non-squash merge, add a brief note explaining why the commit boundaries matter and why this PR should bypass focused changes per package.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (5)
packages/nx-plugin/src/generators/app/lib/add-analog-project-config.ts (1)

19-19: ⚖️ Poor tradeoff

Type safety regression: ProjectConfiguration → any.

This change weakens type safety rather than hardening it, contradicting the PR's stated goal. While the dynamic [targets] and [builders] keys may complicate typing, losing compile-time checks for the entire configuration object increases the risk of property access errors.

Consider using a more precise type or targeted type assertions (e.g., as any only where needed for dynamic keys) instead of declaring the entire object as any.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nx-plugin/src/generators/app/lib/add-analog-project-config.ts` at
line 19, The projectConfiguration variable was widened to any, losing type
safety; restore a precise type by declaring projectConfiguration as
ProjectConfiguration (importing the type from `@nrwl/devkit` or nx) and only use
targeted casts for truly dynamic keys (e.g., cast projectConfiguration.targets
as any when assigning dynamic target names or builders). Keep the overall object
typed as ProjectConfiguration and restrict any usage to the minimal expressions
that need dynamic indexing (such as (projectConfiguration.targets as
any)[dynamicTargetName] = ... or (projectConfiguration.targets as Record<string,
any>)[...]) so you retain compile-time checks for all other properties.
packages/nx-plugin/src/generators/setup-vitest/generator.ts (1)

51-51: 💤 Low value

Redundant assertion after line 47 already asserted.

Since line 47 already asserts angularVersion! is non-null, this assertion on line 51 is redundant. If you add validation at line 47 (as suggested above), you can safely remove this assertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nx-plugin/src/generators/setup-vitest/generator.ts` at line 51, The
call to addAnalogDependencies(tree, angularVersion!, nxVersion ?? undefined)
uses a redundant non-null assertion on angularVersion; remove the trailing "!"
and call addAnalogDependencies(tree, angularVersion, nxVersion ?? undefined)
after ensuring the prior validation that guarantees angularVersion is non-null
(the check/assert at line 47). Update references to the angularVersion symbol
accordingly so the function call no longer uses the unnecessary assertion while
keeping the existing validation that ensures it's defined.
packages/nx-plugin/src/generators/setup-vitest/lib/update-test-target.ts (1)

34-34: 💤 Low value

Simplify redundant condition.

The check projectConfig && projectConfig?.targets is redundant. Optional chaining already handles the nullish case, so you can simplify to if (projectConfig?.targets).

♻️ Simplified condition
-    if (projectConfig && projectConfig?.targets) {
+    if (projectConfig?.targets) {
      projectConfig.targets.test = {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nx-plugin/src/generators/setup-vitest/lib/update-test-target.ts` at
line 34, The if-condition redundantly checks projectConfig twice; update the
conditional in the update-test-target logic to use optional chaining only (check
projectConfig?.targets) so the nullish case is handled once. Locate the
conditional that currently reads projectConfig && projectConfig?.targets
(referencing projectConfig and its targets property) and replace it with a
single if (projectConfig?.targets) check to simplify the guard while preserving
behavior.
packages/nx-plugin/src/generators/init/lib/update-build-target.ts (1)

70-70: 💤 Low value

Redundant optional chaining in guard condition.

projectConfig && projectConfig?.targets uses optional chaining (?.) after already confirming projectConfig is truthy. Simplify to projectConfig && projectConfig.targets.

♻️ Simplify the guard condition
- if (projectConfig && projectConfig?.targets) {
+ if (projectConfig && projectConfig.targets) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nx-plugin/src/generators/init/lib/update-build-target.ts` at line
70, The guard condition in update-build-target.ts uses redundant optional
chaining: replace `projectConfig && projectConfig?.targets` with `projectConfig
&& projectConfig.targets` (or simply `projectConfig?.targets` if preferred)
where `projectConfig` is checked before accessing `targets` to remove the
unnecessary `?.` and keep the condition clear; update the conditional in the
block that references `projectConfig`/`targets` accordingly.
tsconfig.base.json (1)

18-18: ⚡ Quick win

Valid value, but this only defers the baseUrl cleanup it's masking.

"6.0" is the correct escape-hatch value for TS 6.0. The thing it's silencing here is almost certainly baseUrl (Line 17): these deprecations can be temporarily suppressed with "ignoreDeprecations": "6.0", but will be removed in TypeScript 7.0. baseUrl: Deprecated. Path mappings no longer require it, and it is no longer treated as a resolution root. Since the ignoreDeprecations: '6.0' escape hatch stops working in 7.0, consider dropping baseUrl now (path mappings still resolve without it) to avoid a hard failure when TS 7.0 lands. Fine to defer for this RC PR, but worth a tracking note.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tsconfig.base.json` at line 18, The tsconfig currently sets
"ignoreDeprecations": "6.0" which masks the deprecation of "baseUrl"; update
tsconfig.base.json by removing the deprecated "baseUrl" entry (referencing the
"baseUrl" key) and keep or remove the "ignoreDeprecations" escape hatch as
desired—preferably remove "baseUrl" now so path mappings continue to work
without it and we won't hit a hard failure in TS 7.0; ensure any path mappings
in "paths" still resolve after removing "baseUrl" and add a tracking note if you
choose to defer removal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/test-release.yml:
- Line 98: The Node version is inconsistent across release-validation jobs;
update the node-version value in the create-analog, create-analog-windows, and
create-nx-workspace job definitions to match migrate-angular-app (set
node-version: '24.15.0') or confirm intentional divergence; locate the
node-version entries for the jobs named migrate-angular-app, create-analog,
create-analog-windows, and create-nx-workspace and make the values identical if
the bump should apply workspace-wide.

In `@packages/content/package.json`:
- Around line 26-29: Add a BREAKING CHANGE footer to the squash commit message
that documents the raised peer dependency floor from Angular 15/16 to 17 and
shows the before/after peer ranges for the affected packages; explicitly mention
the changed dependency entries (e.g. "`@angular/core`", "`@angular/common`",
"`@angular/platform-browser`", "`@angular/router`") in packages/content/package.json
and the corresponding entries in packages/router/package.json and
packages/vite-plugin-angular/package.json, and include a short line like
"BREAKING CHANGE: `@analogjs/`* now requires Angular >=17. Before: ... After: ..."
showing the exact previous ranges and the new ranges (including ^22.0.0-rc.0) so
consumers can see the impact.

In `@packages/nx-plugin/src/generators/app/generator.ts`:
- Line 112: The code uses angularVersion returned from
getInstalledPackageVersion without a null check when computing
majorAngularVersion (const majorAngularVersion =
major(coerce(angularVersion!)!)); add a null guard: check if angularVersion is
null/undefined and either supply a safe default string (e.g., '0.0.0' or a
sensible fallback) or throw a clear error before calling coerce/major; update
the flow around getInstalledPackageVersion and the constant majorAngularVersion
so you never use the non-null assertion on angularVersion, referencing the
symbols angularVersion, getInstalledPackageVersion, coerce, major and
majorAngularVersion to locate the change.

In `@packages/nx-plugin/src/generators/app/lib/add-tailwind-helpers.ts`:
- Line 230: Replace the unsafe non-null assertions on project.sourceRoot (used
in the relative(...) calls that set relativeSourceRoot and the other two
occurrences) with a guarded/fallback approach: check if project.sourceRoot is
defined and only call relative(project.root, project.sourceRoot) when it exists,
otherwise use a safe fallback such as relative(project.root, project.root) or an
empty string; update the code paths that build relativeSourceRoot and the other
two places to use project.sourceRoot ?? project.root (or skip computing the
relative path) instead of project.sourceRoot! so runtime errors are avoided when
sourceRoot is undefined.

In `@packages/nx-plugin/src/generators/app/lib/initialize-analog-workspace.ts`:
- Line 43: The call belowMinimumSupportedAngularVersion(angularVersion!) is
unsafe because init* helper functions can still leave angularVersion null;
change the logic in initialize-analog-workspace (the function containing the
init* calls and the belowMinimumSupportedAngularVersion check) to explicitly
handle a null angularVersion after initialization: call
getInstalledPackageVersion('`@angular/core`', ...) into the angularVersion
variable, check if angularVersion is null and then either log a clear error and
abort/return or throw a descriptive error before calling
belowMinimumSupportedAngularVersion, so you never use a non-null assertion on
angularVersion.

In `@packages/nx-plugin/src/generators/app/lib/update-index-html.ts`:
- Line 7: The code uses a non-null assertion on projectConfig
(projectConfig!.root) which can be undefined; update the start of the function
that calls joinPathFragments to explicitly validate the lookup result from
projects.get(projectName) (e.g., const projectConfig =
projects.get(projectName); if (!projectConfig) throw new Error(`Project
'${projectName}' not found`); ) and then use projectConfig.root when calling
joinPathFragments to avoid a runtime crash; reference the projectConfig variable
and the joinPathFragments call in update-index-html.ts.
- Line 11: The code uses a non-null assertion on indexContents when building
updatedIndex (indexContents!.replace(...)), which will throw if tree.read
returned null (e.g., empty files); update the logic in updateIndexHtml to first
call tree.read(...) into indexContents, check for null, and handle it safely
(either treat as empty string or bail with a clear error/log) and convert the
Buffer to a string before calling replace so updatedIndex is only computed from
a valid string.

In
`@packages/nx-plugin/src/generators/app/versions/minimum-supported-versions.ts`:
- Around line 5-8: The helper functions belowMinimumSupportedNxVersion and
belowMinimumSupportedAngularVersion currently use non-null assertions on
semver.coerce() which can be null for malformed inputs; change each to first
assign const parsed = coerce(inputVersion) and if parsed is null throw a clear
Error (e.g., `Invalid version string: "${inputVersion}"`) so callers get a
descriptive failure instead of a runtime crash, then return lt(parsed,
MINIMUM_SUPPORTED_NX_VERSION) / lt(parsed, MINIMUM_SUPPORTED_ANGULAR_VERSION)
respectively; keep references to MINIMUM_SUPPORTED_NX_VERSION and
MINIMUM_SUPPORTED_ANGULAR_VERSION unchanged.

In `@packages/nx-plugin/src/generators/app/versions/nx-dependencies.ts`:
- Line 23: The code currently uses non-null assertions on
semver.clean(nxVersion) (seen as escapedNxVersion) which can be null for
malformed versions; update getNxDependencies to explicitly handle clean(...)
returning null by checking the result and throwing a clear, user-facing error
(e.g., "Invalid nxVersion: '<value>'") instead of using !; apply the same
null-check pattern for the other clean(...) call on line 42 so both cleaned
values are validated and a helpful error is thrown when cleaning fails.

In `@packages/nx-plugin/src/generators/init/generator.ts`:
- Line 65: The chained non-null assertions on major(coerce(angularVersion!)!)
can throw a cryptic TypeError; update the logic around angularVersion and
coercion in generator.ts so you first check that angularVersion is defined and
that coerce(angularVersion) returns a non-null SemVer before calling major.
Locate the angularVersion variable and the const majorAngularVersion assignment,
perform a guard (if missing/invalid) and either throw a clear validation error
(with a helpful message) or handle a safe default, ensuring you reference
coerce(...) and major(...) in the guard so the code never calls major on a null
value.

In `@packages/nx-plugin/src/generators/init/lib/update-index-html.ts`:
- Line 7: The code uses a non-null assertion when retrieving projectConfig:
replace the unsafe projects.get(schema.project)! lookup with a guarded lookup
that validates existence; call projects.get(schema.project) into a local (e.g.,
projectConfig) and if it is undefined, throw a clear error or return early with
a helpful message referencing schema.project; update any subsequent uses of
projectConfig in update-index-html.ts to rely on the validated local variable
(avoid using the '!' operator).
- Line 13: The current code uses a non-null assertion on tree.read(indexPath,
'utf-8') assigned to indexContents which can be null for empty files or read
errors; change this to explicitly check the return value of tree.read(indexPath,
'utf-8') (e.g., const raw = tree.read(...); if (raw === null) { handle it: set
indexContents to '' or throw a clear error }) and then use that guarded value
(indexContents) for subsequent processing in update-index-html.ts so you avoid a
runtime crash when the file is empty or unreadable.

In `@packages/nx-plugin/src/generators/setup-vitest/generator.ts`:
- Line 47: The current double non-null assertion at const majorAngularVersion =
major(coerce(angularVersion!)!); can throw at runtime if `@angular/core` is
missing or the version is unparseable; to fix, validate and handle the value
returned by coerce before calling major: first check angularVersion exists
(e.g., if (!angularVersion) throw new Error("Missing `@angular/core` version -
please install `@angular/core` or run this generator in an Angular workspace")),
then call const coerced = coerce(angularVersion); if (!coerced) throw new
Error("Unable to parse `@angular/core` version: " + String(angularVersion));
finally use const majorAngularVersion = major(coerced); replace the original
line with these guarded checks referencing angularVersion, coerce and major.
- Line 25: The code uses a non-null assertion on projects.get(options.project)
when assigning projectConfig, which can throw if the project key doesn't exist;
update the generator to validate the lookup result first (e.g., const
projectConfig = projects.get(options.project); if (!projectConfig) throw new
Error(`Project "${options.project}" not found in workspace`);) so callers
receive a clear validation error instead of a runtime TypeError; locate the
assignment to projectConfig and replace the non-null assertion with this
existence check referencing options.project and projects.get.

In `@packages/nx-plugin/src/utils/version-utils.ts`:
- Line 23: The code in version-utils.ts uses non-null assertions on
coerce(defaultVersion) and coerce(installedPackageVersion) which can throw if
the version strings are unparseable; update the logic in the function that
returns the cleaned/coerced version (references: defaultVersion,
installedPackageVersion, coerce, clean) to handle null from coerce by adding
explicit fallbacks or error handling: first attempt clean(defaultVersion) and if
that yields null call coerce(defaultVersion) and check for null before using
.version, and do the same for installedPackageVersion, returning a sensible
default (e.g., throw a descriptive error or return a safe fallback string)
instead of using the non-null assertion.

In
`@packages/vite-plugin-angular-tools/src/builders/vite-dev-server/dev-server.impl.ts`:
- Around line 15-16: The code inconsistently handles context.target by using a
non-null assertion in the call to getProjectMetadata (context.target!) and an
optional chain when reading projectName (context.target?.project); fix by adding
a single early guard that checks for presence of context.target and bails with a
clear error if missing, then call getProjectMetadata(context.target) and read
context.target.project without optional chaining—update references in this block
(context.target, getProjectMetadata, projectName, projectConfig) so they assume
a defined target after the guard.

---

Nitpick comments:
In `@packages/nx-plugin/src/generators/app/lib/add-analog-project-config.ts`:
- Line 19: The projectConfiguration variable was widened to any, losing type
safety; restore a precise type by declaring projectConfiguration as
ProjectConfiguration (importing the type from `@nrwl/devkit` or nx) and only use
targeted casts for truly dynamic keys (e.g., cast projectConfiguration.targets
as any when assigning dynamic target names or builders). Keep the overall object
typed as ProjectConfiguration and restrict any usage to the minimal expressions
that need dynamic indexing (such as (projectConfiguration.targets as
any)[dynamicTargetName] = ... or (projectConfiguration.targets as Record<string,
any>)[...]) so you retain compile-time checks for all other properties.

In `@packages/nx-plugin/src/generators/init/lib/update-build-target.ts`:
- Line 70: The guard condition in update-build-target.ts uses redundant optional
chaining: replace `projectConfig && projectConfig?.targets` with `projectConfig
&& projectConfig.targets` (or simply `projectConfig?.targets` if preferred)
where `projectConfig` is checked before accessing `targets` to remove the
unnecessary `?.` and keep the condition clear; update the conditional in the
block that references `projectConfig`/`targets` accordingly.

In `@packages/nx-plugin/src/generators/setup-vitest/generator.ts`:
- Line 51: The call to addAnalogDependencies(tree, angularVersion!, nxVersion ??
undefined) uses a redundant non-null assertion on angularVersion; remove the
trailing "!" and call addAnalogDependencies(tree, angularVersion, nxVersion ??
undefined) after ensuring the prior validation that guarantees angularVersion is
non-null (the check/assert at line 47). Update references to the angularVersion
symbol accordingly so the function call no longer uses the unnecessary assertion
while keeping the existing validation that ensures it's defined.

In `@packages/nx-plugin/src/generators/setup-vitest/lib/update-test-target.ts`:
- Line 34: The if-condition redundantly checks projectConfig twice; update the
conditional in the update-test-target logic to use optional chaining only (check
projectConfig?.targets) so the nullish case is handled once. Locate the
conditional that currently reads projectConfig && projectConfig?.targets
(referencing projectConfig and its targets property) and replace it with a
single if (projectConfig?.targets) check to simplify the guard while preserving
behavior.

In `@tsconfig.base.json`:
- Line 18: The tsconfig currently sets "ignoreDeprecations": "6.0" which masks
the deprecation of "baseUrl"; update tsconfig.base.json by removing the
deprecated "baseUrl" entry (referencing the "baseUrl" key) and keep or remove
the "ignoreDeprecations" escape hatch as desired—preferably remove "baseUrl" now
so path mappings continue to work without it and we won't hit a hard failure in
TS 7.0; ensure any path mappings in "paths" still resolve after removing
"baseUrl" and add a tracking note if you choose to defer removal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b65a722a-9af4-4ffa-8141-510f31a65d27

📥 Commits

Reviewing files that changed from the base of the PR and between 986f8d6 and adfbebe.

⛔ Files ignored due to path filters (3)
  • .gitignore is excluded by none and included by none
  • .prettierignore is excluded by none and included by none
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml and included by none
📒 Files selected for processing (37)
  • .github/workflows/test-release.yml
  • apps/blog-app/vite.config.ts
  • nx.json
  • package.json
  • packages/astro-angular/package.json
  • packages/content/package.json
  • packages/nx-plugin/src/generators/app/generator.ts
  • packages/nx-plugin/src/generators/app/lib/add-analog-project-config.ts
  • packages/nx-plugin/src/generators/app/lib/add-tailwind-helpers.ts
  • packages/nx-plugin/src/generators/app/lib/initialize-analog-workspace.ts
  • packages/nx-plugin/src/generators/app/lib/update-index-html.ts
  • packages/nx-plugin/src/generators/app/versions/minimum-supported-versions.ts
  • packages/nx-plugin/src/generators/app/versions/nx-dependencies.ts
  • packages/nx-plugin/src/generators/init/generator.ts
  • packages/nx-plugin/src/generators/init/lib/add-analog-dependencies.ts
  • packages/nx-plugin/src/generators/init/lib/update-app-tsconfig.ts
  • packages/nx-plugin/src/generators/init/lib/update-build-target.ts
  • packages/nx-plugin/src/generators/init/lib/update-git-ignore.ts
  • packages/nx-plugin/src/generators/init/lib/update-index-html.ts
  • packages/nx-plugin/src/generators/init/lib/update-main.ts
  • packages/nx-plugin/src/generators/init/lib/update-package-json.ts
  • packages/nx-plugin/src/generators/init/lib/update-serve-target.ts
  • packages/nx-plugin/src/generators/init/lib/update-test-target.ts
  • packages/nx-plugin/src/generators/init/lib/update-test-tsconfig.ts
  • packages/nx-plugin/src/generators/setup-vitest/generator.ts
  • packages/nx-plugin/src/generators/setup-vitest/lib/add-analog-dependencies.ts
  • packages/nx-plugin/src/generators/setup-vitest/lib/update-test-target.ts
  • packages/nx-plugin/src/generators/setup-vitest/lib/update-tsconfig.ts
  • packages/nx-plugin/src/utils/version-utils.ts
  • packages/router/package.json
  • packages/storybook-angular/src/lib/preset.ts
  • packages/storybook-angular/src/storybook-angular-subpaths.d.ts
  • packages/vite-plugin-angular-tools/src/builders/vite-dev-server/dev-server.impl.ts
  • packages/vite-plugin-angular-tools/src/builders/vite/vite-build.impl.ts
  • packages/vite-plugin-angular/package.json
  • packages/vitest-angular/package.json
  • tsconfig.base.json

- uses: actions/setup-node@v4
with:
node-version: '24.11.0'
node-version: '24.15.0'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Node version now drifts across the release-validation jobs.

Only migrate-angular-app moves to 24.15.0; create-analog (Line 42), create-analog-windows (Line 61), and create-nx-workspace (Line 78) still pin 24.11.0. If the bump was needed for the Angular 22 migration path, the other release-test jobs are now validating on a different Node than the one this release actually targets. Please confirm the divergence is intentional, or align all four jobs.

🔧 Align the remaining jobs
-          node-version: '24.11.0'
+          node-version: '24.15.0'

(apply at Lines 42, 61, and 78 if the bump should be workspace-wide)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/test-release.yml at line 98, The Node version is
inconsistent across release-validation jobs; update the node-version value in
the create-analog, create-analog-windows, and create-nx-workspace job
definitions to match migrate-angular-app (set node-version: '24.15.0') or
confirm intentional divergence; locate the node-version entries for the jobs
named migrate-angular-app, create-analog, create-analog-windows, and
create-nx-workspace and make the values identical if the bump should apply
workspace-wide.

Comment thread packages/content/package.json

const angularVersion = getInstalledPackageVersion(tree, '@angular/core');
const majorAngularVersion = major(coerce(angularVersion));
const majorAngularVersion = major(coerce(angularVersion!)!);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Unsafe non-null assertion on potentially-null angularVersion.

getInstalledPackageVersion returns string | null (line 111), but line 112 immediately asserts angularVersion! without a null check. If @angular/core is not found in package.json and no default version is provided, this will throw a runtime TypeError.

🛡️ Add null guard before deriving major version
  const angularVersion = getInstalledPackageVersion(tree, '`@angular/core`');
+ if (!angularVersion) {
+   throw new Error('Unable to determine Angular version. Please ensure `@angular/core` is installed.');
+ }
  const majorAngularVersion = major(coerce(angularVersion)!);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const majorAngularVersion = major(coerce(angularVersion!)!);
const angularVersion = getInstalledPackageVersion(tree, '`@angular/core`');
if (!angularVersion) {
throw new Error('Unable to determine Angular version. Please ensure `@angular/core` is installed.');
}
const majorAngularVersion = major(coerce(angularVersion)!);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nx-plugin/src/generators/app/generator.ts` at line 112, The code
uses angularVersion returned from getInstalledPackageVersion without a null
check when computing majorAngularVersion (const majorAngularVersion =
major(coerce(angularVersion!)!)); add a null guard: check if angularVersion is
null/undefined and either supply a safe default string (e.g., '0.0.0' or a
sensible fallback) or throw a clear error before calling coerce/major; update
the flow around getInstalledPackageVersion and the constant majorAngularVersion
so you never use the non-null assertion on angularVersion, referencing the
symbols angularVersion, getInstalledPackageVersion, coerce, major and
majorAngularVersion to locate the change.

project.root,
{
relativeSourceRoot: relative(project.root, project.sourceRoot),
relativeSourceRoot: relative(project.root, project.sourceRoot!),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Unsafe non-null assertion on project.sourceRoot.

ProjectConfiguration.sourceRoot is string | undefined, but the non-null assertion project.sourceRoot! on lines 230, 242, and 255 assumes it's always defined. If sourceRoot is undefined, relative(project.root, undefined) will throw at runtime.

🛡️ Add guard or fallback for undefined sourceRoot
  generateFiles(
    tree,
    joinPathFragments(__dirname, '..', 'files', 'tailwind/v2'),
    project.root,
    {
-     relativeSourceRoot: relative(project.root, project.sourceRoot!),
+     relativeSourceRoot: relative(project.root, project.sourceRoot ?? project.root),
      template: '',
    },
  );

Apply the same pattern to lines 242 and 255.

Also applies to: 242-242, 255-255

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nx-plugin/src/generators/app/lib/add-tailwind-helpers.ts` at line
230, Replace the unsafe non-null assertions on project.sourceRoot (used in the
relative(...) calls that set relativeSourceRoot and the other two occurrences)
with a guarded/fallback approach: check if project.sourceRoot is defined and
only call relative(project.root, project.sourceRoot) when it exists, otherwise
use a safe fallback such as relative(project.root, project.root) or an empty
string; update the code paths that build relativeSourceRoot and the other two
places to use project.sourceRoot ?? project.root (or skip computing the relative
path) instead of project.sourceRoot! so runtime errors are avoided when
sourceRoot is undefined.

}

if (belowMinimumSupportedAngularVersion(angularVersion)) {
if (belowMinimumSupportedAngularVersion(angularVersion!)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Unsafe non-null assertion on angularVersion after initialization.

While the initialization logic (lines 23-40) attempts to install Angular if missing, the init* functions return getInstalledPackageVersion(tree, '@angular/core', undefined, true), which can still return null if the package isn't found in package.json. The non-null assertion angularVersion! on line 43 will crash if the initialization doesn't successfully add the package to package.json.

🛡️ Add null check after initialization
    }
  }

+ if (!angularVersion) {
+   throw new Error('Failed to initialize Angular. Unable to determine Angular version.');
+ }
+
  if (belowMinimumSupportedAngularVersion(angularVersion)) {
    throw new Error(
      stripIndents`Analog only supports an Angular version of 15 and higher`,
    );
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (belowMinimumSupportedAngularVersion(angularVersion!)) {
}
}
if (!angularVersion) {
throw new Error('Failed to initialize Angular. Unable to determine Angular version.');
}
if (belowMinimumSupportedAngularVersion(angularVersion)) {
throw new Error(
stripIndents`Analog only supports an Angular version of 15 and higher`,
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nx-plugin/src/generators/app/lib/initialize-analog-workspace.ts` at
line 43, The call belowMinimumSupportedAngularVersion(angularVersion!) is unsafe
because init* helper functions can still leave angularVersion null; change the
logic in initialize-analog-workspace (the function containing the init* calls
and the belowMinimumSupportedAngularVersion check) to explicitly handle a null
angularVersion after initialization: call
getInstalledPackageVersion('`@angular/core`', ...) into the angularVersion
variable, check if angularVersion is null and then either log a clear error and
abort/return or throw a descriptive error before calling
belowMinimumSupportedAngularVersion, so you never use a non-null assertion on
angularVersion.


if (tree.exists(indexPath)) {
const indexContents = tree.read(indexPath, 'utf-8');
const indexContents = tree.read(indexPath, 'utf-8')!;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Non-null assertion on tree.read may fail for empty files.

While tree.exists(indexPath) confirms the file exists, tree.read() can still return null for empty files or read errors. The non-null assertion risks a runtime failure.

🛡️ Proposed guard to handle missing content
  if (tree.exists(indexPath)) {
-   const indexContents = tree.read(indexPath, 'utf-8')!;
+   const indexContents = tree.read(indexPath, 'utf-8');
+   if (!indexContents) {
+     return;
+   }
    let updatedIndex = indexContents.replace(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const indexContents = tree.read(indexPath, 'utf-8')!;
if (tree.exists(indexPath)) {
const indexContents = tree.read(indexPath, 'utf-8');
if (!indexContents) {
return;
}
let updatedIndex = indexContents.replace(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nx-plugin/src/generators/init/lib/update-index-html.ts` at line 13,
The current code uses a non-null assertion on tree.read(indexPath, 'utf-8')
assigned to indexContents which can be null for empty files or read errors;
change this to explicitly check the return value of tree.read(indexPath,
'utf-8') (e.g., const raw = tree.read(...); if (raw === null) { handle it: set
indexContents to '' or throw a clear error }) and then use that guarded value
(indexContents) for subsequent processing in update-index-html.ts so you avoid a
runtime crash when the file is empty or unreadable.

const isNx = tree.exists('/nx.json');

const projectConfig = projects.get(options.project);
const projectConfig = projects.get(options.project)!;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Runtime risk: non-null assertion assumes project exists.

If options.project refers to a non-existent project in the workspace, this assertion will throw a confusing TypeError rather than a clear validation error. Consider validating the project exists or providing a helpful error message.

🛡️ Defensive alternative
-  const projectConfig = projects.get(options.project)!;
+  const projectConfig = projects.get(options.project);
+  if (!projectConfig) {
+    throw new Error(`Project "${options.project}" not found in workspace`);
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const projectConfig = projects.get(options.project)!;
const projectConfig = projects.get(options.project);
if (!projectConfig) {
throw new Error(`Project "${options.project}" not found in workspace`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nx-plugin/src/generators/setup-vitest/generator.ts` at line 25, The
code uses a non-null assertion on projects.get(options.project) when assigning
projectConfig, which can throw if the project key doesn't exist; update the
generator to validate the lookup result first (e.g., const projectConfig =
projects.get(options.project); if (!projectConfig) throw new Error(`Project
"${options.project}" not found in workspace`);) so callers receive a clear
validation error instead of a runtime TypeError; locate the assignment to
projectConfig and replace the non-null assertion with this existence check
referencing options.project and projects.get.

) {
const angularVersion = getInstalledPackageVersion(tree, '@angular/core');
const majorAngularVersion = major(coerce(angularVersion));
const majorAngularVersion = major(coerce(angularVersion!)!);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Runtime risk: double non-null assertion on Angular version.

If @angular/core is not installed or has an unparseable version, this will throw at runtime. For a generator that configures Vitest for Angular projects, validating Angular's presence upfront would provide clearer error messages.

🛡️ Defensive alternative
  const angularVersion = getInstalledPackageVersion(tree, '`@angular/core`');
-  const majorAngularVersion = major(coerce(angularVersion!)!);
+  if (!angularVersion) {
+    throw new Error('`@angular/core` must be installed to set up Vitest');
+  }
+  const coercedVersion = coerce(angularVersion);
+  if (!coercedVersion) {
+    throw new Error(`Invalid `@angular/core` version: ${angularVersion}`);
+  }
+  const majorAngularVersion = major(coercedVersion);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nx-plugin/src/generators/setup-vitest/generator.ts` at line 47, The
current double non-null assertion at const majorAngularVersion =
major(coerce(angularVersion!)!); can throw at runtime if `@angular/core` is
missing or the version is unparseable; to fix, validate and handle the value
returned by coerce before calling major: first check angularVersion exists
(e.g., if (!angularVersion) throw new Error("Missing `@angular/core` version -
please install `@angular/core` or run this generator in an Angular workspace")),
then call const coerced = coerce(angularVersion); if (!coerced) throw new
Error("Unable to parse `@angular/core` version: " + String(angularVersion));
finally use const majorAngularVersion = major(coerced); replace the original
line with these guarded checks referencing angularVersion, coerce and major.

installedPackageVersion === 'next'
) {
return clean(defaultVersion) ?? coerce(defaultVersion).version;
return clean(defaultVersion!) ?? coerce(defaultVersion)!.version;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Non-null assertions on coerce results assume version strings are always valid.

While the control flow ensures defaultVersion exists at line 23 (due to the guard at lines 14-16), both coerce(defaultVersion)! (line 23) and coerce(installedPackageVersion)! (line 28) assume the version strings are always parseable. Malformed version strings in package.json dependencies will cause runtime crashes.

Consider handling null returns from coerce with explicit fallbacks or error messages.

🛡️ Add fallback for unparseable versions
  if (
    !installedPackageVersion ||
    installedPackageVersion === 'latest' ||
    installedPackageVersion === 'next'
  ) {
-   return clean(defaultVersion!) ?? coerce(defaultVersion)!.version;
+   const cleaned = clean(defaultVersion!);
+   if (cleaned) return cleaned;
+   const coerced = coerce(defaultVersion!);
+   if (!coerced) {
+     throw new Error(`Unable to parse version for ${packageName}: "${defaultVersion}"`);
+   }
+   return coerced.version;
  }

  return (
    (raw ? installedPackageVersion : clean(installedPackageVersion)) ??
-   coerce(installedPackageVersion)!.version
+   (coerce(installedPackageVersion)?.version ?? installedPackageVersion)
  );

Also applies to: 28-28

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/nx-plugin/src/utils/version-utils.ts` at line 23, The code in
version-utils.ts uses non-null assertions on coerce(defaultVersion) and
coerce(installedPackageVersion) which can throw if the version strings are
unparseable; update the logic in the function that returns the cleaned/coerced
version (references: defaultVersion, installedPackageVersion, coerce, clean) to
handle null from coerce by adding explicit fallbacks or error handling: first
attempt clean(defaultVersion) and if that yields null call
coerce(defaultVersion) and check for null before using .version, and do the same
for installedPackageVersion, returning a sensible default (e.g., throw a
descriptive error or return a safe fallback string) instead of using the
non-null assertion.

Comment on lines +15 to +16
const projectConfig = await context.getProjectMetadata(context.target!);
const projectName = context.target?.project;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Contradictory nullability handling on context.target.

Line 15 asserts context.target! is defined while line 16 guards it with context.target?.. Pick one stance: if the target can legitimately be undefined, the ! on line 15 just forwards undefined into getProjectMetadata and surfaces an opaque failure; if it can't, the ?. is dead weight. A single early guard keeps both lines honest.

🛡️ Proposed guard
-  const projectConfig = await context.getProjectMetadata(context.target!);
-  const projectName = context.target?.project;
+  if (!context.target) {
+    throw new Error('vite-dev-server builder requires a target.');
+  }
+  const projectConfig = await context.getProjectMetadata(context.target);
+  const projectName = context.target.project;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const projectConfig = await context.getProjectMetadata(context.target!);
const projectName = context.target?.project;
if (!context.target) {
throw new Error('vite-dev-server builder requires a target.');
}
const projectConfig = await context.getProjectMetadata(context.target);
const projectName = context.target.project;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/vite-plugin-angular-tools/src/builders/vite-dev-server/dev-server.impl.ts`
around lines 15 - 16, The code inconsistently handles context.target by using a
non-null assertion in the call to getProjectMetadata (context.target!) and an
optional chain when reading projectName (context.target?.project); fix by adding
a single early guard that checks for presence of context.target and bails with a
clear error if missing, then call getProjectMetadata(context.target) and read
context.target.project without optional chaining—update references in this block
(context.target, getProjectMetadata, projectName, projectConfig) so they assume
a defined target after the guard.

@brandonroberts brandonroberts merged commit 3961629 into beta Jun 1, 2026
37 checks passed
@brandonroberts brandonroberts deleted the feat/angular-22-nx-migration branch June 1, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope:astro-angular Changes in @analogjs/astro-angular scope:ci GitHub workflow changes scope:content Changes in @analogjs/content scope:nx-plugin Changes in @analogjs/nx-plugin scope:repo Repository metadata and tooling scope:router Changes in @analogjs/router scope:storybook-angular Changes in @analogjs/storybook-angular scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular scope:vitest-angular Changes in @analogjs/vitest-angular

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant