@angular/cli 21.0.0-next.2 → 21.0.0-next.3
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/lib/config/schema.json +1 -3
- package/package.json +14 -14
- package/src/commands/mcp/mcp-server.d.ts +0 -10
- package/src/commands/mcp/mcp-server.js +35 -8
- package/src/commands/mcp/tools/examples.d.ts +25 -0
- package/src/commands/mcp/tools/examples.js +268 -43
- package/src/utilities/package-manager.d.ts +12 -0
- package/src/utilities/package-manager.js +31 -22
- package/src/utilities/version.js +1 -1
package/lib/code-examples.db
CHANGED
|
Binary file
|
package/lib/config/schema.json
CHANGED
|
@@ -4342,7 +4342,6 @@
|
|
|
4342
4342
|
"items": {
|
|
4343
4343
|
"type": "string"
|
|
4344
4344
|
},
|
|
4345
|
-
"default": [],
|
|
4346
4345
|
"description": "Globs of files to exclude, relative to the project root."
|
|
4347
4346
|
},
|
|
4348
4347
|
"watch": {
|
|
@@ -4364,8 +4363,7 @@
|
|
|
4364
4363
|
"description": "Globs to exclude from code coverage.",
|
|
4365
4364
|
"items": {
|
|
4366
4365
|
"type": "string"
|
|
4367
|
-
}
|
|
4368
|
-
"default": []
|
|
4366
|
+
}
|
|
4369
4367
|
},
|
|
4370
4368
|
"codeCoverageReporters": {
|
|
4371
4369
|
"type": "array",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular/cli",
|
|
3
|
-
"version": "21.0.0-next.
|
|
3
|
+
"version": "21.0.0-next.3",
|
|
4
4
|
"description": "CLI tool for Angular",
|
|
5
5
|
"main": "lib/cli/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,20 +25,20 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/angular/angular-cli",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@angular-devkit/architect": "0.2100.0-next.
|
|
29
|
-
"@angular-devkit/core": "21.0.0-next.
|
|
30
|
-
"@angular-devkit/schematics": "21.0.0-next.
|
|
28
|
+
"@angular-devkit/architect": "0.2100.0-next.3",
|
|
29
|
+
"@angular-devkit/core": "21.0.0-next.3",
|
|
30
|
+
"@angular-devkit/schematics": "21.0.0-next.3",
|
|
31
31
|
"@inquirer/prompts": "7.8.4",
|
|
32
32
|
"@listr2/prompt-adapter-inquirer": "3.0.3",
|
|
33
33
|
"@modelcontextprotocol/sdk": "1.17.5",
|
|
34
|
-
"@schematics/angular": "21.0.0-next.
|
|
34
|
+
"@schematics/angular": "21.0.0-next.3",
|
|
35
35
|
"@yarnpkg/lockfile": "1.1.0",
|
|
36
36
|
"algoliasearch": "5.37.0",
|
|
37
37
|
"ini": "5.0.0",
|
|
38
38
|
"jsonc-parser": "3.3.1",
|
|
39
39
|
"listr2": "9.0.3",
|
|
40
40
|
"npm-package-arg": "13.0.0",
|
|
41
|
-
"pacote": "21.0.
|
|
41
|
+
"pacote": "21.0.1",
|
|
42
42
|
"resolve": "1.22.10",
|
|
43
43
|
"semver": "7.7.2",
|
|
44
44
|
"yargs": "18.0.0",
|
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
"ng-update": {
|
|
48
48
|
"migrations": "@schematics/angular/migrations/migration-collection.json",
|
|
49
49
|
"packageGroup": {
|
|
50
|
-
"@angular/cli": "21.0.0-next.
|
|
51
|
-
"@angular/build": "21.0.0-next.
|
|
52
|
-
"@angular/ssr": "21.0.0-next.
|
|
53
|
-
"@angular-devkit/architect": "0.2100.0-next.
|
|
54
|
-
"@angular-devkit/build-angular": "21.0.0-next.
|
|
55
|
-
"@angular-devkit/build-webpack": "0.2100.0-next.
|
|
56
|
-
"@angular-devkit/core": "21.0.0-next.
|
|
57
|
-
"@angular-devkit/schematics": "21.0.0-next.
|
|
50
|
+
"@angular/cli": "21.0.0-next.3",
|
|
51
|
+
"@angular/build": "21.0.0-next.3",
|
|
52
|
+
"@angular/ssr": "21.0.0-next.3",
|
|
53
|
+
"@angular-devkit/architect": "0.2100.0-next.3",
|
|
54
|
+
"@angular-devkit/build-angular": "21.0.0-next.3",
|
|
55
|
+
"@angular-devkit/build-webpack": "0.2100.0-next.3",
|
|
56
|
+
"@angular-devkit/core": "21.0.0-next.3",
|
|
57
|
+
"@angular-devkit/schematics": "21.0.0-next.3"
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"packageManager": "pnpm@10.15.1",
|
|
@@ -13,16 +13,6 @@ import { AnyMcpToolDeclaration } from './tools/tool-registry';
|
|
|
13
13
|
* These tools are considered experimental and may have limitations.
|
|
14
14
|
*/
|
|
15
15
|
export declare const EXPERIMENTAL_TOOLS: readonly [import("./tools/tool-registry").McpToolDeclaration<{
|
|
16
|
-
query: import("zod").ZodString;
|
|
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<{
|
|
26
16
|
transformations: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodEnum<[string, ...string[]]>, "many">>;
|
|
27
17
|
}, {
|
|
28
18
|
instructions: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
|
|
@@ -28,16 +28,17 @@ const tool_registry_1 = require("./tools/tool-registry");
|
|
|
28
28
|
* The set of tools that are enabled by default for the MCP server.
|
|
29
29
|
* These tools are considered stable and suitable for general use.
|
|
30
30
|
*/
|
|
31
|
-
const STABLE_TOOLS = [
|
|
31
|
+
const STABLE_TOOLS = [
|
|
32
|
+
best_practices_1.BEST_PRACTICES_TOOL,
|
|
33
|
+
doc_search_1.DOC_SEARCH_TOOL,
|
|
34
|
+
examples_1.FIND_EXAMPLE_TOOL,
|
|
35
|
+
projects_1.LIST_PROJECTS_TOOL,
|
|
36
|
+
];
|
|
32
37
|
/**
|
|
33
38
|
* The set of tools that are available but not enabled by default.
|
|
34
39
|
* These tools are considered experimental and may have limitations.
|
|
35
40
|
*/
|
|
36
|
-
exports.EXPERIMENTAL_TOOLS = [
|
|
37
|
-
examples_1.FIND_EXAMPLE_TOOL,
|
|
38
|
-
modernize_1.MODERNIZE_TOOL,
|
|
39
|
-
zoneless_migration_1.ZONELESS_MIGRATION_TOOL,
|
|
40
|
-
];
|
|
41
|
+
exports.EXPERIMENTAL_TOOLS = [modernize_1.MODERNIZE_TOOL, zoneless_migration_1.ZONELESS_MIGRATION_TOOL];
|
|
41
42
|
async function createMcpServer(options, logger) {
|
|
42
43
|
const server = new mcp_js_1.McpServer({
|
|
43
44
|
name: 'angular-cli-server',
|
|
@@ -48,8 +49,34 @@ async function createMcpServer(options, logger) {
|
|
|
48
49
|
tools: {},
|
|
49
50
|
logging: {},
|
|
50
51
|
},
|
|
51
|
-
instructions:
|
|
52
|
-
|
|
52
|
+
instructions: `
|
|
53
|
+
<General Purpose>
|
|
54
|
+
This server provides a safe, programmatic interface to the Angular CLI for an AI assistant.
|
|
55
|
+
Your primary goal is to use these tools to understand, analyze, refactor, and run Angular
|
|
56
|
+
projects. You MUST prefer the tools provided by this server over using \`run_shell_command\` for
|
|
57
|
+
equivalent actions.
|
|
58
|
+
</General Purpose>
|
|
59
|
+
|
|
60
|
+
<Core Workflows & Tool Guide>
|
|
61
|
+
* **1. Discover Project Structure (Mandatory First Step):** Always begin by calling
|
|
62
|
+
\`list_projects\` to understand the workspace. The outputs from this tool are often
|
|
63
|
+
required inputs for other tools.
|
|
64
|
+
|
|
65
|
+
* **2. Write & Modify Code:** Before writing or changing code, you MUST consult the
|
|
66
|
+
\`get_best_practices\` tool to learn the current, non-negotiable coding standards.
|
|
67
|
+
|
|
68
|
+
* **3. Answer User Questions:**
|
|
69
|
+
- For conceptual questions ("what is..."), use \`search_documentation\`.
|
|
70
|
+
- For code examples ("show me how to..."), use \`find_examples\`.
|
|
71
|
+
</Core Workflows & Tool Guide>
|
|
72
|
+
|
|
73
|
+
<Key Concepts>
|
|
74
|
+
* **Workspace vs. Project:** A 'workspace' contains an \`angular.json\` file and defines 'projects'
|
|
75
|
+
(applications or libraries). A monorepo can have multiple workspaces.
|
|
76
|
+
* **Targeting Projects:** Always use the \`workspaceConfigPath\` from \`list_projects\` when
|
|
77
|
+
available to ensure you are targeting the correct project in a monorepo.
|
|
78
|
+
</Key Concepts>
|
|
79
|
+
`,
|
|
53
80
|
});
|
|
54
81
|
(0, instructions_1.registerInstructionsResource)(server);
|
|
55
82
|
const toolDeclarations = assembleToolDeclarations(STABLE_TOOLS, exports.EXPERIMENTAL_TOOLS, {
|
|
@@ -8,13 +8,38 @@
|
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
export declare const FIND_EXAMPLE_TOOL: import("./tool-registry").McpToolDeclaration<{
|
|
10
10
|
query: z.ZodString;
|
|
11
|
+
keywords: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
12
|
+
required_packages: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
13
|
+
related_concepts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
14
|
+
includeExperimental: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
11
15
|
}, {
|
|
12
16
|
examples: z.ZodArray<z.ZodObject<{
|
|
17
|
+
title: z.ZodString;
|
|
18
|
+
summary: z.ZodString;
|
|
19
|
+
keywords: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
20
|
+
required_packages: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
21
|
+
related_concepts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
22
|
+
related_tools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
13
23
|
content: z.ZodString;
|
|
24
|
+
snippet: z.ZodOptional<z.ZodString>;
|
|
14
25
|
}, "strip", z.ZodTypeAny, {
|
|
26
|
+
title: string;
|
|
15
27
|
content: string;
|
|
28
|
+
summary: string;
|
|
29
|
+
keywords?: string[] | undefined;
|
|
30
|
+
required_packages?: string[] | undefined;
|
|
31
|
+
related_concepts?: string[] | undefined;
|
|
32
|
+
related_tools?: string[] | undefined;
|
|
33
|
+
snippet?: string | undefined;
|
|
16
34
|
}, {
|
|
35
|
+
title: string;
|
|
17
36
|
content: string;
|
|
37
|
+
summary: string;
|
|
38
|
+
keywords?: string[] | undefined;
|
|
39
|
+
required_packages?: string[] | undefined;
|
|
40
|
+
related_concepts?: string[] | undefined;
|
|
41
|
+
related_tools?: string[] | undefined;
|
|
42
|
+
snippet?: string | undefined;
|
|
18
43
|
}>, "many">;
|
|
19
44
|
}>;
|
|
20
45
|
/**
|
|
@@ -50,28 +50,92 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
50
50
|
const zod_1 = require("zod");
|
|
51
51
|
const tool_registry_1 = require("./tool-registry");
|
|
52
52
|
const findExampleInputSchema = zod_1.z.object({
|
|
53
|
-
query: zod_1.z
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
53
|
+
query: zod_1.z
|
|
54
|
+
.string()
|
|
55
|
+
.describe(`The primary, conceptual search query. This should capture the user's main goal or question ` +
|
|
56
|
+
`(e.g., 'lazy loading a route' or 'how to use signal inputs'). The query will be processed ` +
|
|
57
|
+
'by a powerful full-text search engine.\n\n' +
|
|
58
|
+
'Key Syntax Features (see https://www.sqlite.org/fts5.html for full documentation):\n' +
|
|
59
|
+
' - AND (default): Space-separated terms are combined with AND.\n' +
|
|
60
|
+
' - Example: \'standalone component\' (finds results with both "standalone" and "component")\n' +
|
|
61
|
+
' - OR: Use the OR operator to find results with either term.\n' +
|
|
62
|
+
" - Example: 'validation OR validator'\n" +
|
|
63
|
+
' - NOT: Use the NOT operator to exclude terms.\n' +
|
|
64
|
+
" - Example: 'forms NOT reactive'\n" +
|
|
65
|
+
' - Grouping: Use parentheses () to group expressions.\n' +
|
|
66
|
+
" - Example: '(validation OR validator) AND forms'\n" +
|
|
67
|
+
' - Phrase Search: Use double quotes "" for exact phrases.\n' +
|
|
68
|
+
' - Example: \'"template-driven forms"\'\n' +
|
|
69
|
+
' - Prefix Search: Use an asterisk * for prefix matching.\n' +
|
|
70
|
+
' - Example: \'rout*\' (matches "route", "router", "routing")'),
|
|
71
|
+
keywords: zod_1.z
|
|
72
|
+
.array(zod_1.z.string())
|
|
73
|
+
.optional()
|
|
74
|
+
.describe('A list of specific, exact keywords to narrow the search. Use this for precise terms like ' +
|
|
75
|
+
'API names, function names, or decorators (e.g., `ngFor`, `trackBy`, `inject`).'),
|
|
76
|
+
required_packages: zod_1.z
|
|
77
|
+
.array(zod_1.z.string())
|
|
78
|
+
.optional()
|
|
79
|
+
.describe("A list of NPM packages that an example must use. Use this when the user's request is " +
|
|
80
|
+
'specific to a feature within a certain package (e.g., if the user asks about `ngModel`, ' +
|
|
81
|
+
'you should filter by `@angular/forms`).'),
|
|
82
|
+
related_concepts: zod_1.z
|
|
83
|
+
.array(zod_1.z.string())
|
|
84
|
+
.optional()
|
|
85
|
+
.describe('A list of high-level concepts to filter by. Use this to find examples related to broader ' +
|
|
86
|
+
'architectural ideas or patterns (e.g., `signals`, `dependency injection`, `routing`).'),
|
|
87
|
+
includeExperimental: zod_1.z
|
|
88
|
+
.boolean()
|
|
89
|
+
.optional()
|
|
90
|
+
.default(false)
|
|
91
|
+
.describe('By default, this tool returns only production-safe examples. Set this to `true` **only if** ' +
|
|
92
|
+
'the user explicitly asks for a bleeding-edge feature or if a stable solution to their ' +
|
|
93
|
+
'problem cannot be found. If you set this to `true`, you **MUST** preface your answer by ' +
|
|
94
|
+
'warning the user that the example uses experimental APIs that are not suitable for production.'),
|
|
95
|
+
});
|
|
96
|
+
const findExampleOutputSchema = zod_1.z.object({
|
|
97
|
+
examples: zod_1.z.array(zod_1.z.object({
|
|
98
|
+
title: zod_1.z
|
|
99
|
+
.string()
|
|
100
|
+
.describe('The title of the example. Use this as a heading when presenting the example to the user.'),
|
|
101
|
+
summary: zod_1.z
|
|
102
|
+
.string()
|
|
103
|
+
.describe("A one-sentence summary of the example's purpose. Use this to help the user decide " +
|
|
104
|
+
'if the example is relevant to them.'),
|
|
105
|
+
keywords: zod_1.z
|
|
106
|
+
.array(zod_1.z.string())
|
|
107
|
+
.optional()
|
|
108
|
+
.describe('A list of keywords for the example. You can use these to explain why this example ' +
|
|
109
|
+
"was a good match for the user's query."),
|
|
110
|
+
required_packages: zod_1.z
|
|
111
|
+
.array(zod_1.z.string())
|
|
112
|
+
.optional()
|
|
113
|
+
.describe('A list of NPM packages required for the example to work. Before presenting the code, ' +
|
|
114
|
+
'you should inform the user if any of these packages need to be installed.'),
|
|
115
|
+
related_concepts: zod_1.z
|
|
116
|
+
.array(zod_1.z.string())
|
|
117
|
+
.optional()
|
|
118
|
+
.describe('A list of related concepts. You can suggest these to the user as topics for ' +
|
|
119
|
+
'follow-up questions.'),
|
|
120
|
+
related_tools: zod_1.z
|
|
121
|
+
.array(zod_1.z.string())
|
|
122
|
+
.optional()
|
|
123
|
+
.describe('A list of related MCP tools. You can suggest these as potential next steps for the user.'),
|
|
124
|
+
content: zod_1.z
|
|
125
|
+
.string()
|
|
126
|
+
.describe('A complete, self-contained Angular code example in Markdown format. This should be ' +
|
|
127
|
+
'presented to the user inside a markdown code block.'),
|
|
128
|
+
snippet: zod_1.z
|
|
129
|
+
.string()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe('A contextual snippet from the content showing the matched search term. This field is ' +
|
|
132
|
+
'critical for efficiently evaluating a result`s relevance. It enables two primary ' +
|
|
133
|
+
'workflows:\n\n' +
|
|
134
|
+
'1. For direct questions: You can internally review snippets to select the single best ' +
|
|
135
|
+
'result before generating a comprehensive answer from its full `content`.\n' +
|
|
136
|
+
'2. For ambiguous or exploratory questions: You can present a summary of titles and ' +
|
|
137
|
+
'snippets to the user, allowing them to guide the next step.'),
|
|
138
|
+
})),
|
|
75
139
|
});
|
|
76
140
|
exports.FIND_EXAMPLE_TOOL = (0, tool_registry_1.declareTool)({
|
|
77
141
|
name: 'find_examples',
|
|
@@ -89,7 +153,9 @@ new or evolving features.
|
|
|
89
153
|
* **Modern Implementation:** Finding the correct modern syntax for features
|
|
90
154
|
(e.g., query: 'functional route guard' or 'http client with fetch').
|
|
91
155
|
* **Refactoring to Modern Patterns:** Upgrading older code by finding examples of new syntax
|
|
92
|
-
(e.g., query: 'built-in control flow' to replace "*ngIf
|
|
156
|
+
(e.g., query: 'built-in control flow' to replace "*ngIf").
|
|
157
|
+
* **Advanced Filtering:** Combining a full-text search with filters to narrow results.
|
|
158
|
+
(e.g., query: 'forms', required_packages: ['@angular/forms'], keywords: ['validation'])
|
|
93
159
|
</Use Cases>
|
|
94
160
|
<Operational Notes>
|
|
95
161
|
* **Tool Selection:** This database primarily contains examples for new and recently updated Angular
|
|
@@ -98,15 +164,11 @@ new or evolving features.
|
|
|
98
164
|
* The examples in this database are the single source of truth for modern Angular coding patterns.
|
|
99
165
|
* The search query uses a powerful full-text search syntax (FTS5). Refer to the 'query'
|
|
100
166
|
parameter description for detailed syntax rules and examples.
|
|
167
|
+
* You can combine the main 'query' with optional filters like 'keywords', 'required_packages',
|
|
168
|
+
and 'related_concepts' to create highly specific searches.
|
|
101
169
|
</Operational Notes>`,
|
|
102
170
|
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
|
-
},
|
|
171
|
+
outputSchema: findExampleOutputSchema.shape,
|
|
110
172
|
isReadOnly: true,
|
|
111
173
|
isLocalOnly: true,
|
|
112
174
|
shouldRegister: ({ logger }) => {
|
|
@@ -128,7 +190,7 @@ async function createFindExampleHandler({ exampleDatabasePath }) {
|
|
|
128
190
|
db = await setupRuntimeExamples(process.env['NG_MCP_EXAMPLES_DIR']);
|
|
129
191
|
}
|
|
130
192
|
suppressSqliteWarning();
|
|
131
|
-
return async (
|
|
193
|
+
return async (input) => {
|
|
132
194
|
if (!db) {
|
|
133
195
|
if (!exampleDatabasePath) {
|
|
134
196
|
// This should be prevented by the registration logic in mcp-server.ts
|
|
@@ -137,17 +199,67 @@ async function createFindExampleHandler({ exampleDatabasePath }) {
|
|
|
137
199
|
const { DatabaseSync } = await Promise.resolve().then(() => __importStar(require('node:sqlite')));
|
|
138
200
|
db = new DatabaseSync(exampleDatabasePath, { readOnly: true });
|
|
139
201
|
}
|
|
140
|
-
|
|
141
|
-
|
|
202
|
+
const { query, keywords, required_packages, related_concepts, includeExperimental } = input;
|
|
203
|
+
// Build the query dynamically
|
|
204
|
+
const params = [];
|
|
205
|
+
let sql = 'SELECT title, summary, keywords, required_packages, related_concepts, related_tools, content, ' +
|
|
206
|
+
// The `snippet` function generates a contextual snippet of the matched text.
|
|
207
|
+
// Column 6 is the `content` column. We highlight matches with asterisks and limit the snippet size.
|
|
208
|
+
"snippet(examples_fts, 6, '**', '**', '...', 15) AS snippet " +
|
|
209
|
+
'FROM examples_fts';
|
|
210
|
+
const whereClauses = [];
|
|
211
|
+
// FTS query
|
|
212
|
+
if (query) {
|
|
213
|
+
whereClauses.push('examples_fts MATCH ?');
|
|
214
|
+
params.push(escapeSearchQuery(query));
|
|
215
|
+
}
|
|
216
|
+
// JSON array filters
|
|
217
|
+
const addJsonFilter = (column, values) => {
|
|
218
|
+
if (values?.length) {
|
|
219
|
+
for (const value of values) {
|
|
220
|
+
whereClauses.push(`${column} LIKE ?`);
|
|
221
|
+
params.push(`%"${value}"%`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
addJsonFilter('keywords', keywords);
|
|
226
|
+
addJsonFilter('required_packages', required_packages);
|
|
227
|
+
addJsonFilter('related_concepts', related_concepts);
|
|
228
|
+
if (!includeExperimental) {
|
|
229
|
+
whereClauses.push('experimental = 0');
|
|
230
|
+
}
|
|
231
|
+
if (whereClauses.length > 0) {
|
|
232
|
+
sql += ` WHERE ${whereClauses.join(' AND ')}`;
|
|
142
233
|
}
|
|
143
|
-
|
|
234
|
+
// Order the results by relevance using the BM25 algorithm.
|
|
235
|
+
// The weights assigned to each column boost the ranking of documents where the
|
|
236
|
+
// search term appears in a more important field.
|
|
237
|
+
// Column order: title, summary, keywords, required_packages, related_concepts, related_tools, content
|
|
238
|
+
sql += ' ORDER BY bm25(examples_fts, 10.0, 5.0, 5.0, 1.0, 2.0, 1.0, 1.0);';
|
|
239
|
+
const queryStatement = db.prepare(sql);
|
|
144
240
|
// Query database and return results
|
|
145
241
|
const examples = [];
|
|
146
242
|
const textContent = [];
|
|
147
|
-
for (const exampleRecord of queryStatement.all(
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
243
|
+
for (const exampleRecord of queryStatement.all(...params)) {
|
|
244
|
+
const record = exampleRecord;
|
|
245
|
+
const example = {
|
|
246
|
+
title: record['title'],
|
|
247
|
+
summary: record['summary'],
|
|
248
|
+
keywords: JSON.parse(record['keywords'] || '[]'),
|
|
249
|
+
required_packages: JSON.parse(record['required_packages'] || '[]'),
|
|
250
|
+
related_concepts: JSON.parse(record['related_concepts'] || '[]'),
|
|
251
|
+
related_tools: JSON.parse(record['related_tools'] || '[]'),
|
|
252
|
+
content: record['content'],
|
|
253
|
+
snippet: record['snippet'],
|
|
254
|
+
};
|
|
255
|
+
examples.push(example);
|
|
256
|
+
// Also create a more structured text output
|
|
257
|
+
let text = `## Example: ${example.title}\n**Summary:** ${example.summary}`;
|
|
258
|
+
if (example.snippet) {
|
|
259
|
+
text += `\n**Snippet:** ${example.snippet}`;
|
|
260
|
+
}
|
|
261
|
+
text += `\n\n---\n\n${example.content}`;
|
|
262
|
+
textContent.push({ type: 'text', text });
|
|
151
263
|
}
|
|
152
264
|
return {
|
|
153
265
|
content: textContent,
|
|
@@ -232,18 +344,131 @@ function suppressSqliteWarning() {
|
|
|
232
344
|
return originalProcessEmit.apply(process, arguments);
|
|
233
345
|
};
|
|
234
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* A simple YAML front matter parser.
|
|
349
|
+
*
|
|
350
|
+
* This function extracts the YAML block enclosed by `---` at the beginning of a string
|
|
351
|
+
* and parses it into a JavaScript object. It is not a full YAML parser and only
|
|
352
|
+
* supports simple key-value pairs and string arrays.
|
|
353
|
+
*
|
|
354
|
+
* @param content The string content to parse.
|
|
355
|
+
* @returns A record containing the parsed front matter data.
|
|
356
|
+
*/
|
|
357
|
+
function parseFrontmatter(content) {
|
|
358
|
+
const match = content.match(/^---\r?\n(.*?)\r?\n---/s);
|
|
359
|
+
if (!match) {
|
|
360
|
+
return {};
|
|
361
|
+
}
|
|
362
|
+
const frontmatter = match[1];
|
|
363
|
+
const data = {};
|
|
364
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
365
|
+
let currentKey = '';
|
|
366
|
+
let isArray = false;
|
|
367
|
+
const arrayValues = [];
|
|
368
|
+
for (const line of lines) {
|
|
369
|
+
const keyValueMatch = line.match(/^([^:]+):\s*(.*)/);
|
|
370
|
+
if (keyValueMatch) {
|
|
371
|
+
if (currentKey && isArray) {
|
|
372
|
+
data[currentKey] = arrayValues.slice();
|
|
373
|
+
arrayValues.length = 0;
|
|
374
|
+
}
|
|
375
|
+
const [, key, value] = keyValueMatch;
|
|
376
|
+
currentKey = key.trim();
|
|
377
|
+
isArray = value.trim() === '';
|
|
378
|
+
if (!isArray) {
|
|
379
|
+
const trimmedValue = value.trim();
|
|
380
|
+
if (trimmedValue === 'true') {
|
|
381
|
+
data[currentKey] = true;
|
|
382
|
+
}
|
|
383
|
+
else if (trimmedValue === 'false') {
|
|
384
|
+
data[currentKey] = false;
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
data[currentKey] = trimmedValue;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
const arrayItemMatch = line.match(/^\s*-\s*(.*)/);
|
|
393
|
+
if (arrayItemMatch && currentKey && isArray) {
|
|
394
|
+
arrayValues.push(arrayItemMatch[1].trim());
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (currentKey && isArray) {
|
|
399
|
+
data[currentKey] = arrayValues;
|
|
400
|
+
}
|
|
401
|
+
return data;
|
|
402
|
+
}
|
|
235
403
|
async function setupRuntimeExamples(examplesPath) {
|
|
236
404
|
const { DatabaseSync } = await Promise.resolve().then(() => __importStar(require('node:sqlite')));
|
|
237
405
|
const db = new DatabaseSync(':memory:');
|
|
238
|
-
|
|
239
|
-
|
|
406
|
+
// Create a relational table to store the structured example data.
|
|
407
|
+
db.exec(`
|
|
408
|
+
CREATE TABLE examples (
|
|
409
|
+
id INTEGER PRIMARY KEY,
|
|
410
|
+
title TEXT NOT NULL,
|
|
411
|
+
summary TEXT NOT NULL,
|
|
412
|
+
keywords TEXT,
|
|
413
|
+
required_packages TEXT,
|
|
414
|
+
related_concepts TEXT,
|
|
415
|
+
related_tools TEXT,
|
|
416
|
+
experimental INTEGER NOT NULL DEFAULT 0,
|
|
417
|
+
content TEXT NOT NULL
|
|
418
|
+
);
|
|
419
|
+
`);
|
|
420
|
+
// Create an FTS5 virtual table to provide full-text search capabilities.
|
|
421
|
+
db.exec(`
|
|
422
|
+
CREATE VIRTUAL TABLE examples_fts USING fts5(
|
|
423
|
+
title,
|
|
424
|
+
summary,
|
|
425
|
+
keywords,
|
|
426
|
+
required_packages,
|
|
427
|
+
related_concepts,
|
|
428
|
+
related_tools,
|
|
429
|
+
content,
|
|
430
|
+
content='examples',
|
|
431
|
+
content_rowid='id',
|
|
432
|
+
tokenize = 'porter ascii'
|
|
433
|
+
);
|
|
434
|
+
`);
|
|
435
|
+
// Create triggers to keep the FTS table synchronized with the examples table.
|
|
436
|
+
db.exec(`
|
|
437
|
+
CREATE TRIGGER examples_after_insert AFTER INSERT ON examples BEGIN
|
|
438
|
+
INSERT INTO examples_fts(rowid, title, summary, keywords, required_packages, related_concepts, related_tools, content)
|
|
439
|
+
VALUES (
|
|
440
|
+
new.id, new.title, new.summary, new.keywords, new.required_packages, new.related_concepts,
|
|
441
|
+
new.related_tools, new.content
|
|
442
|
+
);
|
|
443
|
+
END;
|
|
444
|
+
`);
|
|
445
|
+
const insertStatement = db.prepare('INSERT INTO examples(' +
|
|
446
|
+
'title, summary, keywords, required_packages, related_concepts, related_tools, experimental, content' +
|
|
447
|
+
') VALUES(?, ?, ?, ?, ?, ?, ?, ?);');
|
|
448
|
+
const frontmatterSchema = zod_1.z.object({
|
|
449
|
+
title: zod_1.z.string(),
|
|
450
|
+
summary: zod_1.z.string(),
|
|
451
|
+
keywords: zod_1.z.array(zod_1.z.string()).optional(),
|
|
452
|
+
required_packages: zod_1.z.array(zod_1.z.string()).optional(),
|
|
453
|
+
related_concepts: zod_1.z.array(zod_1.z.string()).optional(),
|
|
454
|
+
related_tools: zod_1.z.array(zod_1.z.string()).optional(),
|
|
455
|
+
experimental: zod_1.z.boolean().optional(),
|
|
456
|
+
});
|
|
240
457
|
db.exec('BEGIN TRANSACTION');
|
|
241
|
-
for await (const entry of (0, promises_1.glob)('
|
|
458
|
+
for await (const entry of (0, promises_1.glob)('**/*.md', { cwd: examplesPath, withFileTypes: true })) {
|
|
242
459
|
if (!entry.isFile()) {
|
|
243
460
|
continue;
|
|
244
461
|
}
|
|
245
|
-
const
|
|
246
|
-
|
|
462
|
+
const content = await (0, promises_1.readFile)(node_path_1.default.join(entry.parentPath, entry.name), 'utf-8');
|
|
463
|
+
const frontmatter = parseFrontmatter(content);
|
|
464
|
+
const validation = frontmatterSchema.safeParse(frontmatter);
|
|
465
|
+
if (!validation.success) {
|
|
466
|
+
// eslint-disable-next-line no-console
|
|
467
|
+
console.warn(`Skipping invalid example file ${entry.name}:`, validation.error.issues);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
const { title, summary, keywords, required_packages, related_concepts, related_tools, experimental, } = validation.data;
|
|
471
|
+
insertStatement.run(title, summary, JSON.stringify(keywords ?? []), JSON.stringify(required_packages ?? []), JSON.stringify(related_concepts ?? []), JSON.stringify(related_tools ?? []), experimental ? 1 : 0, content);
|
|
247
472
|
}
|
|
248
473
|
db.exec('END TRANSACTION');
|
|
249
474
|
return db;
|
|
@@ -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