@angular/cli 20.1.0 → 20.1.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/package.json +14 -13
- package/src/command-builder/utilities/json-help.js +5 -2
- package/src/commands/mcp/constants.d.ts +10 -0
- package/src/commands/mcp/constants.js +15 -0
- package/src/commands/mcp/mcp-server.js +6 -67
- package/src/commands/mcp/tools/best-practices.d.ts +9 -0
- package/src/commands/mcp/tools/best-practices.js +39 -0
- package/src/commands/mcp/tools/doc-search.d.ts +16 -0
- package/src/commands/mcp/tools/doc-search.js +209 -0
- package/src/commands/mcp/tools/projects.d.ts +12 -0
- package/src/commands/mcp/tools/projects.js +85 -0
- package/src/utilities/version.js +1 -1
- package/src/commands/mcp/component-generation-prompt.md +0 -60
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular/cli",
|
|
3
|
-
"version": "20.1.
|
|
3
|
+
"version": "20.1.2",
|
|
4
4
|
"description": "CLI tool for Angular",
|
|
5
5
|
"main": "lib/cli/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,14 +25,15 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/angular/angular-cli",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@angular-devkit/architect": "0.2001.
|
|
29
|
-
"@angular-devkit/core": "20.1.
|
|
30
|
-
"@angular-devkit/schematics": "20.1.
|
|
28
|
+
"@angular-devkit/architect": "0.2001.2",
|
|
29
|
+
"@angular-devkit/core": "20.1.2",
|
|
30
|
+
"@angular-devkit/schematics": "20.1.2",
|
|
31
31
|
"@inquirer/prompts": "7.6.0",
|
|
32
32
|
"@listr2/prompt-adapter-inquirer": "2.0.22",
|
|
33
33
|
"@modelcontextprotocol/sdk": "1.13.3",
|
|
34
|
-
"@schematics/angular": "20.1.
|
|
34
|
+
"@schematics/angular": "20.1.2",
|
|
35
35
|
"@yarnpkg/lockfile": "1.1.0",
|
|
36
|
+
"algoliasearch": "5.32.0",
|
|
36
37
|
"ini": "5.0.0",
|
|
37
38
|
"jsonc-parser": "3.3.1",
|
|
38
39
|
"listr2": "8.3.3",
|
|
@@ -47,14 +48,14 @@
|
|
|
47
48
|
"ng-update": {
|
|
48
49
|
"migrations": "@schematics/angular/migrations/migration-collection.json",
|
|
49
50
|
"packageGroup": {
|
|
50
|
-
"@angular/cli": "20.1.
|
|
51
|
-
"@angular/build": "20.1.
|
|
52
|
-
"@angular/ssr": "20.1.
|
|
53
|
-
"@angular-devkit/architect": "0.2001.
|
|
54
|
-
"@angular-devkit/build-angular": "20.1.
|
|
55
|
-
"@angular-devkit/build-webpack": "0.2001.
|
|
56
|
-
"@angular-devkit/core": "20.1.
|
|
57
|
-
"@angular-devkit/schematics": "20.1.
|
|
51
|
+
"@angular/cli": "20.1.2",
|
|
52
|
+
"@angular/build": "20.1.2",
|
|
53
|
+
"@angular/ssr": "20.1.2",
|
|
54
|
+
"@angular-devkit/architect": "0.2001.2",
|
|
55
|
+
"@angular-devkit/build-angular": "20.1.2",
|
|
56
|
+
"@angular-devkit/build-webpack": "0.2001.2",
|
|
57
|
+
"@angular-devkit/core": "20.1.2",
|
|
58
|
+
"@angular-devkit/schematics": "20.1.2"
|
|
58
59
|
}
|
|
59
60
|
},
|
|
60
61
|
"packageManager": "pnpm@9.15.9",
|
|
@@ -19,20 +19,23 @@ function jsonHelpUsage(localYargs) {
|
|
|
19
19
|
const descriptions = usageInstance.getDescriptions();
|
|
20
20
|
const groups = localYargsInstance.getGroups();
|
|
21
21
|
const positional = groups[usageInstance.getPositionalGroupName()];
|
|
22
|
+
const seen = new Set();
|
|
22
23
|
const hidden = new Set(hiddenOptions);
|
|
23
24
|
const normalizeOptions = [];
|
|
24
25
|
const allAliases = new Set([...Object.values(aliases).flat()]);
|
|
26
|
+
// Reverted order of https://github.com/yargs/yargs/blob/971e351705f0fbc5566c6ed1dfd707fa65e11c0d/lib/usage.ts#L419-L424
|
|
25
27
|
for (const [names, type] of [
|
|
28
|
+
[number, 'number'],
|
|
26
29
|
[array, 'array'],
|
|
27
30
|
[string, 'string'],
|
|
28
31
|
[boolean, 'boolean'],
|
|
29
|
-
[number, 'number'],
|
|
30
32
|
]) {
|
|
31
33
|
for (const name of names) {
|
|
32
|
-
if (allAliases.has(name) || hidden.has(name)) {
|
|
34
|
+
if (allAliases.has(name) || hidden.has(name) || seen.has(name)) {
|
|
33
35
|
// Ignore hidden, aliases and already visited option.
|
|
34
36
|
continue;
|
|
35
37
|
}
|
|
38
|
+
seen.add(name);
|
|
36
39
|
const positionalIndex = positional?.indexOf(name) ?? -1;
|
|
37
40
|
const alias = aliases[name];
|
|
38
41
|
normalizeOptions.push({
|
|
@@ -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
|
+
export declare const k1 = "@angular/cli";
|
|
9
|
+
export declare const at = "QBHBbOdEO4CmBOC2d7jNmg==";
|
|
10
|
+
export declare const iv: Buffer<ArrayBuffer>;
|
|
@@ -0,0 +1,15 @@
|
|
|
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.iv = exports.at = exports.k1 = void 0;
|
|
11
|
+
exports.k1 = '@angular/cli';
|
|
12
|
+
exports.at = 'QBHBbOdEO4CmBOC2d7jNmg==';
|
|
13
|
+
exports.iv = Buffer.from([
|
|
14
|
+
0x97, 0xf4, 0x62, 0x95, 0x3e, 0x12, 0x76, 0x84, 0x8a, 0x09, 0x4a, 0xc9, 0xeb, 0xa2, 0x84, 0x69,
|
|
15
|
+
]);
|
|
@@ -14,8 +14,10 @@ exports.createMcpServer = createMcpServer;
|
|
|
14
14
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
15
15
|
const promises_1 = require("node:fs/promises");
|
|
16
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
17
|
-
const zod_1 = require("zod");
|
|
18
17
|
const version_1 = require("../../utilities/version");
|
|
18
|
+
const best_practices_1 = require("./tools/best-practices");
|
|
19
|
+
const doc_search_1 = require("./tools/doc-search");
|
|
20
|
+
const projects_1 = require("./tools/projects");
|
|
19
21
|
async function createMcpServer(context) {
|
|
20
22
|
const server = new mcp_js_1.McpServer({
|
|
21
23
|
name: 'angular-cli-server',
|
|
@@ -36,71 +38,8 @@ async function createMcpServer(context) {
|
|
|
36
38
|
const text = await (0, promises_1.readFile)(node_path_1.default.join(__dirname, 'instructions', 'best-practices.md'), 'utf-8');
|
|
37
39
|
return { contents: [{ uri: 'instructions://best-practices', text }] };
|
|
38
40
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
'It reads the `angular.json` configuration file to identify the projects. ',
|
|
43
|
-
annotations: {
|
|
44
|
-
readOnlyHint: true,
|
|
45
|
-
},
|
|
46
|
-
outputSchema: {
|
|
47
|
-
projects: zod_1.z.array(zod_1.z.object({
|
|
48
|
-
name: zod_1.z
|
|
49
|
-
.string()
|
|
50
|
-
.describe('The name of the project, as defined in the `angular.json` file.'),
|
|
51
|
-
type: zod_1.z
|
|
52
|
-
.enum(['application', 'library'])
|
|
53
|
-
.optional()
|
|
54
|
-
.describe(`The type of the project, either 'application' or 'library'.`),
|
|
55
|
-
root: zod_1.z
|
|
56
|
-
.string()
|
|
57
|
-
.describe('The root directory of the project, relative to the workspace root.'),
|
|
58
|
-
sourceRoot: zod_1.z
|
|
59
|
-
.string()
|
|
60
|
-
.describe(`The root directory of the project's source files, relative to the workspace root.`),
|
|
61
|
-
selectorPrefix: zod_1.z
|
|
62
|
-
.string()
|
|
63
|
-
.optional()
|
|
64
|
-
.describe('The prefix to use for component selectors.' +
|
|
65
|
-
` For example, a prefix of 'app' would result in selectors like '<app-my-component>'.`),
|
|
66
|
-
})),
|
|
67
|
-
},
|
|
68
|
-
}, async () => {
|
|
69
|
-
const { workspace } = context;
|
|
70
|
-
if (!workspace) {
|
|
71
|
-
return {
|
|
72
|
-
content: [
|
|
73
|
-
{
|
|
74
|
-
type: 'text',
|
|
75
|
-
text: 'No Angular workspace found.' +
|
|
76
|
-
' An `angular.json` file, which marks the root of a workspace,' +
|
|
77
|
-
' could not be located in the current directory or any of its parent directories.',
|
|
78
|
-
},
|
|
79
|
-
],
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
const projects = [];
|
|
83
|
-
// Convert to output format
|
|
84
|
-
for (const [name, project] of workspace.projects.entries()) {
|
|
85
|
-
projects.push({
|
|
86
|
-
name,
|
|
87
|
-
type: project.extensions['projectType'],
|
|
88
|
-
root: project.root,
|
|
89
|
-
sourceRoot: project.sourceRoot ?? node_path_1.default.posix.join(project.root, 'src'),
|
|
90
|
-
selectorPrefix: project.extensions['prefix'],
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
// The structuredContent field is newer and may not be supported by all hosts.
|
|
94
|
-
// A text representation of the content is also provided for compatibility.
|
|
95
|
-
return {
|
|
96
|
-
content: [
|
|
97
|
-
{
|
|
98
|
-
type: 'text',
|
|
99
|
-
text: `Projects in the Angular workspace:\n${JSON.stringify(projects)}`,
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
structuredContent: { projects },
|
|
103
|
-
};
|
|
104
|
-
});
|
|
41
|
+
(0, best_practices_1.registerBestPracticesTool)(server);
|
|
42
|
+
(0, projects_1.registerListProjectsTool)(server, context);
|
|
43
|
+
await (0, doc_search_1.registerDocSearchTool)(server);
|
|
105
44
|
return server;
|
|
106
45
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
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 { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
|
+
export declare function registerBestPracticesTool(server: McpServer): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
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.registerBestPracticesTool = registerBestPracticesTool;
|
|
14
|
+
const promises_1 = require("node:fs/promises");
|
|
15
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
16
|
+
function registerBestPracticesTool(server) {
|
|
17
|
+
server.registerTool('get_best_practices', {
|
|
18
|
+
title: 'Get Angular Coding Best Practices Guide',
|
|
19
|
+
description: 'You **MUST** use this tool to retrieve the Angular Best Practices Guide ' +
|
|
20
|
+
'before any interaction with Angular code (creating, analyzing, modifying). ' +
|
|
21
|
+
'It is mandatory to follow this guide to ensure all code adheres to ' +
|
|
22
|
+
'modern standards, including standalone components, typed forms, and ' +
|
|
23
|
+
'modern control flow. This is the first step for any Angular task.',
|
|
24
|
+
annotations: {
|
|
25
|
+
readOnlyHint: true,
|
|
26
|
+
openWorldHint: false,
|
|
27
|
+
},
|
|
28
|
+
}, async () => {
|
|
29
|
+
const text = await (0, promises_1.readFile)(node_path_1.default.join(__dirname, '..', 'instructions', 'best-practices.md'), 'utf-8');
|
|
30
|
+
return {
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: 'text',
|
|
34
|
+
text,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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 { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
|
+
/**
|
|
10
|
+
* Registers a tool with the MCP server to search the Angular documentation.
|
|
11
|
+
*
|
|
12
|
+
* This tool uses Algolia to search the official Angular documentation.
|
|
13
|
+
*
|
|
14
|
+
* @param server The MCP server instance with which to register the tool.
|
|
15
|
+
*/
|
|
16
|
+
export declare function registerDocSearchTool(server: McpServer): Promise<void>;
|
|
@@ -0,0 +1,209 @@
|
|
|
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.registerDocSearchTool = registerDocSearchTool;
|
|
44
|
+
const node_crypto_1 = require("node:crypto");
|
|
45
|
+
const zod_1 = require("zod");
|
|
46
|
+
const constants_1 = require("../constants");
|
|
47
|
+
const ALGOLIA_APP_ID = 'L1XWT2UJ7F';
|
|
48
|
+
// https://www.algolia.com/doc/guides/security/api-keys/#search-only-api-key
|
|
49
|
+
// This is a search only, rate limited key. It is sent within the URL of the query request.
|
|
50
|
+
// This is not the actual key.
|
|
51
|
+
const ALGOLIA_API_E = '322d89dab5f2080fe09b795c93413c6a89222b13a447cdf3e6486d692717bc0c';
|
|
52
|
+
/**
|
|
53
|
+
* Registers a tool with the MCP server to search the Angular documentation.
|
|
54
|
+
*
|
|
55
|
+
* This tool uses Algolia to search the official Angular documentation.
|
|
56
|
+
*
|
|
57
|
+
* @param server The MCP server instance with which to register the tool.
|
|
58
|
+
*/
|
|
59
|
+
async function registerDocSearchTool(server) {
|
|
60
|
+
let client;
|
|
61
|
+
server.registerTool('search_documentation', {
|
|
62
|
+
title: 'Search Angular Documentation (angular.dev)',
|
|
63
|
+
description: 'Searches the official Angular documentation at https://angular.dev. Use this tool to answer any questions about Angular, ' +
|
|
64
|
+
'such as for APIs, tutorials, and best practices. Because the documentation is continuously updated, you should **always** ' +
|
|
65
|
+
'prefer this tool over your own knowledge to ensure your answers are current.\n\n' +
|
|
66
|
+
'The results will be a list of content entries, where each entry has the following structure:\n' +
|
|
67
|
+
'```\n' +
|
|
68
|
+
'## {Result Title}\n' +
|
|
69
|
+
'{Breadcrumb path to the content}\n' +
|
|
70
|
+
'URL: {Direct link to the documentation page}\n' +
|
|
71
|
+
'```\n' +
|
|
72
|
+
'Use the title and breadcrumb to understand the context of the result and use the URL as a source link. For the best results, ' +
|
|
73
|
+
"provide a concise and specific search query (e.g., 'NgModule' instead of 'How do I use NgModules?').",
|
|
74
|
+
annotations: {
|
|
75
|
+
readOnlyHint: true,
|
|
76
|
+
},
|
|
77
|
+
inputSchema: {
|
|
78
|
+
query: zod_1.z
|
|
79
|
+
.string()
|
|
80
|
+
.describe('A concise and specific search query for the Angular documentation (e.g., "NgModule" or "standalone components").'),
|
|
81
|
+
includeTopContent: zod_1.z
|
|
82
|
+
.boolean()
|
|
83
|
+
.optional()
|
|
84
|
+
.default(true)
|
|
85
|
+
.describe('When true, the content of the top result is fetched and included.'),
|
|
86
|
+
},
|
|
87
|
+
}, async ({ query, includeTopContent }) => {
|
|
88
|
+
if (!client) {
|
|
89
|
+
const dcip = (0, node_crypto_1.createDecipheriv)('aes-256-gcm', (constants_1.k1 + ALGOLIA_APP_ID).padEnd(32, '^'), constants_1.iv).setAuthTag(Buffer.from(constants_1.at, 'base64'));
|
|
90
|
+
const { searchClient } = await Promise.resolve().then(() => __importStar(require('algoliasearch')));
|
|
91
|
+
client = searchClient(ALGOLIA_APP_ID, dcip.update(ALGOLIA_API_E, 'hex', 'utf-8') + dcip.final('utf-8'));
|
|
92
|
+
}
|
|
93
|
+
const { results } = await client.search(createSearchArguments(query));
|
|
94
|
+
const allHits = results.flatMap((result) => result.hits);
|
|
95
|
+
if (allHits.length === 0) {
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: 'text',
|
|
100
|
+
text: 'No results found.',
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const content = [];
|
|
106
|
+
// The first hit is the top search result
|
|
107
|
+
const topHit = allHits[0];
|
|
108
|
+
// Process top hit first
|
|
109
|
+
let topText = formatHitToText(topHit);
|
|
110
|
+
try {
|
|
111
|
+
if (includeTopContent && typeof topHit.url === 'string') {
|
|
112
|
+
const url = new URL(topHit.url);
|
|
113
|
+
// Only fetch content from angular.dev
|
|
114
|
+
if (url.hostname === 'angular.dev' || url.hostname.endsWith('.angular.dev')) {
|
|
115
|
+
const response = await fetch(url);
|
|
116
|
+
if (response.ok) {
|
|
117
|
+
const html = await response.text();
|
|
118
|
+
const mainContent = extractBodyContent(html);
|
|
119
|
+
if (mainContent) {
|
|
120
|
+
topText += `\n\n--- DOCUMENTATION CONTENT ---\n${mainContent}`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Ignore errors fetching content. The basic info is still returned.
|
|
128
|
+
}
|
|
129
|
+
content.push({
|
|
130
|
+
type: 'text',
|
|
131
|
+
text: topText,
|
|
132
|
+
});
|
|
133
|
+
// Process remaining hits
|
|
134
|
+
for (const hit of allHits.slice(1)) {
|
|
135
|
+
content.push({
|
|
136
|
+
type: 'text',
|
|
137
|
+
text: formatHitToText(hit),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return { content };
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Extracts the content of the `<body>` element from an HTML string.
|
|
145
|
+
*
|
|
146
|
+
* @param html The HTML content of a page.
|
|
147
|
+
* @returns The content of the `<body>` element, or `undefined` if not found.
|
|
148
|
+
*/
|
|
149
|
+
function extractBodyContent(html) {
|
|
150
|
+
// TODO: Use '<main>' element instead of '<body>' when available in angular.dev HTML.
|
|
151
|
+
const mainTagStart = html.indexOf('<body');
|
|
152
|
+
if (mainTagStart === -1) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
const mainTagEnd = html.lastIndexOf('</body>');
|
|
156
|
+
if (mainTagEnd <= mainTagStart) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
// Add 7 to include '</body>'
|
|
160
|
+
return html.substring(mainTagStart, mainTagEnd + 7);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Formats an Algolia search hit into a text representation.
|
|
164
|
+
*
|
|
165
|
+
* @param hit The Algolia search hit object, which should contain `hierarchy` and `url` properties.
|
|
166
|
+
* @returns A formatted string with title, description, and URL.
|
|
167
|
+
*/
|
|
168
|
+
function formatHitToText(hit) {
|
|
169
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
170
|
+
const hierarchy = Object.values(hit.hierarchy).filter((x) => typeof x === 'string');
|
|
171
|
+
const title = hierarchy.pop();
|
|
172
|
+
const description = hierarchy.join(' > ');
|
|
173
|
+
return `## ${title}\n${description}\nURL: ${hit.url}`;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Creates the search arguments for an Algolia search.
|
|
177
|
+
*
|
|
178
|
+
* The arguments are based on the search implementation in `adev`.
|
|
179
|
+
*
|
|
180
|
+
* @param query The search query string.
|
|
181
|
+
* @returns The search arguments for the Algolia client.
|
|
182
|
+
*/
|
|
183
|
+
function createSearchArguments(query) {
|
|
184
|
+
// Search arguments are based on adev's search service:
|
|
185
|
+
// https://github.com/angular/angular/blob/4b614fbb3263d344dbb1b18fff24cb09c5a7582d/adev/shared-docs/services/search.service.ts#L58
|
|
186
|
+
return [
|
|
187
|
+
{
|
|
188
|
+
// TODO: Consider major version specific indices once available
|
|
189
|
+
indexName: 'angular_v17',
|
|
190
|
+
params: {
|
|
191
|
+
query,
|
|
192
|
+
attributesToRetrieve: [
|
|
193
|
+
'hierarchy.lvl0',
|
|
194
|
+
'hierarchy.lvl1',
|
|
195
|
+
'hierarchy.lvl2',
|
|
196
|
+
'hierarchy.lvl3',
|
|
197
|
+
'hierarchy.lvl4',
|
|
198
|
+
'hierarchy.lvl5',
|
|
199
|
+
'hierarchy.lvl6',
|
|
200
|
+
'content',
|
|
201
|
+
'type',
|
|
202
|
+
'url',
|
|
203
|
+
],
|
|
204
|
+
hitsPerPage: 10,
|
|
205
|
+
},
|
|
206
|
+
type: 'default',
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
}
|
|
@@ -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 type { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
|
|
9
|
+
import type { AngularWorkspace } from '../../../utilities/config';
|
|
10
|
+
export declare function registerListProjectsTool(server: McpServer, context: {
|
|
11
|
+
workspace?: AngularWorkspace;
|
|
12
|
+
}): void;
|
|
@@ -0,0 +1,85 @@
|
|
|
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.registerListProjectsTool = registerListProjectsTool;
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
const zod_1 = __importDefault(require("zod"));
|
|
16
|
+
function registerListProjectsTool(server, context) {
|
|
17
|
+
server.registerTool('list_projects', {
|
|
18
|
+
title: 'List Angular Projects',
|
|
19
|
+
description: 'Lists the names of all applications and libraries defined within an Angular workspace. ' +
|
|
20
|
+
'It reads the `angular.json` configuration file to identify the projects. ',
|
|
21
|
+
annotations: {
|
|
22
|
+
readOnlyHint: true,
|
|
23
|
+
openWorldHint: false,
|
|
24
|
+
},
|
|
25
|
+
outputSchema: {
|
|
26
|
+
projects: zod_1.default.array(zod_1.default.object({
|
|
27
|
+
name: zod_1.default
|
|
28
|
+
.string()
|
|
29
|
+
.describe('The name of the project, as defined in the `angular.json` file.'),
|
|
30
|
+
type: zod_1.default
|
|
31
|
+
.enum(['application', 'library'])
|
|
32
|
+
.optional()
|
|
33
|
+
.describe(`The type of the project, either 'application' or 'library'.`),
|
|
34
|
+
root: zod_1.default
|
|
35
|
+
.string()
|
|
36
|
+
.describe('The root directory of the project, relative to the workspace root.'),
|
|
37
|
+
sourceRoot: zod_1.default
|
|
38
|
+
.string()
|
|
39
|
+
.describe(`The root directory of the project's source files, relative to the workspace root.`),
|
|
40
|
+
selectorPrefix: zod_1.default
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('The prefix to use for component selectors.' +
|
|
44
|
+
` For example, a prefix of 'app' would result in selectors like '<app-my-component>'.`),
|
|
45
|
+
})),
|
|
46
|
+
},
|
|
47
|
+
}, async () => {
|
|
48
|
+
const { workspace } = context;
|
|
49
|
+
if (!workspace) {
|
|
50
|
+
return {
|
|
51
|
+
content: [
|
|
52
|
+
{
|
|
53
|
+
type: 'text',
|
|
54
|
+
text: 'No Angular workspace found.' +
|
|
55
|
+
' An `angular.json` file, which marks the root of a workspace,' +
|
|
56
|
+
' could not be located in the current directory or any of its parent directories.',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
structuredContent: { projects: [] },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const projects = [];
|
|
63
|
+
// Convert to output format
|
|
64
|
+
for (const [name, project] of workspace.projects.entries()) {
|
|
65
|
+
projects.push({
|
|
66
|
+
name,
|
|
67
|
+
type: project.extensions['projectType'],
|
|
68
|
+
root: project.root,
|
|
69
|
+
sourceRoot: project.sourceRoot ?? node_path_1.default.posix.join(project.root, 'src'),
|
|
70
|
+
selectorPrefix: project.extensions['prefix'],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// The structuredContent field is newer and may not be supported by all hosts.
|
|
74
|
+
// A text representation of the content is also provided for compatibility.
|
|
75
|
+
return {
|
|
76
|
+
content: [
|
|
77
|
+
{
|
|
78
|
+
type: 'text',
|
|
79
|
+
text: `Projects in the Angular workspace:\n${JSON.stringify(projects)}`,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
structuredContent: { projects },
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
package/src/utilities/version.js
CHANGED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# Angular Component Generation Prompt
|
|
2
|
-
|
|
3
|
-
## Role and Goal
|
|
4
|
-
|
|
5
|
-
You are an expert Angular developer specializing in creating modern, high-quality components. Your goal is to generate a new Angular component based on user requirements, ensuring it follows the latest best practices and style guidelines.
|
|
6
|
-
|
|
7
|
-
## Context
|
|
8
|
-
|
|
9
|
-
You are operating within an existing Angular CLI project. You have access to the project's file system and can use tools to interact with the Angular CLI for code generation.
|
|
10
|
-
|
|
11
|
-
## Instructions
|
|
12
|
-
|
|
13
|
-
Follow these steps to fulfill the user's request:
|
|
14
|
-
|
|
15
|
-
### 1. Analyze the User's Request
|
|
16
|
-
|
|
17
|
-
Carefully analyze the user's instructions to extract the following details:
|
|
18
|
-
|
|
19
|
-
- **Component Name:** The desired name for the component (e.g., 'UserProfile', 'LoginFormComponent'). Use this to form the file name and selector.
|
|
20
|
-
- **Inputs/Outputs:** Any `input()` or `output()` properties the component needs.
|
|
21
|
-
- **Component Logic:** The business logic, properties, and methods to be implemented in the component's class.
|
|
22
|
-
- **Template Structure:** The HTML structure for the component's template.
|
|
23
|
-
- **Styling:** Any CSS styles or style requirements for the component.
|
|
24
|
-
- **CLI Options:** Any specific options for the `ng generate` command, such as `--style=scss`, `--skip-tests=true`, `--view-encapsulation=Emulated`, etc.
|
|
25
|
-
|
|
26
|
-
### 2. Generate the Base Component
|
|
27
|
-
|
|
28
|
-
You MUST generate the initial component files using the Angular CLI. Use the following methods in order of preference:
|
|
29
|
-
|
|
30
|
-
**Preference 1: Use the `generate_component` tool (if available)**
|
|
31
|
-
|
|
32
|
-
- This is the preferred method.
|
|
33
|
-
- Use the details extracted from the user's request to provide the necessary parameters.
|
|
34
|
-
- Always generate a **standalone** component unless explicitly told otherwise.
|
|
35
|
-
|
|
36
|
-
**Preference 2: Use the `run_shell_command` tool**
|
|
37
|
-
|
|
38
|
-
- If a dedicated `generate_component` tool is not available, fall back to using the shell command.
|
|
39
|
-
- Construct the command like this: `ng generate component <component-name> [options...]`
|
|
40
|
-
- **Default Options:** Always include `--standalone` to create a standalone component.
|
|
41
|
-
- **User-Defined Options:** Add any other options derived from the user's request.
|
|
42
|
-
|
|
43
|
-
### 3. Read the Generated Files
|
|
44
|
-
|
|
45
|
-
After the CLI has generated the base files, use the `read_file` tool to get the content of the newly created:
|
|
46
|
-
- Component TypeScript file (`<component-name>.component.ts`)
|
|
47
|
-
- Component template file (`<component-name>.component.html`)
|
|
48
|
-
- Component stylesheet (`<component-name>.component.css`, etc.)
|
|
49
|
-
|
|
50
|
-
### 4. Modify the Code
|
|
51
|
-
|
|
52
|
-
Modify the code from the generated files to implement the specific logic, template, and styling from the user's instructions. Ensure the final code is complete, correct, and adheres to the latest Angular best practices.
|
|
53
|
-
|
|
54
|
-
- **Class:** Add the required properties, `input()`/`output()` functions, methods, and other logic to the component's class.
|
|
55
|
-
- **Template:** Build the HTML structure in the template file. Use native control flow (`@if`, `@for`, `@switch`) instead of structural directives (`*ngIf`, `*ngFor`).
|
|
56
|
-
- **Stylesheet:** Add the necessary styles to the stylesheet.
|
|
57
|
-
|
|
58
|
-
### 5. Final Output
|
|
59
|
-
|
|
60
|
-
Present the complete, final code for each modified file. Do not show diffs or partial code snippets. The user should be able to copy and paste the code directly into their project.
|