@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.
- package/lib/code-examples.db +0 -0
- package/package.json +16 -15
- 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/package-manager.d.ts +12 -0
- package/src/utilities/package-manager.js +31 -22
- package/src/utilities/version.js +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
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 });
|
|
@@ -0,0 +1,14 @@
|
|
|
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 { z } from 'zod';
|
|
11
|
+
export declare const ZONELESS_MIGRATION_TOOL: import("../tool-registry").McpToolDeclaration<{
|
|
12
|
+
fileOrDirPath: z.ZodString;
|
|
13
|
+
}, z.ZodRawShape>;
|
|
14
|
+
export declare function registerZonelessMigrationTool(fileOrDirPath: string, extras: RequestHandlerExtra<ServerRequest, ServerNotification>): Promise<import("./types").MigrationResponse>;
|
|
@@ -0,0 +1,205 @@
|
|
|
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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.ZONELESS_MIGRATION_TOOL = void 0;
|
|
44
|
+
exports.registerZonelessMigrationTool = registerZonelessMigrationTool;
|
|
45
|
+
const fs = __importStar(require("node:fs"));
|
|
46
|
+
const promises_1 = require("node:fs/promises");
|
|
47
|
+
const zod_1 = require("zod");
|
|
48
|
+
const tool_registry_1 = require("../tool-registry");
|
|
49
|
+
const analyze_for_unsupported_zone_uses_1 = require("./analyze_for_unsupported_zone_uses");
|
|
50
|
+
const migrate_single_file_1 = require("./migrate_single_file");
|
|
51
|
+
const migrate_test_file_1 = require("./migrate_test_file");
|
|
52
|
+
const prompts_1 = require("./prompts");
|
|
53
|
+
const send_debug_message_1 = require("./send_debug_message");
|
|
54
|
+
const ts_utils_1 = require("./ts_utils");
|
|
55
|
+
exports.ZONELESS_MIGRATION_TOOL = (0, tool_registry_1.declareTool)({
|
|
56
|
+
name: 'onpush-zoneless-migration',
|
|
57
|
+
title: 'Plan migration to OnPush and/or zoneless',
|
|
58
|
+
description: `
|
|
59
|
+
<Purpose>
|
|
60
|
+
Analyzes Angular code and provides a step-by-step, iterative plan to migrate it to \`OnPush\`
|
|
61
|
+
change detection, a prerequisite for a zoneless application. This tool identifies the next single
|
|
62
|
+
most important action to take in the migration journey.
|
|
63
|
+
</Purpose>
|
|
64
|
+
<Use Cases>
|
|
65
|
+
* **Step-by-Step Migration:** Running the tool repeatedly to get the next instruction for a full
|
|
66
|
+
migration to \`OnPush\`.
|
|
67
|
+
* **Pre-Migration Analysis:** Checking a component or directory for unsupported \`NgZone\` APIs that
|
|
68
|
+
would block a zoneless migration.
|
|
69
|
+
* **Generating Component Migrations:** Getting the exact instructions for converting a single
|
|
70
|
+
component from the default change detection strategy to \`OnPush\`.
|
|
71
|
+
</Use Cases>
|
|
72
|
+
<Operational Notes>
|
|
73
|
+
* **Execution Model:** This tool **DOES NOT** modify code. It **PROVIDES INSTRUCTIONS** for a
|
|
74
|
+
single action at a time. You **MUST** apply the changes it suggests, and then run the tool
|
|
75
|
+
again to get the next step.
|
|
76
|
+
* **Iterative Process:** The migration process is iterative. You must call this tool repeatedly,
|
|
77
|
+
applying the suggested fix after each call, until the tool indicates that no more actions are
|
|
78
|
+
needed.
|
|
79
|
+
* **Relationship to \`modernize\`:** This tool is the specialized starting point for the zoneless/OnPush
|
|
80
|
+
migration. For other migrations (like signal inputs), you should use the \`modernize\` tool first,
|
|
81
|
+
as the zoneless migration may depend on them as prerequisites.
|
|
82
|
+
* **Input:** The tool can operate on either a single file or an entire directory. Provide the
|
|
83
|
+
absolute path.
|
|
84
|
+
</Operational Notes>`,
|
|
85
|
+
isReadOnly: true,
|
|
86
|
+
isLocalOnly: true,
|
|
87
|
+
inputSchema: {
|
|
88
|
+
fileOrDirPath: zod_1.z
|
|
89
|
+
.string()
|
|
90
|
+
.describe('The absolute path of the directory or file with the component(s), directive(s), or service(s) to migrate.' +
|
|
91
|
+
' The contents are read with fs.readFileSync.'),
|
|
92
|
+
},
|
|
93
|
+
factory: () => ({ fileOrDirPath }, requestHandlerExtra) => registerZonelessMigrationTool(fileOrDirPath, requestHandlerExtra),
|
|
94
|
+
});
|
|
95
|
+
async function registerZonelessMigrationTool(fileOrDirPath, extras) {
|
|
96
|
+
let files = [];
|
|
97
|
+
const componentTestFiles = new Set();
|
|
98
|
+
const filesWithComponents = new Set();
|
|
99
|
+
const zoneFiles = new Set();
|
|
100
|
+
if (fs.statSync(fileOrDirPath).isDirectory()) {
|
|
101
|
+
const allFiles = (0, promises_1.glob)(`${fileOrDirPath}/**/*.ts`);
|
|
102
|
+
for await (const file of allFiles) {
|
|
103
|
+
files.push(await (0, ts_utils_1.createSourceFile)(file));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
files = [await (0, ts_utils_1.createSourceFile)(fileOrDirPath)];
|
|
108
|
+
const maybeTestFile = await getTestFilePath(fileOrDirPath);
|
|
109
|
+
if (maybeTestFile) {
|
|
110
|
+
componentTestFiles.add(await (0, ts_utils_1.createSourceFile)(maybeTestFile));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
for (const sourceFile of files) {
|
|
114
|
+
const content = sourceFile.getFullText();
|
|
115
|
+
const componentSpecifier = await (0, ts_utils_1.getImportSpecifier)(sourceFile, '@angular/core', 'Component');
|
|
116
|
+
const zoneSpecifier = await (0, ts_utils_1.getImportSpecifier)(sourceFile, '@angular/core', 'NgZone');
|
|
117
|
+
const testBedSpecifier = await (0, ts_utils_1.getImportSpecifier)(sourceFile, /(@angular\/core)?\/testing/, 'TestBed');
|
|
118
|
+
if (testBedSpecifier) {
|
|
119
|
+
componentTestFiles.add(sourceFile);
|
|
120
|
+
}
|
|
121
|
+
else if (componentSpecifier) {
|
|
122
|
+
if (!content.includes('changeDetectionStrategy: ChangeDetectionStrategy.OnPush') &&
|
|
123
|
+
!content.includes('changeDetectionStrategy: ChangeDetectionStrategy.Default')) {
|
|
124
|
+
filesWithComponents.add(sourceFile);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
(0, send_debug_message_1.sendDebugMessage)(`Component file already has change detection strategy: ${sourceFile.fileName}. Skipping migration.`, extras);
|
|
128
|
+
}
|
|
129
|
+
const testFilePath = await getTestFilePath(sourceFile.fileName);
|
|
130
|
+
if (testFilePath) {
|
|
131
|
+
componentTestFiles.add(await (0, ts_utils_1.createSourceFile)(testFilePath));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (zoneSpecifier) {
|
|
135
|
+
zoneFiles.add(sourceFile);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (zoneFiles.size > 0) {
|
|
139
|
+
for (const file of zoneFiles) {
|
|
140
|
+
const result = await (0, analyze_for_unsupported_zone_uses_1.analyzeForUnsupportedZoneUses)(file);
|
|
141
|
+
if (result !== null) {
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (filesWithComponents.size > 0) {
|
|
147
|
+
const rankedFiles = filesWithComponents.size > 1
|
|
148
|
+
? await rankComponentFilesForMigration(extras, Array.from(filesWithComponents))
|
|
149
|
+
: Array.from(filesWithComponents);
|
|
150
|
+
for (const file of rankedFiles) {
|
|
151
|
+
const result = await (0, migrate_single_file_1.migrateSingleFile)(file, extras);
|
|
152
|
+
if (result !== null) {
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const file of componentTestFiles) {
|
|
158
|
+
const result = await (0, migrate_test_file_1.migrateTestFile)(file);
|
|
159
|
+
if (result !== null) {
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return (0, prompts_1.createTestDebuggingGuideForNonActionableInput)(fileOrDirPath);
|
|
164
|
+
}
|
|
165
|
+
async function rankComponentFilesForMigration({ sendRequest }, componentFiles) {
|
|
166
|
+
try {
|
|
167
|
+
const response = await sendRequest({
|
|
168
|
+
method: 'sampling/createMessage',
|
|
169
|
+
params: {
|
|
170
|
+
messages: [
|
|
171
|
+
{
|
|
172
|
+
role: 'user',
|
|
173
|
+
content: {
|
|
174
|
+
type: 'text',
|
|
175
|
+
text: `The following files are components that need to be migrated to OnPush change detection.` +
|
|
176
|
+
` Please rank them based on which ones are most likely to be shared or common components.` +
|
|
177
|
+
` The most likely shared component should be first.
|
|
178
|
+
${componentFiles.map((f) => f.fileName).join('\n ')}
|
|
179
|
+
Respond ONLY with the ranked list of files, one file per line.`,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
systemPrompt: 'You are a helpful assistant that helps migrate identify shared Angular components.',
|
|
184
|
+
maxTokens: 2000,
|
|
185
|
+
},
|
|
186
|
+
}, zod_1.z.object({ sortedFiles: zod_1.z.array(zod_1.z.string()) }));
|
|
187
|
+
const rankedFiles = response.sortedFiles
|
|
188
|
+
.map((line) => line.trim())
|
|
189
|
+
.map((fileName) => componentFiles.find((f) => f.fileName === fileName))
|
|
190
|
+
.filter((f) => !!f);
|
|
191
|
+
// Ensure the ranking didn't mess up the list of files
|
|
192
|
+
if (rankedFiles.length === componentFiles.length) {
|
|
193
|
+
return rankedFiles;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch { }
|
|
197
|
+
return componentFiles; // Fallback to original order if the response fails
|
|
198
|
+
}
|
|
199
|
+
async function getTestFilePath(filePath) {
|
|
200
|
+
const testFilePath = filePath.replace(/\.ts$/, '.spec.ts');
|
|
201
|
+
if (fs.existsSync(testFilePath)) {
|
|
202
|
+
return testFilePath;
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
@@ -12,8 +12,14 @@ export interface PackageManagerUtilsContext {
|
|
|
12
12
|
workspace?: AngularWorkspace;
|
|
13
13
|
root: string;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Utilities for interacting with various package managers.
|
|
17
|
+
*/
|
|
15
18
|
export declare class PackageManagerUtils {
|
|
16
19
|
private readonly context;
|
|
20
|
+
/**
|
|
21
|
+
* @param context The context for the package manager utilities, including workspace and global configuration.
|
|
22
|
+
*/
|
|
17
23
|
constructor(context: PackageManagerUtilsContext);
|
|
18
24
|
/** Get the package manager name. */
|
|
19
25
|
get name(): PackageManager;
|
|
@@ -32,6 +38,12 @@ export declare class PackageManagerUtils {
|
|
|
32
38
|
private run;
|
|
33
39
|
private getVersion;
|
|
34
40
|
private getName;
|
|
41
|
+
/**
|
|
42
|
+
* Checks if a lockfile for a specific package manager exists in the root directory.
|
|
43
|
+
* @param packageManager The package manager to check for.
|
|
44
|
+
* @param filesInRoot An array of file names in the root directory.
|
|
45
|
+
* @returns True if the lockfile exists, false otherwise.
|
|
46
|
+
*/
|
|
35
47
|
private hasLockfile;
|
|
36
48
|
private getConfiguredPackageManager;
|
|
37
49
|
}
|
|
@@ -50,6 +50,18 @@ const node_path_1 = require("node:path");
|
|
|
50
50
|
const workspace_schema_1 = require("../../lib/config/workspace-schema");
|
|
51
51
|
const config_1 = require("./config");
|
|
52
52
|
const memoize_1 = require("./memoize");
|
|
53
|
+
/**
|
|
54
|
+
* A map of package managers to their corresponding lockfile names.
|
|
55
|
+
*/
|
|
56
|
+
const LOCKFILE_NAMES = {
|
|
57
|
+
[workspace_schema_1.PackageManager.Yarn]: 'yarn.lock',
|
|
58
|
+
[workspace_schema_1.PackageManager.Pnpm]: 'pnpm-lock.yaml',
|
|
59
|
+
[workspace_schema_1.PackageManager.Bun]: ['bun.lockb', 'bun.lock'],
|
|
60
|
+
[workspace_schema_1.PackageManager.Npm]: 'package-lock.json',
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Utilities for interacting with various package managers.
|
|
64
|
+
*/
|
|
53
65
|
let PackageManagerUtils = (() => {
|
|
54
66
|
let _instanceExtraInitializers = [];
|
|
55
67
|
let _getVersion_decorators;
|
|
@@ -64,6 +76,9 @@ let PackageManagerUtils = (() => {
|
|
|
64
76
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
65
77
|
}
|
|
66
78
|
context = __runInitializers(this, _instanceExtraInitializers);
|
|
79
|
+
/**
|
|
80
|
+
* @param context The context for the package manager utilities, including workspace and global configuration.
|
|
81
|
+
*/
|
|
67
82
|
constructor(context) {
|
|
68
83
|
this.context = context;
|
|
69
84
|
}
|
|
@@ -211,10 +226,11 @@ let PackageManagerUtils = (() => {
|
|
|
211
226
|
if (packageManager) {
|
|
212
227
|
return packageManager;
|
|
213
228
|
}
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
const
|
|
229
|
+
const filesInRoot = (0, node_fs_1.readdirSync)(this.context.root);
|
|
230
|
+
const hasNpmLock = this.hasLockfile(workspace_schema_1.PackageManager.Npm, filesInRoot);
|
|
231
|
+
const hasYarnLock = this.hasLockfile(workspace_schema_1.PackageManager.Yarn, filesInRoot);
|
|
232
|
+
const hasPnpmLock = this.hasLockfile(workspace_schema_1.PackageManager.Pnpm, filesInRoot);
|
|
233
|
+
const hasBunLock = this.hasLockfile(workspace_schema_1.PackageManager.Bun, filesInRoot);
|
|
218
234
|
// PERF NOTE: `this.getVersion` spawns the package a the child_process which can take around ~300ms at times.
|
|
219
235
|
// Therefore, we should only call this method when needed. IE: don't call `this.getVersion(PackageManager.Pnpm)` unless truly needed.
|
|
220
236
|
// The result of this method is not stored in a variable because it's memoized.
|
|
@@ -259,24 +275,17 @@ let PackageManagerUtils = (() => {
|
|
|
259
275
|
// Potentially with a prompt to choose and optionally set as the default.
|
|
260
276
|
return workspace_schema_1.PackageManager.Npm;
|
|
261
277
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
break;
|
|
274
|
-
case workspace_schema_1.PackageManager.Npm:
|
|
275
|
-
default:
|
|
276
|
-
lockfileName = 'package-lock.json';
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
return (0, node_fs_1.existsSync)((0, node_path_1.join)(this.context.root, lockfileName));
|
|
278
|
+
/**
|
|
279
|
+
* Checks if a lockfile for a specific package manager exists in the root directory.
|
|
280
|
+
* @param packageManager The package manager to check for.
|
|
281
|
+
* @param filesInRoot An array of file names in the root directory.
|
|
282
|
+
* @returns True if the lockfile exists, false otherwise.
|
|
283
|
+
*/
|
|
284
|
+
hasLockfile(packageManager, filesInRoot) {
|
|
285
|
+
const lockfiles = LOCKFILE_NAMES[packageManager];
|
|
286
|
+
return typeof lockfiles === 'string'
|
|
287
|
+
? filesInRoot.includes(lockfiles)
|
|
288
|
+
: lockfiles.some((lockfile) => filesInRoot.includes(lockfile));
|
|
280
289
|
}
|
|
281
290
|
getConfiguredPackageManager() {
|
|
282
291
|
const getPackageManager = (source) => {
|
package/src/utilities/version.js
CHANGED