@schematics/angular 20.2.0-next.3 → 20.2.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.
@@ -0,0 +1,49 @@
1
+ <% if (frontmatter) { %><%= frontmatter %>
2
+
3
+ <% } %>You are an expert in TypeScript, Angular, and scalable web application development. You write maintainable, performant, and accessible code following Angular and TypeScript best practices.
4
+
5
+ ## TypeScript Best Practices
6
+
7
+ - Use strict type checking
8
+ - Prefer type inference when the type is obvious
9
+ - Avoid the `any` type; use `unknown` when type is uncertain
10
+
11
+ ## Angular Best Practices
12
+
13
+ - Always use standalone components over NgModules
14
+ - Must NOT set `standalone: true` inside Angular decorators. It's the default.
15
+ - Use signals for state management
16
+ - Implement lazy loading for feature routes
17
+ - Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
18
+ - Use `NgOptimizedImage` for all static images.
19
+ - `NgOptimizedImage` does not work for inline base64 images.
20
+
21
+ ## Components
22
+
23
+ - Keep components small and focused on a single responsibility
24
+ - Use `input()` and `output()` functions instead of decorators
25
+ - Use `computed()` for derived state
26
+ - Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator
27
+ - Prefer inline templates for small components
28
+ - Prefer Reactive forms instead of Template-driven ones
29
+ - Do NOT use `ngClass`, use `class` bindings instead
30
+ - Do NOT use `ngStyle`, use `style` bindings instead
31
+
32
+ ## State Management
33
+
34
+ - Use signals for local component state
35
+ - Use `computed()` for derived state
36
+ - Keep state transformations pure and predictable
37
+ - Do NOT use `mutate` on signals, use `update` or `set` instead
38
+
39
+ ## Templates
40
+
41
+ - Keep templates simple and avoid complex logic
42
+ - Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
43
+ - Use the async pipe to handle observables
44
+
45
+ ## Services
46
+
47
+ - Design services around a single responsibility
48
+ - Use the `providedIn: 'root'` option for singleton services
49
+ - Use the `inject()` function instead of constructor injection
@@ -0,0 +1,10 @@
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
+ import { Rule } from '@angular-devkit/schematics';
9
+ import { Schema as ConfigOptions } from './schema';
10
+ export default function ({ tool }: ConfigOptions): Rule;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.default = default_1;
11
+ const schematics_1 = require("@angular-devkit/schematics");
12
+ const schema_1 = require("./schema");
13
+ const AI_TOOLS = {
14
+ gemini: {
15
+ rulesName: 'GEMINI.md',
16
+ directory: '.gemini',
17
+ },
18
+ claude: {
19
+ rulesName: 'CLAUDE.md',
20
+ directory: '.claude',
21
+ },
22
+ copilot: {
23
+ rulesName: 'copilot-instructions.md',
24
+ directory: '.github',
25
+ },
26
+ windsurf: {
27
+ rulesName: 'guidelines.md',
28
+ directory: '.windsurf/rules',
29
+ },
30
+ jetbrains: {
31
+ rulesName: 'guidelines.md',
32
+ directory: '.junie',
33
+ },
34
+ // Cursor file has a front matter section.
35
+ cursor: {
36
+ rulesName: 'cursor.mdc',
37
+ directory: '.cursor/rules',
38
+ frontmatter: `---\ncontext: true\npriority: high\nscope: project\n---`,
39
+ },
40
+ };
41
+ function default_1({ tool }) {
42
+ if (!tool || tool.includes(schema_1.Tool.None)) {
43
+ return (0, schematics_1.noop)();
44
+ }
45
+ const files = tool.map((selectedTool) => AI_TOOLS[selectedTool]);
46
+ const rules = files.map(({ rulesName, directory, frontmatter }) => (0, schematics_1.mergeWith)((0, schematics_1.apply)((0, schematics_1.url)('./files'), [
47
+ (0, schematics_1.applyTemplates)({
48
+ ...schematics_1.strings,
49
+ rulesName,
50
+ frontmatter,
51
+ }),
52
+ (0, schematics_1.move)(directory),
53
+ ])));
54
+ return (0, schematics_1.chain)(rules);
55
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Generates AI configuration files for Angular projects. This schematic creates
3
+ * configuration files that help AI tools follow Angular best practices, improving the
4
+ * quality of AI-generated code and suggestions.
5
+ */
6
+ export type Schema = {
7
+ /**
8
+ * Specifies which AI tools to generate configuration files for. These file are used to
9
+ * improve the outputs of AI tools by following the best practices.
10
+ */
11
+ tool?: Tool[];
12
+ };
13
+ export declare enum Tool {
14
+ Claude = "claude",
15
+ Copilot = "copilot",
16
+ Cursor = "cursor",
17
+ Gemini = "gemini",
18
+ Jetbrains = "jetbrains",
19
+ None = "none",
20
+ Windsurf = "windsurf"
21
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ // THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
3
+ // CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.Tool = void 0;
6
+ var Tool;
7
+ (function (Tool) {
8
+ Tool["Claude"] = "claude";
9
+ Tool["Copilot"] = "copilot";
10
+ Tool["Cursor"] = "cursor";
11
+ Tool["Gemini"] = "gemini";
12
+ Tool["Jetbrains"] = "jetbrains";
13
+ Tool["None"] = "none";
14
+ Tool["Windsurf"] = "windsurf";
15
+ })(Tool || (exports.Tool = Tool = {}));
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "$id": "SchematicsAngularAIConfig",
4
+ "title": "Angular AI Config File Options Schema",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "description": "Generates AI configuration files for Angular projects. This schematic creates configuration files that help AI tools follow Angular best practices, improving the quality of AI-generated code and suggestions.",
8
+ "properties": {
9
+ "tool": {
10
+ "type": "array",
11
+ "uniqueItems": true,
12
+ "default": "none",
13
+ "x-prompt": "Which AI tools do you want to configure with Angular best practices? https://angular.dev/ai/develop-with-ai",
14
+ "description": "Specifies which AI tools to generate configuration files for. These file are used to improve the outputs of AI tools by following the best practices.",
15
+ "minItems": 1,
16
+ "items": {
17
+ "type": "string",
18
+ "enum": ["none", "gemini", "copilot", "claude", "cursor", "jetbrains", "windsurf"]
19
+ }
20
+ }
21
+ },
22
+ "if": {
23
+ "properties": {
24
+ "tool": {
25
+ "contains": {
26
+ "const": "none"
27
+ }
28
+ }
29
+ },
30
+ "required": ["tool"]
31
+ },
32
+ "then": {
33
+ "properties": {
34
+ "tool": {
35
+ "maxItems": 1
36
+ }
37
+ }
38
+ }
39
+ }
@@ -10,14 +10,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.default = default_1;
11
11
  const core_1 = require("@angular-devkit/core");
12
12
  const schematics_1 = require("@angular-devkit/schematics");
13
- const tasks_1 = require("@angular-devkit/schematics/tasks");
14
- const dependencies_1 = require("../utility/dependencies");
13
+ const dependency_1 = require("../utility/dependency");
15
14
  const json_file_1 = require("../utility/json-file");
16
15
  const latest_versions_1 = require("../utility/latest-versions");
17
16
  const paths_1 = require("../utility/paths");
18
17
  const workspace_1 = require("../utility/workspace");
19
18
  const workspace_models_1 = require("../utility/workspace-models");
20
19
  const schema_1 = require("./schema");
20
+ const APPLICATION_DEV_DEPENDENCIES = [
21
+ { name: '@angular/compiler-cli', version: latest_versions_1.latestVersions.Angular },
22
+ { name: '@angular/build', version: latest_versions_1.latestVersions.AngularBuild },
23
+ { name: 'typescript', version: latest_versions_1.latestVersions['typescript'] },
24
+ ];
21
25
  function addTsProjectReference(...paths) {
22
26
  return (host) => {
23
27
  if (!host.exists('tsconfig.json')) {
@@ -104,43 +108,26 @@ function default_1(options) {
104
108
  };
105
109
  }
106
110
  function addDependenciesToPackageJson(options) {
107
- return (host, context) => {
108
- [
109
- {
110
- type: dependencies_1.NodeDependencyType.Dev,
111
- name: '@angular/compiler-cli',
112
- version: latest_versions_1.latestVersions.Angular,
113
- },
114
- {
115
- type: dependencies_1.NodeDependencyType.Dev,
116
- name: '@angular/build',
117
- version: latest_versions_1.latestVersions.AngularBuild,
118
- },
119
- {
120
- type: dependencies_1.NodeDependencyType.Dev,
121
- name: 'typescript',
122
- version: latest_versions_1.latestVersions['typescript'],
123
- },
124
- ].forEach((dependency) => (0, dependencies_1.addPackageJsonDependency)(host, dependency));
125
- if (!options.zoneless) {
126
- (0, dependencies_1.addPackageJsonDependency)(host, {
127
- type: dependencies_1.NodeDependencyType.Default,
128
- name: 'zone.js',
129
- version: latest_versions_1.latestVersions['zone.js'],
130
- });
131
- }
132
- if (options.style === schema_1.Style.Less) {
133
- (0, dependencies_1.addPackageJsonDependency)(host, {
134
- type: dependencies_1.NodeDependencyType.Dev,
135
- name: 'less',
136
- version: latest_versions_1.latestVersions['less'],
137
- });
138
- }
139
- if (!options.skipInstall) {
140
- context.addTask(new tasks_1.NodePackageInstallTask());
141
- }
142
- return host;
143
- };
111
+ const rules = APPLICATION_DEV_DEPENDENCIES.map((dependency) => (0, dependency_1.addDependency)(dependency.name, dependency.version, {
112
+ type: dependency_1.DependencyType.Dev,
113
+ existing: dependency_1.ExistingBehavior.Skip,
114
+ install: options.skipInstall ? dependency_1.InstallBehavior.None : dependency_1.InstallBehavior.Auto,
115
+ }));
116
+ if (!options.zoneless) {
117
+ rules.push((0, dependency_1.addDependency)('zone.js', latest_versions_1.latestVersions['zone.js'], {
118
+ type: dependency_1.DependencyType.Default,
119
+ existing: dependency_1.ExistingBehavior.Skip,
120
+ install: options.skipInstall ? dependency_1.InstallBehavior.None : dependency_1.InstallBehavior.Auto,
121
+ }));
122
+ }
123
+ if (options.style === schema_1.Style.Less) {
124
+ rules.push((0, dependency_1.addDependency)('less', latest_versions_1.latestVersions['less'], {
125
+ type: dependency_1.DependencyType.Dev,
126
+ existing: dependency_1.ExistingBehavior.Skip,
127
+ install: options.skipInstall ? dependency_1.InstallBehavior.None : dependency_1.InstallBehavior.Auto,
128
+ }));
129
+ }
130
+ return (0, schematics_1.chain)(rules);
144
131
  }
145
132
  function addAppToWorkspaceFile(options, appDir) {
146
133
  let projectRoot = appDir;
package/collection.json CHANGED
@@ -131,6 +131,11 @@
131
131
  "factory": "./config",
132
132
  "schema": "./config/schema.json",
133
133
  "description": "Generates a configuration file."
134
+ },
135
+ "ai-config": {
136
+ "factory": "./ai-config",
137
+ "schema": "./ai-config/schema.json",
138
+ "description": "Generates an AI tool configuration file."
134
139
  }
135
140
  }
136
141
  }
package/library/index.js CHANGED
@@ -9,14 +9,19 @@
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.default = default_1;
11
11
  const schematics_1 = require("@angular-devkit/schematics");
12
- const tasks_1 = require("@angular-devkit/schematics/tasks");
13
12
  const posix_1 = require("node:path/posix");
14
- const dependencies_1 = require("../utility/dependencies");
13
+ const dependency_1 = require("../utility/dependency");
15
14
  const json_file_1 = require("../utility/json-file");
16
15
  const latest_versions_1 = require("../utility/latest-versions");
17
16
  const paths_1 = require("../utility/paths");
18
17
  const workspace_1 = require("../utility/workspace");
19
18
  const workspace_models_1 = require("../utility/workspace-models");
19
+ const LIBRARY_DEV_DEPENDENCIES = [
20
+ { name: '@angular/compiler-cli', version: latest_versions_1.latestVersions.Angular },
21
+ { name: '@angular/build', version: latest_versions_1.latestVersions.AngularBuild },
22
+ { name: 'ng-packagr', version: latest_versions_1.latestVersions.NgPackagr },
23
+ { name: 'typescript', version: latest_versions_1.latestVersions['typescript'] },
24
+ ];
20
25
  function updateTsConfig(packageName, ...paths) {
21
26
  return (host) => {
22
27
  if (!host.exists('tsconfig.json')) {
@@ -40,37 +45,19 @@ function addTsProjectReference(...paths) {
40
45
  file.modify(jsonPath, Array.isArray(value) ? [...value, ...newReferences] : newReferences);
41
46
  };
42
47
  }
43
- function addDependenciesToPackageJson() {
44
- return (host) => {
45
- [
46
- {
47
- type: dependencies_1.NodeDependencyType.Dev,
48
- name: '@angular/compiler-cli',
49
- version: latest_versions_1.latestVersions.Angular,
50
- },
51
- {
52
- type: dependencies_1.NodeDependencyType.Dev,
53
- name: '@angular/build',
54
- version: latest_versions_1.latestVersions.AngularBuild,
55
- },
56
- {
57
- type: dependencies_1.NodeDependencyType.Dev,
58
- name: 'ng-packagr',
59
- version: latest_versions_1.latestVersions.NgPackagr,
60
- },
61
- {
62
- type: dependencies_1.NodeDependencyType.Default,
63
- name: 'tslib',
64
- version: latest_versions_1.latestVersions['tslib'],
65
- },
66
- {
67
- type: dependencies_1.NodeDependencyType.Dev,
68
- name: 'typescript',
69
- version: latest_versions_1.latestVersions['typescript'],
70
- },
71
- ].forEach((dependency) => (0, dependencies_1.addPackageJsonDependency)(host, dependency));
72
- return host;
73
- };
48
+ function addDependenciesToPackageJson(skipInstall) {
49
+ return (0, schematics_1.chain)([
50
+ ...LIBRARY_DEV_DEPENDENCIES.map((dependency) => (0, dependency_1.addDependency)(dependency.name, dependency.version, {
51
+ type: dependency_1.DependencyType.Dev,
52
+ existing: dependency_1.ExistingBehavior.Skip,
53
+ install: skipInstall ? dependency_1.InstallBehavior.None : dependency_1.InstallBehavior.Auto,
54
+ })),
55
+ (0, dependency_1.addDependency)('tslib', latest_versions_1.latestVersions['tslib'], {
56
+ type: dependency_1.DependencyType.Default,
57
+ existing: dependency_1.ExistingBehavior.Skip,
58
+ install: skipInstall ? dependency_1.InstallBehavior.None : dependency_1.InstallBehavior.Auto,
59
+ }),
60
+ ]);
74
61
  }
75
62
  function addLibToWorkspaceFile(options, projectRoot, projectName, hasZoneDependency) {
76
63
  return (0, workspace_1.updateWorkspace)((workspace) => {
@@ -139,11 +126,11 @@ function default_1(options) {
139
126
  }),
140
127
  (0, schematics_1.move)(libDir),
141
128
  ]);
142
- const hasZoneDependency = (0, dependencies_1.getPackageJsonDependency)(host, 'zone.js') !== null;
129
+ const hasZoneDependency = (0, dependency_1.getDependency)(host, 'zone.js') !== null;
143
130
  return (0, schematics_1.chain)([
144
131
  (0, schematics_1.mergeWith)(templateSource),
145
132
  addLibToWorkspaceFile(options, libDir, packageName, hasZoneDependency),
146
- options.skipPackageJson ? (0, schematics_1.noop)() : addDependenciesToPackageJson(),
133
+ options.skipPackageJson ? (0, schematics_1.noop)() : addDependenciesToPackageJson(!!options.skipInstall),
147
134
  options.skipTsConfig ? (0, schematics_1.noop)() : updateTsConfig(packageName, './' + distRoot),
148
135
  options.skipTsConfig
149
136
  ? (0, schematics_1.noop)()
@@ -156,6 +143,9 @@ function default_1(options) {
156
143
  flat: true,
157
144
  path: sourceDir,
158
145
  project: packageName,
146
+ // Explicitly set the `typeSeparator` this also ensures that the generated files are valid even if the `module` schematic
147
+ // inherits its `typeSeparator` from the workspace.
148
+ typeSeparator: '-',
159
149
  }),
160
150
  (0, schematics_1.schematic)('component', {
161
151
  name: options.name,
@@ -172,11 +162,6 @@ function default_1(options) {
172
162
  // inherits its `type` from the workspace.
173
163
  type: '',
174
164
  }),
175
- (_tree, context) => {
176
- if (!options.skipPackageJson && !options.skipInstall) {
177
- context.addTask(new tasks_1.NodePackageInstallTask());
178
- }
179
- },
180
165
  ]);
181
166
  };
182
167
  }
@@ -0,0 +1,27 @@
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
+ export interface RequireInfo {
9
+ module: string;
10
+ export?: string;
11
+ isCall?: boolean;
12
+ arguments?: KarmaConfigValue[];
13
+ }
14
+ export type KarmaConfigValue = string | boolean | number | KarmaConfigValue[] | {
15
+ [key: string]: KarmaConfigValue;
16
+ } | RequireInfo | undefined;
17
+ export interface KarmaConfigAnalysis {
18
+ settings: Map<string, KarmaConfigValue>;
19
+ hasUnsupportedValues: boolean;
20
+ }
21
+ /**
22
+ * Analyzes the content of a Karma configuration file to extract its settings.
23
+ *
24
+ * @param content The string content of the `karma.conf.js` file.
25
+ * @returns An object containing the configuration settings and a flag indicating if unsupported values were found.
26
+ */
27
+ export declare function analyzeKarmaConfig(content: string): KarmaConfigAnalysis;
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.analyzeKarmaConfig = analyzeKarmaConfig;
14
+ const typescript_1 = __importDefault(require("../../third_party/github.com/Microsoft/TypeScript/lib/typescript"));
15
+ function isRequireInfo(value) {
16
+ return typeof value === 'object' && value !== null && !Array.isArray(value) && 'module' in value;
17
+ }
18
+ function isSupportedPropertyAssignment(prop) {
19
+ return (typescript_1.default.isPropertyAssignment(prop) && (typescript_1.default.isIdentifier(prop.name) || typescript_1.default.isStringLiteral(prop.name)));
20
+ }
21
+ /**
22
+ * Analyzes the content of a Karma configuration file to extract its settings.
23
+ *
24
+ * @param content The string content of the `karma.conf.js` file.
25
+ * @returns An object containing the configuration settings and a flag indicating if unsupported values were found.
26
+ */
27
+ function analyzeKarmaConfig(content) {
28
+ const sourceFile = typescript_1.default.createSourceFile('karma.conf.js', content, typescript_1.default.ScriptTarget.Latest, true);
29
+ const settings = new Map();
30
+ let hasUnsupportedValues = false;
31
+ function visit(node) {
32
+ // The Karma configuration is defined within a `config.set({ ... })` call.
33
+ if (typescript_1.default.isCallExpression(node) &&
34
+ typescript_1.default.isPropertyAccessExpression(node.expression) &&
35
+ node.expression.expression.getText(sourceFile) === 'config' &&
36
+ node.expression.name.text === 'set' &&
37
+ node.arguments.length === 1 &&
38
+ typescript_1.default.isObjectLiteralExpression(node.arguments[0])) {
39
+ // We found `config.set`, now we extract the properties from the object literal.
40
+ for (const prop of node.arguments[0].properties) {
41
+ if (isSupportedPropertyAssignment(prop)) {
42
+ const key = prop.name.text;
43
+ const value = extractValue(prop.initializer);
44
+ settings.set(key, value);
45
+ }
46
+ else {
47
+ hasUnsupportedValues = true;
48
+ }
49
+ }
50
+ }
51
+ else {
52
+ typescript_1.default.forEachChild(node, visit);
53
+ }
54
+ }
55
+ function extractValue(node) {
56
+ switch (node.kind) {
57
+ case typescript_1.default.SyntaxKind.StringLiteral:
58
+ return node.text;
59
+ case typescript_1.default.SyntaxKind.NumericLiteral:
60
+ return Number(node.text);
61
+ case typescript_1.default.SyntaxKind.TrueKeyword:
62
+ return true;
63
+ case typescript_1.default.SyntaxKind.FalseKeyword:
64
+ return false;
65
+ case typescript_1.default.SyntaxKind.Identifier: {
66
+ const identifier = node.text;
67
+ if (identifier === '__dirname' || identifier === '__filename') {
68
+ return identifier;
69
+ }
70
+ break;
71
+ }
72
+ case typescript_1.default.SyntaxKind.CallExpression: {
73
+ const callExpr = node;
74
+ // Handle require('...')
75
+ if (typescript_1.default.isIdentifier(callExpr.expression) &&
76
+ callExpr.expression.text === 'require' &&
77
+ callExpr.arguments.length === 1 &&
78
+ typescript_1.default.isStringLiteral(callExpr.arguments[0])) {
79
+ return { module: callExpr.arguments[0].text };
80
+ }
81
+ // Handle calls on a require, e.g. require('path').join()
82
+ const calleeValue = extractValue(callExpr.expression);
83
+ if (isRequireInfo(calleeValue)) {
84
+ return {
85
+ ...calleeValue,
86
+ isCall: true,
87
+ arguments: callExpr.arguments.map(extractValue),
88
+ };
89
+ }
90
+ break;
91
+ }
92
+ case typescript_1.default.SyntaxKind.PropertyAccessExpression: {
93
+ const propAccessExpr = node;
94
+ // Handle config constants like `config.LOG_INFO`
95
+ if (typescript_1.default.isIdentifier(propAccessExpr.expression) &&
96
+ propAccessExpr.expression.text === 'config') {
97
+ return `config.${propAccessExpr.name.text}`;
98
+ }
99
+ const value = extractValue(propAccessExpr.expression);
100
+ if (isRequireInfo(value)) {
101
+ const currentExport = value.export
102
+ ? `${value.export}.${propAccessExpr.name.text}`
103
+ : propAccessExpr.name.text;
104
+ return { ...value, export: currentExport };
105
+ }
106
+ break;
107
+ }
108
+ case typescript_1.default.SyntaxKind.ArrayLiteralExpression:
109
+ return node.elements.map(extractValue);
110
+ case typescript_1.default.SyntaxKind.ObjectLiteralExpression: {
111
+ const obj = {};
112
+ for (const prop of node.properties) {
113
+ if (isSupportedPropertyAssignment(prop)) {
114
+ // Recursively extract values for nested objects.
115
+ obj[prop.name.text] = extractValue(prop.initializer);
116
+ }
117
+ else {
118
+ hasUnsupportedValues = true;
119
+ }
120
+ }
121
+ return obj;
122
+ }
123
+ }
124
+ // For complex expressions (like variables) that we don't need to resolve,
125
+ // we mark the analysis as potentially incomplete.
126
+ hasUnsupportedValues = true;
127
+ return undefined;
128
+ }
129
+ visit(sourceFile);
130
+ return { settings, hasUnsupportedValues };
131
+ }
@@ -0,0 +1,63 @@
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
+ import { KarmaConfigAnalysis, KarmaConfigValue } from './karma-config-analyzer';
9
+ /**
10
+ * Represents the difference between two Karma configurations.
11
+ */
12
+ export interface KarmaConfigDiff {
13
+ /** A map of settings that were added in the project's configuration. */
14
+ added: Map<string, KarmaConfigValue>;
15
+ /** A map of settings that were removed from the project's configuration. */
16
+ removed: Map<string, KarmaConfigValue>;
17
+ /** A map of settings that were modified between the two configurations. */
18
+ modified: Map<string, {
19
+ projectValue: KarmaConfigValue;
20
+ defaultValue: KarmaConfigValue;
21
+ }>;
22
+ /** A boolean indicating if the comparison is reliable (i.e., no unsupported values were found). */
23
+ isReliable: boolean;
24
+ }
25
+ /**
26
+ * Generates the default Karma configuration file content as a string.
27
+ * @param relativePathToWorkspaceRoot The relative path from the Karma config file to the workspace root.
28
+ * @param projectName The name of the project.
29
+ * @param needDevkitPlugin A boolean indicating if the devkit plugin is needed.
30
+ * @returns The content of the default `karma.conf.js` file.
31
+ */
32
+ export declare function generateDefaultKarmaConfig(relativePathToWorkspaceRoot: string, projectName: string, needDevkitPlugin: boolean): Promise<string>;
33
+ /**
34
+ * Compares two Karma configuration analyses and returns the difference.
35
+ * @param projectAnalysis The analysis of the project's configuration.
36
+ * @param defaultAnalysis The analysis of the default configuration to compare against.
37
+ * @returns A diff object representing the changes between the two configurations.
38
+ */
39
+ export declare function compareKarmaConfigs(projectAnalysis: KarmaConfigAnalysis, defaultAnalysis: KarmaConfigAnalysis): KarmaConfigDiff;
40
+ /**
41
+ * Checks if there are any differences in the provided Karma configuration diff.
42
+ * @param diff The Karma configuration diff object to check.
43
+ * @returns True if there are any differences; false otherwise.
44
+ */
45
+ export declare function hasDifferences(diff: KarmaConfigDiff): boolean;
46
+ /**
47
+ * Compares a project's Karma configuration with the default configuration.
48
+ * @param projectConfigContent The content of the project's `karma.conf.js` file.
49
+ * @param projectRoot The root directory of the project.
50
+ * @param needDevkitPlugin A boolean indicating if the devkit plugin is needed for the default config.
51
+ * @param karmaConfigPath The path to the Karma configuration file, used to resolve relative paths.
52
+ * @returns A diff object representing the changes.
53
+ */
54
+ export declare function compareKarmaConfigToDefault(projectConfigContent: string, projectName: string, karmaConfigPath: string, needDevkitPlugin: boolean): Promise<KarmaConfigDiff>;
55
+ /**
56
+ * Compares a project's Karma configuration with the default configuration.
57
+ * @param projectAnalysis The analysis of the project's configuration.
58
+ * @param projectRoot The root directory of the project.
59
+ * @param needDevkitPlugin A boolean indicating if the devkit plugin is needed for the default config.
60
+ * @param karmaConfigPath The path to the Karma configuration file, used to resolve relative paths.
61
+ * @returns A diff object representing the changes.
62
+ */
63
+ export declare function compareKarmaConfigToDefault(projectAnalysis: KarmaConfigAnalysis, projectName: string, karmaConfigPath: string, needDevkitPlugin: boolean): Promise<KarmaConfigDiff>;