@schematics/angular 20.2.0-next.2 → 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.
- package/ai-config/files/__rulesName__.template +49 -0
- package/ai-config/index.d.ts +10 -0
- package/ai-config/index.js +55 -0
- package/ai-config/schema.d.ts +21 -0
- package/ai-config/schema.js +15 -0
- package/ai-config/schema.json +39 -0
- package/application/index.js +26 -39
- package/collection.json +5 -0
- package/library/index.js +25 -40
- package/migrations/karma/karma-config-analyzer.d.ts +27 -0
- package/migrations/karma/karma-config-analyzer.js +131 -0
- package/migrations/karma/karma-config-comparer.d.ts +63 -0
- package/migrations/karma/karma-config-comparer.js +89 -0
- package/migrations/karma/migration.d.ts +9 -0
- package/migrations/karma/migration.js +62 -0
- package/migrations/migration-collection.json +5 -0
- package/migrations/replace-provide-server-rendering-import/migration.js +13 -48
- package/migrations/use-application-builder/migration.js +5 -142
- package/migrations/use-application-builder/stylesheet-updates.d.ts +36 -0
- package/migrations/use-application-builder/stylesheet-updates.js +219 -0
- package/ng-new/index.js +3 -0
- package/ng-new/schema.d.ts +14 -0
- package/ng-new/schema.js +11 -1
- package/ng-new/schema.json +10 -3
- package/package.json +4 -4
- package/service-worker/index.js +2 -2
- package/third_party/github.com/Microsoft/TypeScript/lib/typescript.js +1 -1
- package/utility/dependency.d.ts +47 -1
- package/utility/dependency.js +55 -0
- package/utility/latest-versions/package.json +1 -1
- package/utility/latest-versions.js +3 -3
- package/workspace/files/package.json.template +2 -0
|
@@ -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
|
+
}
|
package/application/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
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 (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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,
|
|
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>;
|