Skip to content

Commit 2e3cfd5

Browse files
clydinalan-agius4
authored andcommitted
feat(@schematics/angular): add migration to remove default Karma configurations
Introduces a new migration that removes `karma.conf.js` files from projects if they contain only the default generated configuration. When a file is removed, the corresponding `karmaConfig` option in `angular.json` is also removed from the project's test target. The Angular CLI now provides a default Karma configuration internally, making explicit configuration files unnecessary for projects that do not require customization. This migration helps to clean up and simplify existing projects by removing these now redundant files. The migration will safely skip any `karma.conf.js` file that has been modified or contains values that cannot be reliably analyzed.
1 parent 716195e commit 2e3cfd5

File tree

6 files changed

+576
-15
lines changed

6 files changed

+576
-15
lines changed

packages/schematics/angular/migrations/karma/karma-config-analyzer.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ export function analyzeKarmaConfig(content: string): KarmaConfigAnalysis {
119119
}
120120
case ts.SyntaxKind.PropertyAccessExpression: {
121121
const propAccessExpr = node as ts.PropertyAccessExpression;
122+
123+
// Handle config constants like `config.LOG_INFO`
124+
if (
125+
ts.isIdentifier(propAccessExpr.expression) &&
126+
propAccessExpr.expression.text === 'config'
127+
) {
128+
return `config.${propAccessExpr.name.text}`;
129+
}
130+
122131
const value = extractValue(propAccessExpr.expression);
123132
if (isRequireInfo(value)) {
124133
const currentExport = value.export

packages/schematics/angular/migrations/karma/karma-config-analyzer_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ describe('Karma Config Analyzer', () => {
7373
expect(dirInfo.isCall).toBe(true);
7474
expect(dirInfo.arguments as unknown).toEqual(['__dirname', './coverage/test-project']);
7575

76-
// config.LOG_INFO is a variable, so it should be flagged as unsupported
77-
expect(hasUnsupportedValues).toBe(true);
76+
expect(settings.get('logLevel') as unknown).toBe('config.LOG_INFO');
77+
expect(hasUnsupportedValues).toBe(false);
7878
});
7979

8080
it('should return an empty map for an empty config file', () => {

packages/schematics/angular/migrations/karma/karma-config-comparer.ts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,33 @@ import { isDeepStrictEqual } from 'node:util';
1212
import { relativePathToWorkspaceRoot } from '../../utility/paths';
1313
import { KarmaConfigAnalysis, KarmaConfigValue, analyzeKarmaConfig } from './karma-config-analyzer';
1414

15+
/**
16+
* Represents the difference between two Karma configurations.
17+
*/
1518
export interface KarmaConfigDiff {
19+
/** A map of settings that were added in the project's configuration. */
1620
added: Map<string, KarmaConfigValue>;
21+
22+
/** A map of settings that were removed from the project's configuration. */
1723
removed: Map<string, KarmaConfigValue>;
24+
25+
/** A map of settings that were modified between the two configurations. */
1826
modified: Map<string, { projectValue: KarmaConfigValue; defaultValue: KarmaConfigValue }>;
27+
28+
/** A boolean indicating if the comparison is reliable (i.e., no unsupported values were found). */
1929
isReliable: boolean;
2030
}
2131

2232
/**
2333
* Generates the default Karma configuration file content as a string.
24-
* @param relativePathToWorkspaceRoot The relative path from the project root to the workspace root.
25-
* @param folderName The name of the project folder.
34+
* @param relativePathToWorkspaceRoot The relative path from the Karma config file to the workspace root.
35+
* @param projectName The name of the project.
2636
* @param needDevkitPlugin A boolean indicating if the devkit plugin is needed.
2737
* @returns The content of the default `karma.conf.js` file.
2838
*/
2939
export async function generateDefaultKarmaConfig(
3040
relativePathToWorkspaceRoot: string,
31-
folderName: string,
41+
projectName: string,
3242
needDevkitPlugin: boolean,
3343
): Promise<string> {
3444
const templatePath = path.join(__dirname, '../../config/files/karma.conf.js.template');
@@ -40,7 +50,7 @@ export async function generateDefaultKarmaConfig(
4050
/<%= relativePathToWorkspaceRoot %>/g,
4151
path.normalize(relativePathToWorkspaceRoot).replace(/\\/g, '/'),
4252
)
43-
.replace(/<%= folderName %>/g, folderName);
53+
.replace(/<%= folderName %>/g, projectName);
4454

4555
const devkitPluginRegex = /<% if \(needDevkitPlugin\) { %>(.*?)<% } %>/gs;
4656
const replacement = needDevkitPlugin ? '$1' : '';
@@ -52,8 +62,8 @@ export async function generateDefaultKarmaConfig(
5262
/**
5363
* Compares two Karma configuration analyses and returns the difference.
5464
* @param projectAnalysis The analysis of the project's configuration.
55-
* @param defaultAnalysis The analysis of the default configuration.
56-
* @returns A diff object representing the changes.
65+
* @param defaultAnalysis The analysis of the default configuration to compare against.
66+
* @returns A diff object representing the changes between the two configurations.
5767
*/
5868
export function compareKarmaConfigs(
5969
projectAnalysis: KarmaConfigAnalysis,
@@ -93,8 +103,8 @@ export function compareKarmaConfigs(
93103

94104
/**
95105
* Checks if there are any differences in the provided Karma configuration diff.
96-
* @param diff The Karma configuration diff object.
97-
* @returns True if there are any differences, false otherwise.
106+
* @param diff The Karma configuration diff object to check.
107+
* @returns True if there are any differences; false otherwise.
98108
*/
99109
export function hasDifferences(diff: KarmaConfigDiff): boolean {
100110
return diff.added.size > 0 || diff.removed.size > 0 || diff.modified.size > 0;
@@ -103,41 +113,46 @@ export function hasDifferences(diff: KarmaConfigDiff): boolean {
103113
/**
104114
* Compares a project's Karma configuration with the default configuration.
105115
* @param projectConfigContent The content of the project's `karma.conf.js` file.
106-
* @param projectRoot The root of the project's project.
107-
* @param needDevkitPlugin A boolean indicating if the devkit plugin is needed.
116+
* @param projectRoot The root directory of the project.
117+
* @param needDevkitPlugin A boolean indicating if the devkit plugin is needed for the default config.
118+
* @param karmaConfigPath The path to the Karma configuration file, used to resolve relative paths.
108119
* @returns A diff object representing the changes.
109120
*/
110121
export async function compareKarmaConfigToDefault(
111122
projectConfigContent: string,
112123
projectRoot: string,
113124
needDevkitPlugin: boolean,
125+
karmaConfigPath?: string,
114126
): Promise<KarmaConfigDiff>;
115127

116128
/**
117129
* Compares a project's Karma configuration with the default configuration.
118130
* @param projectAnalysis The analysis of the project's configuration.
119-
* @param projectRoot The root of the project's project.
120-
* @param needDevkitPlugin A boolean indicating if the devkit plugin is needed.
131+
* @param projectRoot The root directory of the project.
132+
* @param needDevkitPlugin A boolean indicating if the devkit plugin is needed for the default config.
133+
* @param karmaConfigPath The path to the Karma configuration file, used to resolve relative paths.
121134
* @returns A diff object representing the changes.
122135
*/
123136
export async function compareKarmaConfigToDefault(
124137
projectAnalysis: KarmaConfigAnalysis,
125138
projectRoot: string,
126139
needDevkitPlugin: boolean,
140+
karmaConfigPath?: string,
127141
): Promise<KarmaConfigDiff>;
128142

129143
export async function compareKarmaConfigToDefault(
130144
projectConfigOrAnalysis: string | KarmaConfigAnalysis,
131145
projectRoot: string,
132146
needDevkitPlugin: boolean,
147+
karmaConfigPath?: string,
133148
): Promise<KarmaConfigDiff> {
134149
const projectAnalysis =
135150
typeof projectConfigOrAnalysis === 'string'
136151
? analyzeKarmaConfig(projectConfigOrAnalysis)
137152
: projectConfigOrAnalysis;
138153

139154
const defaultContent = await generateDefaultKarmaConfig(
140-
relativePathToWorkspaceRoot(projectRoot),
155+
relativePathToWorkspaceRoot(karmaConfigPath ? path.dirname(karmaConfigPath) : projectRoot),
141156
path.basename(projectRoot),
142157
needDevkitPlugin,
143158
);
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import type { Rule, Tree } from '@angular-devkit/schematics';
10+
import { allTargetOptions, updateWorkspace } from '../../utility/workspace';
11+
import { Builders } from '../../utility/workspace-models';
12+
import { analyzeKarmaConfig } from './karma-config-analyzer';
13+
import { compareKarmaConfigToDefault, hasDifferences } from './karma-config-comparer';
14+
15+
function updateProjects(tree: Tree): Rule {
16+
return updateWorkspace(async (workspace) => {
17+
const removableKarmaConfigs = new Map<string, boolean>();
18+
19+
for (const [, project] of workspace.projects) {
20+
for (const [, target] of project.targets) {
21+
let needDevkitPlugin = false;
22+
switch (target.builder) {
23+
case Builders.Karma:
24+
needDevkitPlugin = true;
25+
break;
26+
case Builders.BuildKarma:
27+
break;
28+
default:
29+
continue;
30+
}
31+
32+
for (const [, options] of allTargetOptions(target, false)) {
33+
const karmaConfig = options['karmaConfig'];
34+
if (typeof karmaConfig !== 'string') {
35+
continue;
36+
}
37+
38+
let isRemovable = removableKarmaConfigs.get(karmaConfig);
39+
if (isRemovable === undefined && tree.exists(karmaConfig)) {
40+
const content = tree.readText(karmaConfig);
41+
const analysis = analyzeKarmaConfig(content);
42+
43+
if (analysis.hasUnsupportedValues) {
44+
// Cannot safely determine if the file is removable.
45+
isRemovable = false;
46+
} else {
47+
const diff = await compareKarmaConfigToDefault(
48+
analysis,
49+
project.root,
50+
needDevkitPlugin,
51+
karmaConfig,
52+
);
53+
isRemovable = !hasDifferences(diff) && diff.isReliable;
54+
}
55+
56+
removableKarmaConfigs.set(karmaConfig, isRemovable);
57+
58+
if (isRemovable) {
59+
tree.delete(karmaConfig);
60+
}
61+
}
62+
63+
if (isRemovable) {
64+
delete options['karmaConfig'];
65+
}
66+
}
67+
}
68+
}
69+
});
70+
}
71+
72+
export default function (): Rule {
73+
return updateProjects;
74+
}

0 commit comments

Comments
 (0)