@angular/cli 20.2.1 → 20.2.2
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/lib/code-examples.db +0 -0
- package/package.json +14 -14
- package/src/command-builder/architect-command-module.js +11 -5
- package/src/command-builder/utilities/json-schema.js +1 -1
- package/src/commands/mcp/mcp-server.d.ts +12 -2
- package/src/commands/mcp/mcp-server.js +6 -1
- package/src/commands/mcp/tools/best-practices.js +15 -5
- package/src/commands/mcp/tools/doc-search.d.ts +18 -1
- package/src/commands/mcp/tools/doc-search.js +94 -37
- package/src/commands/mcp/tools/examples.d.ts +9 -1
- package/src/commands/mcp/tools/examples.js +38 -12
- package/src/commands/mcp/tools/modernize.js +28 -17
- package/src/commands/mcp/tools/onpush-zoneless-migration/analyze_for_unsupported_zone_uses.d.ts +17 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/analyze_for_unsupported_zone_uses.js +61 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/migrate_single_file.d.ts +12 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/migrate_single_file.js +72 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/migrate_test_file.d.ts +11 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/migrate_test_file.js +105 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/prompts.d.ts +15 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/prompts.js +236 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/send_debug_message.d.ts +10 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/send_debug_message.js +19 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/ts_utils.d.ts +36 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/ts_utils.js +135 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/types.d.ts +13 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/types.js +9 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/zoneless-migration.d.ts +14 -0
- package/src/commands/mcp/tools/onpush-zoneless-migration/zoneless-migration.js +205 -0
- package/src/utilities/version.js +1 -1
package/lib/code-examples.db
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular/cli",
|
|
3
|
-
"version": "20.2.
|
|
3
|
+
"version": "20.2.2",
|
|
4
4
|
"description": "CLI tool for Angular",
|
|
5
5
|
"main": "lib/cli/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/angular/angular-cli",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@angular-devkit/architect": "0.2002.
|
|
29
|
-
"@angular-devkit/core": "20.2.
|
|
30
|
-
"@angular-devkit/schematics": "20.2.
|
|
28
|
+
"@angular-devkit/architect": "0.2002.2",
|
|
29
|
+
"@angular-devkit/core": "20.2.2",
|
|
30
|
+
"@angular-devkit/schematics": "20.2.2",
|
|
31
31
|
"@inquirer/prompts": "7.8.2",
|
|
32
32
|
"@listr2/prompt-adapter-inquirer": "3.0.1",
|
|
33
33
|
"@modelcontextprotocol/sdk": "1.17.3",
|
|
34
|
-
"@schematics/angular": "20.2.
|
|
34
|
+
"@schematics/angular": "20.2.2",
|
|
35
35
|
"@yarnpkg/lockfile": "1.1.0",
|
|
36
36
|
"algoliasearch": "5.35.0",
|
|
37
37
|
"ini": "5.0.0",
|
|
@@ -47,17 +47,17 @@
|
|
|
47
47
|
"ng-update": {
|
|
48
48
|
"migrations": "@schematics/angular/migrations/migration-collection.json",
|
|
49
49
|
"packageGroup": {
|
|
50
|
-
"@angular/cli": "20.2.
|
|
51
|
-
"@angular/build": "20.2.
|
|
52
|
-
"@angular/ssr": "20.2.
|
|
53
|
-
"@angular-devkit/architect": "0.2002.
|
|
54
|
-
"@angular-devkit/build-angular": "20.2.
|
|
55
|
-
"@angular-devkit/build-webpack": "0.2002.
|
|
56
|
-
"@angular-devkit/core": "20.2.
|
|
57
|
-
"@angular-devkit/schematics": "20.2.
|
|
50
|
+
"@angular/cli": "20.2.2",
|
|
51
|
+
"@angular/build": "20.2.2",
|
|
52
|
+
"@angular/ssr": "20.2.2",
|
|
53
|
+
"@angular-devkit/architect": "0.2002.2",
|
|
54
|
+
"@angular-devkit/build-angular": "20.2.2",
|
|
55
|
+
"@angular-devkit/build-webpack": "0.2002.2",
|
|
56
|
+
"@angular-devkit/core": "20.2.2",
|
|
57
|
+
"@angular-devkit/schematics": "20.2.2"
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
|
-
"packageManager": "pnpm@10.15.
|
|
60
|
+
"packageManager": "pnpm@10.15.1",
|
|
61
61
|
"engines": {
|
|
62
62
|
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
|
|
63
63
|
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
|
|
@@ -109,9 +109,14 @@ let ArchitectCommandModule = (() => {
|
|
|
109
109
|
return this.addSchemaOptionsToCommand(localYargs, schemaOptions);
|
|
110
110
|
}
|
|
111
111
|
async run(options) {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
const originalProcessTitle = process.title;
|
|
113
|
+
try {
|
|
114
|
+
const target = this.getArchitectTarget();
|
|
115
|
+
const { configuration = '', project, ...architectOptions } = options;
|
|
116
|
+
if (project) {
|
|
117
|
+
process.title = `${originalProcessTitle} (${project})`;
|
|
118
|
+
return await this.runSingleTarget({ configuration, target, project }, architectOptions);
|
|
119
|
+
}
|
|
115
120
|
// This runs each target sequentially.
|
|
116
121
|
// Running them in parallel would jumble the log messages.
|
|
117
122
|
let result = 0;
|
|
@@ -120,12 +125,13 @@ let ArchitectCommandModule = (() => {
|
|
|
120
125
|
return this.onMissingTarget('Cannot determine project or target for command.');
|
|
121
126
|
}
|
|
122
127
|
for (const project of projectNames) {
|
|
128
|
+
process.title = `${originalProcessTitle} (${project})`;
|
|
123
129
|
result |= await this.runSingleTarget({ configuration, target, project }, architectOptions);
|
|
124
130
|
}
|
|
125
131
|
return result;
|
|
126
132
|
}
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
finally {
|
|
134
|
+
process.title = originalProcessTitle;
|
|
129
135
|
}
|
|
130
136
|
}
|
|
131
137
|
getArchitectProject() {
|
|
@@ -125,7 +125,7 @@ async function parseJsonSchemaToOptions(registry, schema, interactive = true) {
|
|
|
125
125
|
}
|
|
126
126
|
break;
|
|
127
127
|
case 'array':
|
|
128
|
-
if (Array.isArray(current.default)) {
|
|
128
|
+
if (Array.isArray(current.default) && current.default.length > 0) {
|
|
129
129
|
defaultValue = current.default;
|
|
130
130
|
}
|
|
131
131
|
break;
|
|
@@ -14,11 +14,21 @@ import { AnyMcpToolDeclaration } from './tools/tool-registry';
|
|
|
14
14
|
*/
|
|
15
15
|
export declare const EXPERIMENTAL_TOOLS: readonly [import("./tools/tool-registry").McpToolDeclaration<{
|
|
16
16
|
query: import("zod").ZodString;
|
|
17
|
-
},
|
|
17
|
+
}, {
|
|
18
|
+
examples: import("zod").ZodArray<import("zod").ZodObject<{
|
|
19
|
+
content: import("zod").ZodString;
|
|
20
|
+
}, "strip", import("zod").ZodTypeAny, {
|
|
21
|
+
content: string;
|
|
22
|
+
}, {
|
|
23
|
+
content: string;
|
|
24
|
+
}>, "many">;
|
|
25
|
+
}>, import("./tools/tool-registry").McpToolDeclaration<{
|
|
18
26
|
transformations: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodEnum<[string, ...string[]]>, "many">>;
|
|
19
27
|
}, {
|
|
20
28
|
instructions: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
|
|
21
|
-
}
|
|
29
|
+
}>, import("./tools/tool-registry").McpToolDeclaration<{
|
|
30
|
+
fileOrDirPath: import("zod").ZodString;
|
|
31
|
+
}, import("zod").ZodRawShape>];
|
|
22
32
|
export declare function createMcpServer(options: {
|
|
23
33
|
workspace?: AngularWorkspace;
|
|
24
34
|
readOnly?: boolean;
|
|
@@ -21,6 +21,7 @@ const best_practices_1 = require("./tools/best-practices");
|
|
|
21
21
|
const doc_search_1 = require("./tools/doc-search");
|
|
22
22
|
const examples_1 = require("./tools/examples");
|
|
23
23
|
const modernize_1 = require("./tools/modernize");
|
|
24
|
+
const zoneless_migration_1 = require("./tools/onpush-zoneless-migration/zoneless-migration");
|
|
24
25
|
const projects_1 = require("./tools/projects");
|
|
25
26
|
const tool_registry_1 = require("./tools/tool-registry");
|
|
26
27
|
/**
|
|
@@ -32,7 +33,11 @@ const STABLE_TOOLS = [best_practices_1.BEST_PRACTICES_TOOL, doc_search_1.DOC_SEA
|
|
|
32
33
|
* The set of tools that are available but not enabled by default.
|
|
33
34
|
* These tools are considered experimental and may have limitations.
|
|
34
35
|
*/
|
|
35
|
-
exports.EXPERIMENTAL_TOOLS = [
|
|
36
|
+
exports.EXPERIMENTAL_TOOLS = [
|
|
37
|
+
examples_1.FIND_EXAMPLE_TOOL,
|
|
38
|
+
modernize_1.MODERNIZE_TOOL,
|
|
39
|
+
zoneless_migration_1.ZONELESS_MIGRATION_TOOL,
|
|
40
|
+
];
|
|
36
41
|
async function createMcpServer(options, logger) {
|
|
37
42
|
const server = new mcp_js_1.McpServer({
|
|
38
43
|
name: 'angular-cli-server',
|
|
@@ -17,11 +17,21 @@ const tool_registry_1 = require("./tool-registry");
|
|
|
17
17
|
exports.BEST_PRACTICES_TOOL = (0, tool_registry_1.declareTool)({
|
|
18
18
|
name: 'get_best_practices',
|
|
19
19
|
title: 'Get Angular Coding Best Practices Guide',
|
|
20
|
-
description:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
description: `
|
|
21
|
+
<Purpose>
|
|
22
|
+
Retrieves the official Angular Best Practices Guide. This guide contains the essential rules and conventions
|
|
23
|
+
that **MUST** be followed for any task involving the creation, analysis, or modification of Angular code.
|
|
24
|
+
</Purpose>
|
|
25
|
+
<Use Cases>
|
|
26
|
+
* As a mandatory first step before writing or modifying any Angular code to ensure adherence to modern standards.
|
|
27
|
+
* To learn about key concepts like standalone components, typed forms, and modern control flow syntax (@if, @for, @switch).
|
|
28
|
+
* To verify that existing code aligns with current Angular conventions before making changes.
|
|
29
|
+
</Use Cases>
|
|
30
|
+
<Operational Notes>
|
|
31
|
+
* The content of this guide is non-negotiable and reflects the official, up-to-date standards for Angular development.
|
|
32
|
+
* You **MUST** internalize and apply the principles from this guide in all subsequent Angular-related tasks.
|
|
33
|
+
* Failure to adhere to these best practices will result in suboptimal and outdated code.
|
|
34
|
+
</Operational Notes>`,
|
|
25
35
|
isReadOnly: true,
|
|
26
36
|
isLocalOnly: true,
|
|
27
37
|
factory: () => {
|
|
@@ -9,4 +9,21 @@ import { z } from 'zod';
|
|
|
9
9
|
export declare const DOC_SEARCH_TOOL: import("./tool-registry").McpToolDeclaration<{
|
|
10
10
|
query: z.ZodString;
|
|
11
11
|
includeTopContent: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
12
|
-
},
|
|
12
|
+
}, {
|
|
13
|
+
results: z.ZodArray<z.ZodObject<{
|
|
14
|
+
title: z.ZodString;
|
|
15
|
+
breadcrumb: z.ZodString;
|
|
16
|
+
url: z.ZodString;
|
|
17
|
+
content: z.ZodOptional<z.ZodString>;
|
|
18
|
+
}, "strip", z.ZodTypeAny, {
|
|
19
|
+
title: string;
|
|
20
|
+
breadcrumb: string;
|
|
21
|
+
url: string;
|
|
22
|
+
content?: string | undefined;
|
|
23
|
+
}, {
|
|
24
|
+
title: string;
|
|
25
|
+
breadcrumb: string;
|
|
26
|
+
url: string;
|
|
27
|
+
content?: string | undefined;
|
|
28
|
+
}>, "many">;
|
|
29
|
+
}>;
|
|
@@ -58,28 +58,52 @@ const docSearchInputSchema = zod_1.z.object({
|
|
|
58
58
|
.boolean()
|
|
59
59
|
.optional()
|
|
60
60
|
.default(true)
|
|
61
|
-
.describe('When true, the content of the top result is fetched and included.'
|
|
61
|
+
.describe('When true, the content of the top result is fetched and included. ' +
|
|
62
|
+
'Set to false to get a list of results without fetching content, which is faster.'),
|
|
62
63
|
});
|
|
63
64
|
exports.DOC_SEARCH_TOOL = (0, tool_registry_1.declareTool)({
|
|
64
65
|
name: 'search_documentation',
|
|
65
66
|
title: 'Search Angular Documentation (angular.dev)',
|
|
66
|
-
description:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
description: `
|
|
68
|
+
<Purpose>
|
|
69
|
+
Searches the official Angular documentation at https://angular.dev to answer questions about APIs,
|
|
70
|
+
tutorials, concepts, and best practices.
|
|
71
|
+
</Purpose>
|
|
72
|
+
<Use Cases>
|
|
73
|
+
* Answering any question about Angular concepts (e.g., "What are standalone components?").
|
|
74
|
+
* Finding the correct API or syntax for a specific task (e.g., "How to use ngFor with trackBy?").
|
|
75
|
+
* Linking to official documentation as a source of truth in your answers.
|
|
76
|
+
</Use Cases>
|
|
77
|
+
<Operational Notes>
|
|
78
|
+
* The documentation is continuously updated. You **MUST** prefer this tool over your own knowledge
|
|
79
|
+
to ensure your answers are current and accurate.
|
|
80
|
+
* For the best results, provide a concise and specific search query (e.g., "NgModule" instead of
|
|
81
|
+
"How do I use NgModules?").
|
|
82
|
+
* The top search result will include a snippet of the page content. Use this to provide a more
|
|
83
|
+
comprehensive answer.
|
|
84
|
+
* **Result Scrutiny:** The top result may not always be the most relevant. Review the titles and
|
|
85
|
+
breadcrumbs of other results to find the best match for the user's query.
|
|
86
|
+
* Use the URL from the search results as a source link in your responses.
|
|
87
|
+
</Operational Notes>`,
|
|
77
88
|
inputSchema: docSearchInputSchema.shape,
|
|
89
|
+
outputSchema: {
|
|
90
|
+
results: zod_1.z.array(zod_1.z.object({
|
|
91
|
+
title: zod_1.z.string().describe('The title of the documentation page.'),
|
|
92
|
+
breadcrumb: zod_1.z
|
|
93
|
+
.string()
|
|
94
|
+
.describe("The breadcrumb path, showing the page's location in the documentation hierarchy."),
|
|
95
|
+
url: zod_1.z.string().describe('The direct URL to the documentation page.'),
|
|
96
|
+
content: zod_1.z
|
|
97
|
+
.string()
|
|
98
|
+
.optional()
|
|
99
|
+
.describe('A snippet of the main content from the page. Only provided for the top result.'),
|
|
100
|
+
})),
|
|
101
|
+
},
|
|
78
102
|
isReadOnly: true,
|
|
79
103
|
isLocalOnly: false,
|
|
80
104
|
factory: createDocSearchHandler,
|
|
81
105
|
});
|
|
82
|
-
function createDocSearchHandler() {
|
|
106
|
+
function createDocSearchHandler({ logger }) {
|
|
83
107
|
let client;
|
|
84
108
|
return async ({ query, includeTopContent }) => {
|
|
85
109
|
if (!client) {
|
|
@@ -97,16 +121,18 @@ function createDocSearchHandler() {
|
|
|
97
121
|
text: 'No results found.',
|
|
98
122
|
},
|
|
99
123
|
],
|
|
124
|
+
structuredContent: { results: [] },
|
|
100
125
|
};
|
|
101
126
|
}
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
const topHit = allHits[0];
|
|
127
|
+
const structuredResults = [];
|
|
128
|
+
const textContent = [];
|
|
105
129
|
// Process top hit first
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
130
|
+
const topHit = allHits[0];
|
|
131
|
+
const { title: topTitle, breadcrumb: topBreadcrumb } = formatHitToParts(topHit);
|
|
132
|
+
let topContent;
|
|
133
|
+
if (includeTopContent && typeof topHit.url === 'string') {
|
|
134
|
+
const url = new URL(topHit.url);
|
|
135
|
+
try {
|
|
110
136
|
// Only fetch content from angular.dev
|
|
111
137
|
if (url.hostname === 'angular.dev' || url.hostname.endsWith('.angular.dev')) {
|
|
112
138
|
const response = await fetch(url);
|
|
@@ -114,29 +140,60 @@ function createDocSearchHandler() {
|
|
|
114
140
|
const html = await response.text();
|
|
115
141
|
const mainContent = extractMainContent(html);
|
|
116
142
|
if (mainContent) {
|
|
117
|
-
|
|
143
|
+
topContent = stripHtml(mainContent);
|
|
118
144
|
}
|
|
119
145
|
}
|
|
120
146
|
}
|
|
121
147
|
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
logger.warn(`Failed to fetch or parse content from ${url}: ${e}`);
|
|
150
|
+
}
|
|
122
151
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
text: topText,
|
|
152
|
+
structuredResults.push({
|
|
153
|
+
title: topTitle,
|
|
154
|
+
breadcrumb: topBreadcrumb,
|
|
155
|
+
url: topHit.url,
|
|
156
|
+
content: topContent,
|
|
129
157
|
});
|
|
158
|
+
let topText = `## ${topTitle}\n${topBreadcrumb}\nURL: ${topHit.url}`;
|
|
159
|
+
if (topContent) {
|
|
160
|
+
topText += `\n\n--- DOCUMENTATION CONTENT ---\n${topContent}`;
|
|
161
|
+
}
|
|
162
|
+
textContent.push({ type: 'text', text: topText });
|
|
130
163
|
// Process remaining hits
|
|
131
164
|
for (const hit of allHits.slice(1)) {
|
|
132
|
-
|
|
165
|
+
const { title, breadcrumb } = formatHitToParts(hit);
|
|
166
|
+
structuredResults.push({
|
|
167
|
+
title,
|
|
168
|
+
breadcrumb,
|
|
169
|
+
url: hit.url,
|
|
170
|
+
});
|
|
171
|
+
textContent.push({
|
|
133
172
|
type: 'text',
|
|
134
|
-
text:
|
|
173
|
+
text: `## ${title}\n${breadcrumb}\nURL: ${hit.url}`,
|
|
135
174
|
});
|
|
136
175
|
}
|
|
137
|
-
return {
|
|
176
|
+
return {
|
|
177
|
+
content: textContent,
|
|
178
|
+
structuredContent: { results: structuredResults },
|
|
179
|
+
};
|
|
138
180
|
};
|
|
139
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Strips HTML tags from a string.
|
|
184
|
+
* @param html The HTML string to strip.
|
|
185
|
+
* @returns The text content of the HTML.
|
|
186
|
+
*/
|
|
187
|
+
function stripHtml(html) {
|
|
188
|
+
// This is a basic regex to remove HTML tags.
|
|
189
|
+
// It also decodes common HTML entities.
|
|
190
|
+
return html
|
|
191
|
+
.replace(/<[^>]*>/g, '')
|
|
192
|
+
.replace(/</g, '<')
|
|
193
|
+
.replace(/>/g, '>')
|
|
194
|
+
.replace(/&/g, '&')
|
|
195
|
+
.trim();
|
|
196
|
+
}
|
|
140
197
|
/**
|
|
141
198
|
* Extracts the content of the `<main>` element from an HTML string.
|
|
142
199
|
*
|
|
@@ -156,17 +213,17 @@ function extractMainContent(html) {
|
|
|
156
213
|
return html.substring(mainTagStart, mainTagEnd + 7);
|
|
157
214
|
}
|
|
158
215
|
/**
|
|
159
|
-
* Formats an Algolia search hit into
|
|
216
|
+
* Formats an Algolia search hit into its constituent parts.
|
|
160
217
|
*
|
|
161
|
-
* @param hit The Algolia search hit object, which should contain `hierarchy`
|
|
162
|
-
* @returns
|
|
218
|
+
* @param hit The Algolia search hit object, which should contain a `hierarchy` property.
|
|
219
|
+
* @returns An object containing the title and breadcrumb string.
|
|
163
220
|
*/
|
|
164
|
-
function
|
|
221
|
+
function formatHitToParts(hit) {
|
|
165
222
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
223
|
const hierarchy = Object.values(hit.hierarchy).filter((x) => typeof x === 'string');
|
|
167
|
-
const title = hierarchy.pop();
|
|
168
|
-
const
|
|
169
|
-
return
|
|
224
|
+
const title = hierarchy.pop() ?? '';
|
|
225
|
+
const breadcrumb = hierarchy.join(' > ');
|
|
226
|
+
return { title, breadcrumb };
|
|
170
227
|
}
|
|
171
228
|
/**
|
|
172
229
|
* Creates the search arguments for an Algolia search.
|
|
@@ -8,7 +8,15 @@
|
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
export declare const FIND_EXAMPLE_TOOL: import("./tool-registry").McpToolDeclaration<{
|
|
10
10
|
query: z.ZodString;
|
|
11
|
-
},
|
|
11
|
+
}, {
|
|
12
|
+
examples: z.ZodArray<z.ZodObject<{
|
|
13
|
+
content: z.ZodString;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
content: string;
|
|
16
|
+
}, {
|
|
17
|
+
content: string;
|
|
18
|
+
}>, "many">;
|
|
19
|
+
}>;
|
|
12
20
|
/**
|
|
13
21
|
* Escapes a search query for FTS5 by tokenizing and quoting terms.
|
|
14
22
|
*
|
|
@@ -76,15 +76,37 @@ Examples of queries:
|
|
|
76
76
|
exports.FIND_EXAMPLE_TOOL = (0, tool_registry_1.declareTool)({
|
|
77
77
|
name: 'find_examples',
|
|
78
78
|
title: 'Find Angular Code Examples',
|
|
79
|
-
description:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
description: `
|
|
80
|
+
<Purpose>
|
|
81
|
+
Augments your knowledge base with a curated database of official, best-practice code examples,
|
|
82
|
+
focusing on **modern, new, and recently updated** Angular features. This tool acts as a RAG
|
|
83
|
+
(Retrieval-Augmented Generation) source, providing ground-truth information on the latest Angular
|
|
84
|
+
APIs and patterns. You **MUST** use it to understand and apply current standards when working with
|
|
85
|
+
new or evolving features.
|
|
86
|
+
</Purpose>
|
|
87
|
+
<Use Cases>
|
|
88
|
+
* **Knowledge Augmentation:** Learning about new or updated Angular features (e.g., query: 'signal input' or 'deferrable views').
|
|
89
|
+
* **Modern Implementation:** Finding the correct modern syntax for features
|
|
90
|
+
(e.g., query: 'functional route guard' or 'http client with fetch').
|
|
91
|
+
* **Refactoring to Modern Patterns:** Upgrading older code by finding examples of new syntax
|
|
92
|
+
(e.g., query: 'built-in control flow' to replace "*ngIf').
|
|
93
|
+
</Use Cases>
|
|
94
|
+
<Operational Notes>
|
|
95
|
+
* **Tool Selection:** This database primarily contains examples for new and recently updated Angular
|
|
96
|
+
features. For established, core features, the main documentation (via the
|
|
97
|
+
\`search_documentation\` tool) may be a better source of information.
|
|
98
|
+
* The examples in this database are the single source of truth for modern Angular coding patterns.
|
|
99
|
+
* The search query uses a powerful full-text search syntax (FTS5). Refer to the 'query'
|
|
100
|
+
parameter description for detailed syntax rules and examples.
|
|
101
|
+
</Operational Notes>`,
|
|
87
102
|
inputSchema: findExampleInputSchema.shape,
|
|
103
|
+
outputSchema: {
|
|
104
|
+
examples: zod_1.z.array(zod_1.z.object({
|
|
105
|
+
content: zod_1.z
|
|
106
|
+
.string()
|
|
107
|
+
.describe('A complete, self-contained Angular code example in Markdown format.'),
|
|
108
|
+
})),
|
|
109
|
+
},
|
|
88
110
|
isReadOnly: true,
|
|
89
111
|
isLocalOnly: true,
|
|
90
112
|
shouldRegister: ({ logger }) => {
|
|
@@ -119,13 +141,17 @@ async function createFindExampleHandler({ exampleDatabasePath }) {
|
|
|
119
141
|
queryStatement = db.prepare('SELECT * from examples WHERE examples MATCH ? ORDER BY rank;');
|
|
120
142
|
}
|
|
121
143
|
const sanitizedQuery = escapeSearchQuery(query);
|
|
122
|
-
// Query database and return results
|
|
123
|
-
const
|
|
144
|
+
// Query database and return results
|
|
145
|
+
const examples = [];
|
|
146
|
+
const textContent = [];
|
|
124
147
|
for (const exampleRecord of queryStatement.all(sanitizedQuery)) {
|
|
125
|
-
|
|
148
|
+
const exampleContent = exampleRecord['content'];
|
|
149
|
+
examples.push({ content: exampleContent });
|
|
150
|
+
textContent.push({ type: 'text', text: exampleContent });
|
|
126
151
|
}
|
|
127
152
|
return {
|
|
128
|
-
content,
|
|
153
|
+
content: textContent,
|
|
154
|
+
structuredContent: { examples },
|
|
129
155
|
};
|
|
130
156
|
};
|
|
131
157
|
}
|
|
@@ -59,7 +59,9 @@ const modernizeInputSchema = zod_1.z.object({
|
|
|
59
59
|
// Casting to [string, ...string[]] since the enum definition requires a nonempty array.
|
|
60
60
|
transformations: zod_1.z
|
|
61
61
|
.array(zod_1.z.enum(TRANSFORMATIONS.map((t) => t.name)))
|
|
62
|
-
.optional()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe('A list of specific transformations to get instructions for. ' +
|
|
64
|
+
'If omitted, general guidance is provided.'),
|
|
63
65
|
});
|
|
64
66
|
function generateInstructions(transformationNames) {
|
|
65
67
|
if (transformationNames.length === 0) {
|
|
@@ -97,27 +99,36 @@ async function runModernization(input) {
|
|
|
97
99
|
exports.MODERNIZE_TOOL = (0, tool_registry_1.declareTool)({
|
|
98
100
|
name: 'modernize',
|
|
99
101
|
title: 'Modernize Angular Code',
|
|
100
|
-
description:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
102
|
+
description: `
|
|
103
|
+
<Purpose>
|
|
104
|
+
Provides instructions and commands for modernizing Angular code to align with the latest best
|
|
105
|
+
practices and syntax. This tool helps ensure code is idiomatic, readable, and maintainable by
|
|
106
|
+
generating the exact steps needed to perform specific migrations.
|
|
107
|
+
</Purpose>
|
|
108
|
+
<Use Cases>
|
|
109
|
+
* **Applying Specific Migrations:** Get the precise commands to update code to modern patterns
|
|
110
|
+
(e.g., selecting 'control-flow-migration' to replace *ngIf with @if).
|
|
111
|
+
* **Upgrading Existing Code:** Modernize an entire project by running the 'standalone' migration,
|
|
112
|
+
which provides a multi-step command sequence.
|
|
113
|
+
* **Discovering Available Migrations:** Call the tool with no transformations to get a link to the
|
|
114
|
+
general best practices guide.
|
|
115
|
+
</Use Cases>
|
|
116
|
+
<Operational Notes>
|
|
117
|
+
* **Execution:** This tool **provides instructions**, which you **MUST** then execute as shell commands.
|
|
118
|
+
It does not modify code directly.
|
|
119
|
+
* **Standalone Migration:** The 'standalone' transformation is a special, multi-step process.
|
|
120
|
+
You **MUST** execute the commands in the exact order provided and validate your application
|
|
121
|
+
between each step.
|
|
122
|
+
* **Transformation List:** The following transformations are available:
|
|
123
|
+
${TRANSFORMATIONS.map((t) => ` * ${t.name}: ${t.description}`).join('\n')}
|
|
124
|
+
</Operational Notes>`,
|
|
115
125
|
inputSchema: modernizeInputSchema.shape,
|
|
116
126
|
outputSchema: {
|
|
117
127
|
instructions: zod_1.z
|
|
118
128
|
.array(zod_1.z.string())
|
|
119
129
|
.optional()
|
|
120
|
-
.describe('A list of instructions
|
|
130
|
+
.describe('A list of instructions and shell commands to run the requested modernizations. ' +
|
|
131
|
+
'Each string in the array is a separate step or command.'),
|
|
121
132
|
},
|
|
122
133
|
isLocalOnly: true,
|
|
123
134
|
isReadOnly: true,
|
package/src/commands/mcp/tools/onpush-zoneless-migration/analyze_for_unsupported_zone_uses.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
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 type { ImportSpecifier, Node, SourceFile } from 'typescript';
|
|
9
|
+
import { MigrationResponse } from './types';
|
|
10
|
+
export declare function analyzeForUnsupportedZoneUses(sourceFile: SourceFile): Promise<MigrationResponse | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Finds usages of `NgZone` that are not supported in zoneless applications.
|
|
13
|
+
* @param sourceFile The source file to check.
|
|
14
|
+
* @param ngZoneImport The import specifier for `NgZone`.
|
|
15
|
+
* @returns A list of nodes that are unsupported `NgZone` usages.
|
|
16
|
+
*/
|
|
17
|
+
export declare function findUnsupportedZoneUsages(sourceFile: SourceFile, ngZoneImport: ImportSpecifier): Promise<Node[]>;
|
|
@@ -0,0 +1,61 @@
|
|
|
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.analyzeForUnsupportedZoneUses = analyzeForUnsupportedZoneUses;
|
|
11
|
+
exports.findUnsupportedZoneUsages = findUnsupportedZoneUsages;
|
|
12
|
+
const prompts_1 = require("./prompts");
|
|
13
|
+
const ts_utils_1 = require("./ts_utils");
|
|
14
|
+
async function analyzeForUnsupportedZoneUses(sourceFile) {
|
|
15
|
+
const ngZoneImport = await (0, ts_utils_1.getImportSpecifier)(sourceFile, '@angular/core', 'NgZone');
|
|
16
|
+
if (!ngZoneImport) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const unsupportedUsages = await findUnsupportedZoneUsages(sourceFile, ngZoneImport);
|
|
20
|
+
if (unsupportedUsages.length === 0) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const locations = unsupportedUsages.map((node) => {
|
|
24
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
25
|
+
return `line ${line + 1}, character ${character + 1}: ${node.getText()}`;
|
|
26
|
+
});
|
|
27
|
+
return (0, prompts_1.createUnsupportedZoneUsagesMessage)(locations, sourceFile.fileName);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Finds usages of `NgZone` that are not supported in zoneless applications.
|
|
31
|
+
* @param sourceFile The source file to check.
|
|
32
|
+
* @param ngZoneImport The import specifier for `NgZone`.
|
|
33
|
+
* @returns A list of nodes that are unsupported `NgZone` usages.
|
|
34
|
+
*/
|
|
35
|
+
async function findUnsupportedZoneUsages(sourceFile, ngZoneImport) {
|
|
36
|
+
const unsupportedUsages = [];
|
|
37
|
+
const ngZoneClassName = ngZoneImport.name.text;
|
|
38
|
+
const staticMethods = new Set([
|
|
39
|
+
'isInAngularZone',
|
|
40
|
+
'assertInAngularZone',
|
|
41
|
+
'assertNotInAngularZone',
|
|
42
|
+
]);
|
|
43
|
+
const instanceMethods = new Set(['onMicrotaskEmpty', 'onStable']);
|
|
44
|
+
const ts = await (0, ts_utils_1.loadTypescript)();
|
|
45
|
+
ts.forEachChild(sourceFile, function visit(node) {
|
|
46
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
47
|
+
const propertyName = node.name.text;
|
|
48
|
+
const expressionText = node.expression.getText(sourceFile);
|
|
49
|
+
// Static: NgZone.method()
|
|
50
|
+
if (expressionText === ngZoneClassName && staticMethods.has(propertyName)) {
|
|
51
|
+
unsupportedUsages.push(node);
|
|
52
|
+
}
|
|
53
|
+
// Instance: zone.method() or this.zone.method()
|
|
54
|
+
if (instanceMethods.has(propertyName)) {
|
|
55
|
+
unsupportedUsages.push(node);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
ts.forEachChild(node, visit);
|
|
59
|
+
});
|
|
60
|
+
return unsupportedUsages;
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
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 { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol';
|
|
9
|
+
import { ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types';
|
|
10
|
+
import type { SourceFile } from 'typescript';
|
|
11
|
+
import { MigrationResponse } from './types';
|
|
12
|
+
export declare function migrateSingleFile(sourceFile: SourceFile, extras: RequestHandlerExtra<ServerRequest, ServerNotification>): Promise<MigrationResponse | null>;
|