From 13271c20775ad187bc7b564ad54a99b2b5de9c6b Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 10 Jul 2026 09:19:28 +0200 Subject: [PATCH 1/4] Angular: Install @analogjs/vite-plugin-angular in angular-to-angular-vite automigration --- code/frameworks/angular-vite/package.json | 2 -- .../fixes/angular-to-angular-vite.test.ts | 27 +++++++++++++++++++ .../fixes/angular-to-angular-vite.ts | 8 ++++++ .../src/generators/ANGULAR/index.ts | 8 +----- yarn.lock | 13 --------- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/code/frameworks/angular-vite/package.json b/code/frameworks/angular-vite/package.json index 220b30a41dbd..c2deef633773 100644 --- a/code/frameworks/angular-vite/package.json +++ b/code/frameworks/angular-vite/package.json @@ -73,7 +73,6 @@ "@angular-devkit/architect": "^0.2102.12", "@angular-devkit/build-angular": "^21.2.12", "@angular-devkit/core": "^21.2.12", - "@angular/animations": "^21.2.14", "@angular/common": "^21.2.14", "@angular/compiler": "^21.2.14", "@angular/compiler-cli": "^21.2.14", @@ -91,7 +90,6 @@ "@analogjs/vite-plugin-angular": ">=2.0.0", "@angular-devkit/architect": ">=0.2100.0 < 0.2300.0", "@angular-devkit/core": ">=21.0.0 < 23.0.0", - "@angular/animations": ">=21.0.0 < 23.0.0", "@angular/build": ">=21.0.0 < 23.0.0", "@angular/cli": ">=21.0.0 < 23.0.0", "@angular/common": ">=21.0.0 < 23.0.0", diff --git a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts index 3779fd7bdd83..6ee451ec473a 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts @@ -95,6 +95,7 @@ describe('angular-to-angular-vite', () => { vi.mocked(mockPackageManager.removeDependencies).mockResolvedValue(undefined); vi.mocked(mockPackageManager.addDependencies).mockResolvedValue(undefined); vi.mocked(mockPackageManager.getDependencyVersion).mockReturnValue(null); + vi.mocked(mockPackageManager.getAllDependencies).mockReturnValue({}); // Default: angular.json doesn't exist (AngularJSON gracefully skips it). Tests that need // angular.json content override this via `mockAngularJson(...)`. mockExistsSync.mockReturnValue(false); @@ -352,6 +353,32 @@ describe('angular-to-angular-vite', () => { } as any); expect(mockPackageManager.removeDependencies).toHaveBeenCalledWith([ANGULAR_PACKAGE]); + // @analogjs/vite-plugin-angular is a required peer of @storybook/angular-vite and is + // installed alongside the framework because yarn/pnpm do not auto-install missing peers. + expect(mockPackageManager.addDependencies).toHaveBeenCalledWith( + { type: 'devDependencies', skipInstall: true }, + [`${ANGULAR_VITE_PACKAGE}@9.0.0`, '@analogjs/vite-plugin-angular'] + ); + }); + + it('does not re-add @analogjs/vite-plugin-angular when the project already declares it', async () => { + mockPromptConfirm.mockResolvedValue(false); + mockReadFile.mockResolvedValue(`export default { framework: '${ANGULAR_PACKAGE}' };`); + vi.mocked(mockPackageManager.getAllDependencies).mockReturnValue({ + [ANGULAR_PACKAGE]: '^9.0.0', + '@analogjs/vite-plugin-angular': '^2.5.0', + }); + + await angularToAngularVite.run!({ + result: baseResult, + dryRun: false, + packageManager: mockPackageManager, + mainConfigPath: '/project/.storybook/main.ts', + storiesPaths: [], + configDir: '.storybook', + storybookVersion: '9.0.0', + } as any); + expect(mockPackageManager.addDependencies).toHaveBeenCalledWith( { type: 'devDependencies', skipInstall: true }, [`${ANGULAR_VITE_PACKAGE}@9.0.0`] diff --git a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts index 89bb4e6ba99f..428ad8efe9c0 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts @@ -527,8 +527,16 @@ export const angularToAngularVite: Fix = { } else { logger.debug('Updating dependencies...'); await packageManager.removeDependencies([ANGULAR_PACKAGE]); + + // @analogjs/vite-plugin-angular is a required peer dependency of @storybook/angular-vite + // that a webpack-based Angular project won't have. npm auto-installs missing peers, but + // yarn and pnpm do not, so it must be added explicitly — mirroring the init generator. + // An already-declared version is left untouched. `vite` is a direct dependency of the + // framework, so it needs no entry here. + const allDeps = packageManager.getAllDependencies(); await packageManager.addDependencies({ type: 'devDependencies', skipInstall: true }, [ `${ANGULAR_VITE_PACKAGE}@${storybookVersion}`, + ...(allDeps['@analogjs/vite-plugin-angular'] ? [] : ['@analogjs/vite-plugin-angular']), ]); } diff --git a/code/lib/create-storybook/src/generators/ANGULAR/index.ts b/code/lib/create-storybook/src/generators/ANGULAR/index.ts index c27a4c06888b..a1989fa13267 100644 --- a/code/lib/create-storybook/src/generators/ANGULAR/index.ts +++ b/code/lib/create-storybook/src/generators/ANGULAR/index.ts @@ -162,13 +162,7 @@ export default defineGeneratorModule({ const extraPackages = [ `@types/node@^${minNodeMajor}`, ...extraAngularDeps, - ...(isVite - ? [ - angularVersion ? `@angular/animations@${angularVersion}` : '@angular/animations', - '@analogjs/vite-plugin-angular', - 'vite', - ] - : []), + ...(isVite ? ['@analogjs/vite-plugin-angular', 'vite'] : []), ...(useCompodoc ? ['@compodoc/compodoc', '@storybook/addon-docs'] : []), ]; diff --git a/yarn.lock b/yarn.lock index 732da319b5f9..945b9488d792 100644 --- a/yarn.lock +++ b/yarn.lock @@ -446,17 +446,6 @@ __metadata: languageName: node linkType: hard -"@angular/animations@npm:^21.2.14": - version: 21.2.17 - resolution: "@angular/animations@npm:21.2.17" - dependencies: - tslib: "npm:^2.3.0" - peerDependencies: - "@angular/core": 21.2.17 - checksum: 10c0/e846d1572c6c03374cdd4115140b8630bd660e8e94f5c29f8d8a405bd79436b739105ad143738d16330f4ae3782cc28814fdd0eafa0c91fe6fbe05941d3b4195 - languageName: node - linkType: hard - "@angular/build@npm:19.2.19": version: 19.2.19 resolution: "@angular/build@npm:19.2.19" @@ -9310,7 +9299,6 @@ __metadata: "@angular-devkit/architect": "npm:^0.2102.12" "@angular-devkit/build-angular": "npm:^21.2.12" "@angular-devkit/core": "npm:^21.2.12" - "@angular/animations": "npm:^21.2.14" "@angular/common": "npm:^21.2.14" "@angular/compiler": "npm:^21.2.14" "@angular/compiler-cli": "npm:^21.2.14" @@ -9332,7 +9320,6 @@ __metadata: "@analogjs/vite-plugin-angular": ">=2.0.0" "@angular-devkit/architect": ">=0.2100.0 < 0.2300.0" "@angular-devkit/core": ">=21.0.0 < 23.0.0" - "@angular/animations": ">=21.0.0 < 23.0.0" "@angular/build": ">=21.0.0 < 23.0.0" "@angular/cli": ">=21.0.0 < 23.0.0" "@angular/common": ">=21.0.0 < 23.0.0" From c7a71515f0c9c4a0524026c54b9c572b8f572eec Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 10 Jul 2026 09:32:55 +0200 Subject: [PATCH 2/4] Angular: Keep @angular/animations, install it during angular-to-angular-vite automigration --- code/frameworks/angular-vite/package.json | 2 ++ .../fixes/angular-to-angular-vite.test.ts | 36 ++++++++++++++++--- .../fixes/angular-to-angular-vite.ts | 22 ++++++++---- .../src/generators/ANGULAR/index.ts | 8 ++++- yarn.lock | 13 +++++++ 5 files changed, 70 insertions(+), 11 deletions(-) diff --git a/code/frameworks/angular-vite/package.json b/code/frameworks/angular-vite/package.json index c2deef633773..220b30a41dbd 100644 --- a/code/frameworks/angular-vite/package.json +++ b/code/frameworks/angular-vite/package.json @@ -73,6 +73,7 @@ "@angular-devkit/architect": "^0.2102.12", "@angular-devkit/build-angular": "^21.2.12", "@angular-devkit/core": "^21.2.12", + "@angular/animations": "^21.2.14", "@angular/common": "^21.2.14", "@angular/compiler": "^21.2.14", "@angular/compiler-cli": "^21.2.14", @@ -90,6 +91,7 @@ "@analogjs/vite-plugin-angular": ">=2.0.0", "@angular-devkit/architect": ">=0.2100.0 < 0.2300.0", "@angular-devkit/core": ">=21.0.0 < 23.0.0", + "@angular/animations": ">=21.0.0 < 23.0.0", "@angular/build": ">=21.0.0 < 23.0.0", "@angular/cli": ">=21.0.0 < 23.0.0", "@angular/common": ">=21.0.0 < 23.0.0", diff --git a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts index 6ee451ec473a..0e79ebd1635f 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts @@ -353,20 +353,48 @@ describe('angular-to-angular-vite', () => { } as any); expect(mockPackageManager.removeDependencies).toHaveBeenCalledWith([ANGULAR_PACKAGE]); - // @analogjs/vite-plugin-angular is a required peer of @storybook/angular-vite and is - // installed alongside the framework because yarn/pnpm do not auto-install missing peers. + // Required peers of @storybook/angular-vite are installed alongside the framework because + // yarn/pnpm do not auto-install missing peer dependencies. expect(mockPackageManager.addDependencies).toHaveBeenCalledWith( { type: 'devDependencies', skipInstall: true }, - [`${ANGULAR_VITE_PACKAGE}@9.0.0`, '@analogjs/vite-plugin-angular'] + [`${ANGULAR_VITE_PACKAGE}@9.0.0`, '@analogjs/vite-plugin-angular', '@angular/animations'] ); }); - it('does not re-add @analogjs/vite-plugin-angular when the project already declares it', async () => { + it('pins @angular/animations to the declared @angular/core range', async () => { + mockPromptConfirm.mockResolvedValue(false); + mockReadFile.mockResolvedValue(`export default { framework: '${ANGULAR_PACKAGE}' };`); + vi.mocked(mockPackageManager.getDependencyVersion).mockImplementation((dep: string) => + dep === '@angular/core' ? '^21.2.0' : null + ); + + await angularToAngularVite.run!({ + result: baseResult, + dryRun: false, + packageManager: mockPackageManager, + mainConfigPath: '/project/.storybook/main.ts', + storiesPaths: [], + configDir: '.storybook', + storybookVersion: '9.0.0', + } as any); + + expect(mockPackageManager.addDependencies).toHaveBeenCalledWith( + { type: 'devDependencies', skipInstall: true }, + [ + `${ANGULAR_VITE_PACKAGE}@9.0.0`, + '@analogjs/vite-plugin-angular', + '@angular/animations@^21.2.0', + ] + ); + }); + + it('does not re-add peer dependencies the project already declares', async () => { mockPromptConfirm.mockResolvedValue(false); mockReadFile.mockResolvedValue(`export default { framework: '${ANGULAR_PACKAGE}' };`); vi.mocked(mockPackageManager.getAllDependencies).mockReturnValue({ [ANGULAR_PACKAGE]: '^9.0.0', '@analogjs/vite-plugin-angular': '^2.5.0', + '@angular/animations': '^21.0.0', }); await angularToAngularVite.run!({ diff --git a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts index 428ad8efe9c0..dd331da57b69 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts @@ -528,15 +528,25 @@ export const angularToAngularVite: Fix = { logger.debug('Updating dependencies...'); await packageManager.removeDependencies([ANGULAR_PACKAGE]); - // @analogjs/vite-plugin-angular is a required peer dependency of @storybook/angular-vite - // that a webpack-based Angular project won't have. npm auto-installs missing peers, but - // yarn and pnpm do not, so it must be added explicitly — mirroring the init generator. - // An already-declared version is left untouched. `vite` is a direct dependency of the - // framework, so it needs no entry here. + // Required peer dependencies of @storybook/angular-vite that a webpack-based Angular + // project may not have (@angular/animations is only an optional peer of + // @storybook/angular). npm auto-installs missing peers, but yarn and pnpm do not, so + // they must be added explicitly — mirroring the init generator. Packages the project + // already declares are left untouched. `vite` is a direct dependency of the framework, + // so it needs no entry here. const allDeps = packageManager.getAllDependencies(); + const angularVersion = packageManager.getDependencyVersion('@angular/core'); + const requiredPeerDeps = [ + { name: '@analogjs/vite-plugin-angular', spec: '@analogjs/vite-plugin-angular' }, + { + name: '@angular/animations', + spec: angularVersion ? `@angular/animations@${angularVersion}` : '@angular/animations', + }, + ]; + await packageManager.addDependencies({ type: 'devDependencies', skipInstall: true }, [ `${ANGULAR_VITE_PACKAGE}@${storybookVersion}`, - ...(allDeps['@analogjs/vite-plugin-angular'] ? [] : ['@analogjs/vite-plugin-angular']), + ...requiredPeerDeps.filter(({ name }) => !allDeps[name]).map(({ spec }) => spec), ]); } diff --git a/code/lib/create-storybook/src/generators/ANGULAR/index.ts b/code/lib/create-storybook/src/generators/ANGULAR/index.ts index a1989fa13267..c27a4c06888b 100644 --- a/code/lib/create-storybook/src/generators/ANGULAR/index.ts +++ b/code/lib/create-storybook/src/generators/ANGULAR/index.ts @@ -162,7 +162,13 @@ export default defineGeneratorModule({ const extraPackages = [ `@types/node@^${minNodeMajor}`, ...extraAngularDeps, - ...(isVite ? ['@analogjs/vite-plugin-angular', 'vite'] : []), + ...(isVite + ? [ + angularVersion ? `@angular/animations@${angularVersion}` : '@angular/animations', + '@analogjs/vite-plugin-angular', + 'vite', + ] + : []), ...(useCompodoc ? ['@compodoc/compodoc', '@storybook/addon-docs'] : []), ]; diff --git a/yarn.lock b/yarn.lock index 945b9488d792..732da319b5f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -446,6 +446,17 @@ __metadata: languageName: node linkType: hard +"@angular/animations@npm:^21.2.14": + version: 21.2.17 + resolution: "@angular/animations@npm:21.2.17" + dependencies: + tslib: "npm:^2.3.0" + peerDependencies: + "@angular/core": 21.2.17 + checksum: 10c0/e846d1572c6c03374cdd4115140b8630bd660e8e94f5c29f8d8a405bd79436b739105ad143738d16330f4ae3782cc28814fdd0eafa0c91fe6fbe05941d3b4195 + languageName: node + linkType: hard + "@angular/build@npm:19.2.19": version: 19.2.19 resolution: "@angular/build@npm:19.2.19" @@ -9299,6 +9310,7 @@ __metadata: "@angular-devkit/architect": "npm:^0.2102.12" "@angular-devkit/build-angular": "npm:^21.2.12" "@angular-devkit/core": "npm:^21.2.12" + "@angular/animations": "npm:^21.2.14" "@angular/common": "npm:^21.2.14" "@angular/compiler": "npm:^21.2.14" "@angular/compiler-cli": "npm:^21.2.14" @@ -9320,6 +9332,7 @@ __metadata: "@analogjs/vite-plugin-angular": ">=2.0.0" "@angular-devkit/architect": ">=0.2100.0 < 0.2300.0" "@angular-devkit/core": ">=21.0.0 < 23.0.0" + "@angular/animations": ">=21.0.0 < 23.0.0" "@angular/build": ">=21.0.0 < 23.0.0" "@angular/cli": ">=21.0.0 < 23.0.0" "@angular/common": ">=21.0.0 < 23.0.0" From d9eb708bd08e0590a9b66226a2dbd3a7dede145c Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 10 Jul 2026 09:37:11 +0200 Subject: [PATCH 3/4] Angular: Drop unnecessary @angular/animations install from automigration --- .../fixes/angular-to-angular-vite.test.ts | 36 +++---------------- .../fixes/angular-to-angular-vite.ts | 22 ++++-------- 2 files changed, 10 insertions(+), 48 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts index 0e79ebd1635f..6ee451ec473a 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.test.ts @@ -353,48 +353,20 @@ describe('angular-to-angular-vite', () => { } as any); expect(mockPackageManager.removeDependencies).toHaveBeenCalledWith([ANGULAR_PACKAGE]); - // Required peers of @storybook/angular-vite are installed alongside the framework because - // yarn/pnpm do not auto-install missing peer dependencies. + // @analogjs/vite-plugin-angular is a required peer of @storybook/angular-vite and is + // installed alongside the framework because yarn/pnpm do not auto-install missing peers. expect(mockPackageManager.addDependencies).toHaveBeenCalledWith( { type: 'devDependencies', skipInstall: true }, - [`${ANGULAR_VITE_PACKAGE}@9.0.0`, '@analogjs/vite-plugin-angular', '@angular/animations'] + [`${ANGULAR_VITE_PACKAGE}@9.0.0`, '@analogjs/vite-plugin-angular'] ); }); - it('pins @angular/animations to the declared @angular/core range', async () => { - mockPromptConfirm.mockResolvedValue(false); - mockReadFile.mockResolvedValue(`export default { framework: '${ANGULAR_PACKAGE}' };`); - vi.mocked(mockPackageManager.getDependencyVersion).mockImplementation((dep: string) => - dep === '@angular/core' ? '^21.2.0' : null - ); - - await angularToAngularVite.run!({ - result: baseResult, - dryRun: false, - packageManager: mockPackageManager, - mainConfigPath: '/project/.storybook/main.ts', - storiesPaths: [], - configDir: '.storybook', - storybookVersion: '9.0.0', - } as any); - - expect(mockPackageManager.addDependencies).toHaveBeenCalledWith( - { type: 'devDependencies', skipInstall: true }, - [ - `${ANGULAR_VITE_PACKAGE}@9.0.0`, - '@analogjs/vite-plugin-angular', - '@angular/animations@^21.2.0', - ] - ); - }); - - it('does not re-add peer dependencies the project already declares', async () => { + it('does not re-add @analogjs/vite-plugin-angular when the project already declares it', async () => { mockPromptConfirm.mockResolvedValue(false); mockReadFile.mockResolvedValue(`export default { framework: '${ANGULAR_PACKAGE}' };`); vi.mocked(mockPackageManager.getAllDependencies).mockReturnValue({ [ANGULAR_PACKAGE]: '^9.0.0', '@analogjs/vite-plugin-angular': '^2.5.0', - '@angular/animations': '^21.0.0', }); await angularToAngularVite.run!({ diff --git a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts index dd331da57b69..428ad8efe9c0 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts @@ -528,25 +528,15 @@ export const angularToAngularVite: Fix = { logger.debug('Updating dependencies...'); await packageManager.removeDependencies([ANGULAR_PACKAGE]); - // Required peer dependencies of @storybook/angular-vite that a webpack-based Angular - // project may not have (@angular/animations is only an optional peer of - // @storybook/angular). npm auto-installs missing peers, but yarn and pnpm do not, so - // they must be added explicitly — mirroring the init generator. Packages the project - // already declares are left untouched. `vite` is a direct dependency of the framework, - // so it needs no entry here. + // @analogjs/vite-plugin-angular is a required peer dependency of @storybook/angular-vite + // that a webpack-based Angular project won't have. npm auto-installs missing peers, but + // yarn and pnpm do not, so it must be added explicitly — mirroring the init generator. + // An already-declared version is left untouched. `vite` is a direct dependency of the + // framework, so it needs no entry here. const allDeps = packageManager.getAllDependencies(); - const angularVersion = packageManager.getDependencyVersion('@angular/core'); - const requiredPeerDeps = [ - { name: '@analogjs/vite-plugin-angular', spec: '@analogjs/vite-plugin-angular' }, - { - name: '@angular/animations', - spec: angularVersion ? `@angular/animations@${angularVersion}` : '@angular/animations', - }, - ]; - await packageManager.addDependencies({ type: 'devDependencies', skipInstall: true }, [ `${ANGULAR_VITE_PACKAGE}@${storybookVersion}`, - ...requiredPeerDeps.filter(({ name }) => !allDeps[name]).map(({ spec }) => spec), + ...(allDeps['@analogjs/vite-plugin-angular'] ? [] : ['@analogjs/vite-plugin-angular']), ]); } From 8487b1abf0cbe386639e6ed21fddcf1ae59244ca Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Fri, 10 Jul 2026 09:42:29 +0200 Subject: [PATCH 4/4] Apply suggestion from @valentinpalkovic --- .../src/automigrate/fixes/angular-to-angular-vite.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts index 428ad8efe9c0..42aa02c9c2a3 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/angular-to-angular-vite.ts @@ -528,11 +528,6 @@ export const angularToAngularVite: Fix = { logger.debug('Updating dependencies...'); await packageManager.removeDependencies([ANGULAR_PACKAGE]); - // @analogjs/vite-plugin-angular is a required peer dependency of @storybook/angular-vite - // that a webpack-based Angular project won't have. npm auto-installs missing peers, but - // yarn and pnpm do not, so it must be added explicitly — mirroring the init generator. - // An already-declared version is left untouched. `vite` is a direct dependency of the - // framework, so it needs no entry here. const allDeps = packageManager.getAllDependencies(); await packageManager.addDependencies({ type: 'devDependencies', skipInstall: true }, [ `${ANGULAR_VITE_PACKAGE}@${storybookVersion}`,