Skip to content

Commit 3c2461e

Browse files
committed
fix(@schematics/angular): remove karma config devkit package usages during application migration
When performing an `ng update` to Angular v20, the application migration will detect the usage of the `karma` builder's `karmaConfig` option and attempt to remove usages of the no longer needed `@angular-devkit/build-angular` karma framework and plugin usage. While the karma framework usage will be specifically ignored when using the `@angular/build:karma` builder, the plugin usage leverages a direct `require` within the configuration file. Regardless of the ability of the builder to ignore, neither usage is needed with `@angular/build:karma` and removing the code aligns with what would be generated by `ng generate config karma`.
1 parent c7e73fa commit 3c2461e

File tree

2 files changed

+135
-1
lines changed

2 files changed

+135
-1
lines changed

packages/schematics/angular/migrations/use-application-builder/migration.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ function updateProjects(tree: Tree, context: SchematicContext) {
232232

233233
// Use @angular/build directly if there is no devkit package usage
234234
if (!hasAngularDevkitUsage) {
235+
const karmaConfigFiles = new Set<string>();
236+
235237
for (const [, target] of allWorkspaceTargets(workspace)) {
236238
switch (target.builder) {
237239
case Builders.Application:
@@ -245,9 +247,15 @@ function updateProjects(tree: Tree, context: SchematicContext) {
245247
break;
246248
case Builders.Karma:
247249
target.builder = '@angular/build:karma';
248-
// Remove "builderMode" option since the builder will always use "application"
249250
for (const [, karmaOptions] of allTargetOptions(target)) {
251+
// Remove "builderMode" option since the builder will always use "application"
250252
delete karmaOptions['builderMode'];
253+
254+
// Collect custom karma configurations for @angular-devkit/build-angular plugin removal
255+
const karmaConfig = karmaOptions['karmaConfig'];
256+
if (karmaConfig && typeof karmaConfig === 'string') {
257+
karmaConfigFiles.add(karmaConfig);
258+
}
251259
}
252260
break;
253261
case Builders.NgPackagr:
@@ -292,6 +300,35 @@ function updateProjects(tree: Tree, context: SchematicContext) {
292300
}),
293301
);
294302
}
303+
304+
for (const karmaConfigFile of karmaConfigFiles) {
305+
if (!tree.exists(karmaConfigFile)) {
306+
continue;
307+
}
308+
309+
try {
310+
const originalKarmaConfigText = tree.readText(karmaConfigFile);
311+
const updatedKarmaConfigText = originalKarmaConfigText
312+
.replaceAll(`'@angular-devkit/build-angular'`, '')
313+
.replaceAll(`require('@angular-devkit/build-angular/plugins/karma'),`, '')
314+
.replaceAll(`require('@angular-devkit/build-angular/plugins/karma')`, '');
315+
316+
if (updatedKarmaConfigText.includes('@angular-devkit/build-angular')) {
317+
throw new Error(
318+
'Migration does not support found usage of "@angular-devkit/build-angular".',
319+
);
320+
} else {
321+
tree.overwrite(karmaConfigFile, updatedKarmaConfigText);
322+
}
323+
} catch (error) {
324+
const reason = error instanceof Error ? `Reason: ${error.message}` : '';
325+
context.logger.warn(
326+
`Unable to update custom karma configuration file ("${karmaConfigFile}"). ` +
327+
reason +
328+
'\nReferences to the "@angular-devkit/build-angular" package within the file may need to be removed manually.',
329+
);
330+
}
331+
}
295332
}
296333

297334
return chain(rules);

packages/schematics/angular/migrations/use-application-builder/migration_spec.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,103 @@ describe(`Migration to use the application builder`, () => {
130130
expect(builderMode).toBeUndefined();
131131
});
132132

133+
it(`should update file for 'karmaConfig' karma option (no require trailing comma)`, async () => {
134+
addWorkspaceTarget(tree, 'test', {
135+
'builder': Builders.Karma,
136+
'options': {
137+
'karmaConfig': './karma.conf.js',
138+
'polyfills': ['zone.js', 'zone.js/testing'],
139+
'tsConfig': 'projects/app-a/tsconfig.spec.json',
140+
},
141+
});
142+
tree.create(
143+
'./karma.conf.js',
144+
`
145+
module.exports = function (config) {
146+
config.set({
147+
basePath: '',
148+
frameworks: ['jasmine', '@angular-devkit/build-angular'],
149+
plugins: [
150+
require('karma-jasmine'),
151+
require('karma-chrome-launcher'),
152+
require('karma-jasmine-html-reporter'),
153+
require('karma-coverage'),
154+
require('@angular-devkit/build-angular/plugins/karma')
155+
]
156+
});
157+
};`,
158+
);
159+
160+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
161+
const {
162+
projects: { app },
163+
} = JSON.parse(newTree.readContent('/angular.json'));
164+
165+
const { karmaConfig } = app.architect['test'].options;
166+
expect(karmaConfig).toBe('./karma.conf.js');
167+
168+
const karmaConfigText = newTree.readText('./karma.conf.js');
169+
expect(karmaConfigText).not.toContain('@angular-devkit/build-angular');
170+
});
171+
172+
it(`should update file for 'karmaConfig' karma option (require trailing comma)`, async () => {
173+
addWorkspaceTarget(tree, 'test', {
174+
'builder': Builders.Karma,
175+
'options': {
176+
'karmaConfig': './karma.conf.js',
177+
'polyfills': ['zone.js', 'zone.js/testing'],
178+
'tsConfig': 'projects/app-a/tsconfig.spec.json',
179+
},
180+
});
181+
tree.create(
182+
'./karma.conf.js',
183+
`
184+
module.exports = function (config) {
185+
config.set({
186+
basePath: '',
187+
frameworks: ['jasmine', '@angular-devkit/build-angular'],
188+
plugins: [
189+
require('karma-jasmine'),
190+
require('karma-chrome-launcher'),
191+
require('karma-jasmine-html-reporter'),
192+
require('karma-coverage'),
193+
require('@angular-devkit/build-angular/plugins/karma'),
194+
]
195+
});
196+
};`,
197+
);
198+
199+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
200+
const {
201+
projects: { app },
202+
} = JSON.parse(newTree.readContent('/angular.json'));
203+
204+
const { karmaConfig } = app.architect['test'].options;
205+
expect(karmaConfig).toBe('./karma.conf.js');
206+
207+
const karmaConfigText = newTree.readText('./karma.conf.js');
208+
expect(karmaConfigText).not.toContain('@angular-devkit/build-angular');
209+
});
210+
211+
it(`should ignore missing file for 'karmaConfig' karma option`, async () => {
212+
addWorkspaceTarget(tree, 'test', {
213+
'builder': Builders.Karma,
214+
'options': {
215+
'karmaConfig': './karma.conf.js',
216+
'polyfills': ['zone.js', 'zone.js/testing'],
217+
'tsConfig': 'projects/app-a/tsconfig.spec.json',
218+
},
219+
});
220+
221+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
222+
const {
223+
projects: { app },
224+
} = JSON.parse(newTree.readContent('/angular.json'));
225+
226+
const { karmaConfig } = app.architect['test'].options;
227+
expect(karmaConfig).toBe('./karma.conf.js');
228+
});
229+
133230
it('should remove tilde prefix from CSS @import specifiers', async () => {
134231
// Replace outputPath
135232
tree.create(

0 commit comments

Comments
 (0)