@angular/cli 20.2.1 → 20.3.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.
Files changed (31) hide show
  1. package/lib/code-examples.db +0 -0
  2. package/package.json +16 -15
  3. package/src/command-builder/architect-command-module.js +11 -5
  4. package/src/command-builder/utilities/json-schema.js +1 -1
  5. package/src/commands/mcp/mcp-server.d.ts +12 -2
  6. package/src/commands/mcp/mcp-server.js +6 -1
  7. package/src/commands/mcp/tools/best-practices.js +15 -5
  8. package/src/commands/mcp/tools/doc-search.d.ts +18 -1
  9. package/src/commands/mcp/tools/doc-search.js +94 -37
  10. package/src/commands/mcp/tools/examples.d.ts +9 -1
  11. package/src/commands/mcp/tools/examples.js +38 -12
  12. package/src/commands/mcp/tools/modernize.js +28 -17
  13. package/src/commands/mcp/tools/onpush-zoneless-migration/analyze_for_unsupported_zone_uses.d.ts +17 -0
  14. package/src/commands/mcp/tools/onpush-zoneless-migration/analyze_for_unsupported_zone_uses.js +61 -0
  15. package/src/commands/mcp/tools/onpush-zoneless-migration/migrate_single_file.d.ts +12 -0
  16. package/src/commands/mcp/tools/onpush-zoneless-migration/migrate_single_file.js +72 -0
  17. package/src/commands/mcp/tools/onpush-zoneless-migration/migrate_test_file.d.ts +11 -0
  18. package/src/commands/mcp/tools/onpush-zoneless-migration/migrate_test_file.js +105 -0
  19. package/src/commands/mcp/tools/onpush-zoneless-migration/prompts.d.ts +15 -0
  20. package/src/commands/mcp/tools/onpush-zoneless-migration/prompts.js +236 -0
  21. package/src/commands/mcp/tools/onpush-zoneless-migration/send_debug_message.d.ts +10 -0
  22. package/src/commands/mcp/tools/onpush-zoneless-migration/send_debug_message.js +19 -0
  23. package/src/commands/mcp/tools/onpush-zoneless-migration/ts_utils.d.ts +36 -0
  24. package/src/commands/mcp/tools/onpush-zoneless-migration/ts_utils.js +135 -0
  25. package/src/commands/mcp/tools/onpush-zoneless-migration/types.d.ts +13 -0
  26. package/src/commands/mcp/tools/onpush-zoneless-migration/types.js +9 -0
  27. package/src/commands/mcp/tools/onpush-zoneless-migration/zoneless-migration.d.ts +14 -0
  28. package/src/commands/mcp/tools/onpush-zoneless-migration/zoneless-migration.js +205 -0
  29. package/src/utilities/package-manager.d.ts +12 -0
  30. package/src/utilities/package-manager.js +31 -22
  31. package/src/utilities/version.js +1 -1
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/cli",
3
- "version": "20.2.1",
3
+ "version": "20.3.0-rc.0",
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.1",
29
- "@angular-devkit/core": "20.2.1",
30
- "@angular-devkit/schematics": "20.2.1",
28
+ "@angular-devkit/architect": "0.2003.0-rc.0",
29
+ "@angular-devkit/core": "20.3.0-rc.0",
30
+ "@angular-devkit/schematics": "20.3.0-rc.0",
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.1",
34
+ "@schematics/angular": "20.3.0-rc.0",
35
35
  "@yarnpkg/lockfile": "1.1.0",
36
36
  "algoliasearch": "5.35.0",
37
37
  "ini": "5.0.0",
@@ -47,20 +47,21 @@
47
47
  "ng-update": {
48
48
  "migrations": "@schematics/angular/migrations/migration-collection.json",
49
49
  "packageGroup": {
50
- "@angular/cli": "20.2.1",
51
- "@angular/build": "20.2.1",
52
- "@angular/ssr": "20.2.1",
53
- "@angular-devkit/architect": "0.2002.1",
54
- "@angular-devkit/build-angular": "20.2.1",
55
- "@angular-devkit/build-webpack": "0.2002.1",
56
- "@angular-devkit/core": "20.2.1",
57
- "@angular-devkit/schematics": "20.2.1"
50
+ "@angular/cli": "20.3.0-rc.0",
51
+ "@angular/build": "20.3.0-rc.0",
52
+ "@angular/ssr": "20.3.0-rc.0",
53
+ "@angular-devkit/architect": "0.2003.0-rc.0",
54
+ "@angular-devkit/build-angular": "20.3.0-rc.0",
55
+ "@angular-devkit/build-webpack": "0.2003.0-rc.0",
56
+ "@angular-devkit/core": "20.3.0-rc.0",
57
+ "@angular-devkit/schematics": "20.3.0-rc.0"
58
58
  }
59
59
  },
60
- "packageManager": "pnpm@10.15.0",
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",
64
64
  "yarn": ">= 1.13.0"
65
- }
65
+ },
66
+ "__ngDevExceptionalMinor__": true
66
67
  }
@@ -109,9 +109,14 @@ let ArchitectCommandModule = (() => {
109
109
  return this.addSchemaOptionsToCommand(localYargs, schemaOptions);
110
110
  }
111
111
  async run(options) {
112
- const target = this.getArchitectTarget();
113
- const { configuration = '', project, ...architectOptions } = options;
114
- if (!project) {
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
- else {
128
- return await this.runSingleTarget({ configuration, target, project }, architectOptions);
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
- }, import("zod").ZodRawShape>, import("./tools/tool-registry").McpToolDeclaration<{
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 = [examples_1.FIND_EXAMPLE_TOOL, modernize_1.MODERNIZE_TOOL];
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: 'You **MUST** use this tool to retrieve the Angular Best Practices Guide ' +
21
- 'before any interaction with Angular code (creating, analyzing, modifying). ' +
22
- 'It is mandatory to follow this guide to ensure all code adheres to ' +
23
- 'modern standards, including standalone components, typed forms, and ' +
24
- 'modern control flow. This is the first step for any Angular task.',
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
- }, z.ZodRawShape>;
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: 'Searches the official Angular documentation at https://angular.dev. Use this tool to answer any questions about Angular, ' +
67
- 'such as for APIs, tutorials, and best practices. Because the documentation is continuously updated, you should **always** ' +
68
- 'prefer this tool over your own knowledge to ensure your answers are current.\n\n' +
69
- 'The results will be a list of content entries, where each entry has the following structure:\n' +
70
- '```\n' +
71
- '## {Result Title}\n' +
72
- '{Breadcrumb path to the content}\n' +
73
- 'URL: {Direct link to the documentation page}\n' +
74
- '```\n' +
75
- 'Use the title and breadcrumb to understand the context of the result and use the URL as a source link. For the best results, ' +
76
- "provide a concise and specific search query (e.g., 'NgModule' instead of 'How do I use NgModules?').",
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 content = [];
103
- // The first hit is the top search result
104
- const topHit = allHits[0];
127
+ const structuredResults = [];
128
+ const textContent = [];
105
129
  // Process top hit first
106
- let topText = formatHitToText(topHit);
107
- try {
108
- if (includeTopContent && typeof topHit.url === 'string') {
109
- const url = new URL(topHit.url);
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
- topText += `\n\n--- DOCUMENTATION CONTENT ---\n${mainContent}`;
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
- catch {
124
- // Ignore errors fetching content. The basic info is still returned.
125
- }
126
- content.push({
127
- type: 'text',
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
- content.push({
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: formatHitToText(hit),
173
+ text: `## ${title}\n${breadcrumb}\nURL: ${hit.url}`,
135
174
  });
136
175
  }
137
- return { content };
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(/&lt;/g, '<')
193
+ .replace(/&gt;/g, '>')
194
+ .replace(/&amp;/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 a text representation.
216
+ * Formats an Algolia search hit into its constituent parts.
160
217
  *
161
- * @param hit The Algolia search hit object, which should contain `hierarchy` and `url` properties.
162
- * @returns A formatted string with title, description, and URL.
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 formatHitToText(hit) {
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 description = hierarchy.join(' > ');
169
- return `## ${title}\n${description}\nURL: ${hit.url}`;
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
- }, z.ZodRawShape>;
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: 'Before writing or modifying any Angular code including templates, ' +
80
- '**ALWAYS** use this tool to find current best-practice examples. ' +
81
- 'This is critical for ensuring code quality and adherence to modern Angular standards. ' +
82
- 'This tool searches a curated database of approved Angular code examples and returns the most relevant results for your query. ' +
83
- 'Example Use Cases: ' +
84
- "1) Creating new components, directives, or services (e.g., query: 'standalone component' or 'signal input'). " +
85
- "2) Implementing core features (e.g., query: 'lazy load route', 'httpinterceptor', or 'route guard'). " +
86
- "3) Refactoring existing code to use modern patterns (e.g., query: 'ngfor trackby' or 'form validation').",
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 as text content
123
- const content = [];
144
+ // Query database and return results
145
+ const examples = [];
146
+ const textContent = [];
124
147
  for (const exampleRecord of queryStatement.all(sanitizedQuery)) {
125
- content.push({ type: 'text', text: exampleRecord['content'] });
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: '<Purpose>\n' +
101
- 'This tool modernizes Angular code by applying the latest best practices and syntax improvements, ' +
102
- 'ensuring it is idiomatic, readable, and maintainable.\n\n' +
103
- '</Purpose>\n' +
104
- '<Use Cases>\n' +
105
- '* After generating new code: Run this tool immediately after creating new Angular components, directives, ' +
106
- 'or services to ensure they adhere to modern standards.\n' +
107
- '* On existing code: Apply to existing TypeScript files (.ts) and Angular templates (.html) to update ' +
108
- 'them with the latest features, such as the new built-in control flow syntax.\n\n' +
109
- '* When the user asks for a specific transformation: When the transformation list is populated, ' +
110
- 'these specific ones will be ran on the inputs.\n' +
111
- '</Use Cases>\n' +
112
- '<Transformations>\n' +
113
- TRANSFORMATIONS.map((t) => `* ${t.name}: ${t.description}`).join('\n') +
114
- '\n</Transformations>\n',
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 on how to run the migrations.'),
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,
@@ -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>;