@angular/cli 21.0.2 → 21.1.0-next.1

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 (102) hide show
  1. package/lib/code-examples.db +0 -0
  2. package/package.json +18 -18
  3. package/src/commands/mcp/cli.d.ts +2 -2
  4. package/src/commands/mcp/cli.js.map +1 -1
  5. package/src/commands/mcp/dev-server.d.ts +71 -0
  6. package/src/commands/mcp/dev-server.js +96 -0
  7. package/src/commands/mcp/dev-server.js.map +1 -0
  8. package/src/commands/mcp/host.d.ts +47 -5
  9. package/src/commands/mcp/host.js +52 -13
  10. package/src/commands/mcp/host.js.map +1 -1
  11. package/src/commands/mcp/mcp-server.d.ts +35 -4
  12. package/src/commands/mcp/mcp-server.js +16 -8
  13. package/src/commands/mcp/mcp-server.js.map +1 -1
  14. package/src/commands/mcp/resources/ai-tutor.md +199 -3
  15. package/src/commands/mcp/resources/instructions.d.ts +1 -1
  16. package/src/commands/mcp/resources/instructions.js +2 -5
  17. package/src/commands/mcp/resources/instructions.js.map +1 -1
  18. package/src/commands/mcp/tools/ai-tutor.js +2 -5
  19. package/src/commands/mcp/tools/ai-tutor.js.map +1 -1
  20. package/src/commands/mcp/tools/best-practices.js +6 -9
  21. package/src/commands/mcp/tools/best-practices.js.map +1 -1
  22. package/src/commands/mcp/tools/build.d.ts +37 -0
  23. package/src/commands/mcp/tools/build.js +97 -0
  24. package/src/commands/mcp/tools/build.js.map +1 -0
  25. package/src/commands/mcp/tools/devserver/start-devserver.d.ts +31 -0
  26. package/src/commands/mcp/tools/devserver/start-devserver.js +82 -0
  27. package/src/commands/mcp/tools/devserver/start-devserver.js.map +1 -0
  28. package/src/commands/mcp/tools/devserver/stop-devserver.d.ts +39 -0
  29. package/src/commands/mcp/tools/devserver/stop-devserver.js +66 -0
  30. package/src/commands/mcp/tools/devserver/stop-devserver.js.map +1 -0
  31. package/src/commands/mcp/tools/devserver/wait-for-devserver-build.d.ts +41 -0
  32. package/src/commands/mcp/tools/devserver/wait-for-devserver-build.js +100 -0
  33. package/src/commands/mcp/tools/devserver/wait-for-devserver-build.js.map +1 -0
  34. package/src/commands/mcp/tools/doc-search.js.map +1 -1
  35. package/src/commands/mcp/tools/examples/database-discovery.d.ts +34 -0
  36. package/src/commands/mcp/tools/examples/database-discovery.js +87 -0
  37. package/src/commands/mcp/tools/examples/database-discovery.js.map +1 -0
  38. package/src/commands/mcp/tools/examples/database.d.ts +35 -0
  39. package/src/commands/mcp/tools/examples/database.js +125 -0
  40. package/src/commands/mcp/tools/examples/database.js.map +1 -0
  41. package/src/commands/mcp/tools/examples/index.d.ts +26 -0
  42. package/src/commands/mcp/tools/examples/index.js +149 -0
  43. package/src/commands/mcp/tools/examples/index.js.map +1 -0
  44. package/src/commands/mcp/tools/examples/query-escaper.d.ts +20 -0
  45. package/src/commands/mcp/tools/examples/query-escaper.js +68 -0
  46. package/src/commands/mcp/tools/examples/query-escaper.js.map +1 -0
  47. package/src/commands/mcp/tools/examples/runtime-database.d.ts +10 -0
  48. package/src/commands/mcp/tools/examples/runtime-database.js +192 -0
  49. package/src/commands/mcp/tools/examples/runtime-database.js.map +1 -0
  50. package/src/commands/mcp/tools/{examples.d.ts → examples/schemas.d.ts} +5 -16
  51. package/src/commands/mcp/tools/examples/schemas.js +101 -0
  52. package/src/commands/mcp/tools/examples/schemas.js.map +1 -0
  53. package/src/commands/mcp/tools/examples/utils.d.ts +14 -0
  54. package/src/commands/mcp/tools/examples/utils.js +31 -0
  55. package/src/commands/mcp/tools/examples/utils.js.map +1 -0
  56. package/src/commands/mcp/tools/modernize.d.ts +13 -7
  57. package/src/commands/mcp/tools/modernize.js +13 -45
  58. package/src/commands/mcp/tools/modernize.js.map +1 -1
  59. package/src/commands/mcp/tools/onpush-zoneless-migration/{analyze_for_unsupported_zone_uses.d.ts → analyze-for-unsupported-zone-uses.d.ts} +1 -1
  60. package/src/commands/mcp/tools/onpush-zoneless-migration/{analyze_for_unsupported_zone_uses.js → analyze-for-unsupported-zone-uses.js} +2 -2
  61. package/src/commands/mcp/tools/onpush-zoneless-migration/{analyze_for_unsupported_zone_uses.js.map → analyze-for-unsupported-zone-uses.js.map} +1 -1
  62. package/src/commands/mcp/tools/onpush-zoneless-migration/{migrate_single_file.d.ts → migrate-single-file.d.ts} +3 -3
  63. package/src/commands/mcp/tools/onpush-zoneless-migration/{migrate_single_file.js → migrate-single-file.js} +5 -5
  64. package/src/commands/mcp/tools/onpush-zoneless-migration/{migrate_single_file.js.map → migrate-single-file.js.map} +1 -1
  65. package/src/commands/mcp/tools/onpush-zoneless-migration/{migrate_test_file.js → migrate-test-file.js} +6 -52
  66. package/src/commands/mcp/tools/onpush-zoneless-migration/migrate-test-file.js.map +1 -0
  67. package/src/commands/mcp/tools/onpush-zoneless-migration/prompts.d.ts +1 -1
  68. package/src/commands/mcp/tools/onpush-zoneless-migration/prompts.js +2 -2
  69. package/src/commands/mcp/tools/onpush-zoneless-migration/{send_debug_message.d.ts → send-debug-message.d.ts} +2 -2
  70. package/src/commands/mcp/tools/onpush-zoneless-migration/{send_debug_message.js → send-debug-message.js} +1 -1
  71. package/src/commands/mcp/tools/onpush-zoneless-migration/{send_debug_message.js.map → send-debug-message.js.map} +1 -1
  72. package/src/commands/mcp/tools/onpush-zoneless-migration/{ts_utils.d.ts → ts-utils.d.ts} +3 -4
  73. package/src/commands/mcp/tools/onpush-zoneless-migration/{ts_utils.js → ts-utils.js} +3 -3
  74. package/src/commands/mcp/tools/onpush-zoneless-migration/{ts_utils.js.map → ts-utils.js.map} +1 -1
  75. package/src/commands/mcp/tools/onpush-zoneless-migration/zoneless-migration.d.ts +2 -2
  76. package/src/commands/mcp/tools/onpush-zoneless-migration/zoneless-migration.js +67 -71
  77. package/src/commands/mcp/tools/onpush-zoneless-migration/zoneless-migration.js.map +1 -1
  78. package/src/commands/mcp/tools/projects.d.ts +1 -1
  79. package/src/commands/mcp/tools/projects.js +33 -33
  80. package/src/commands/mcp/tools/projects.js.map +1 -1
  81. package/src/commands/mcp/tools/tool-registry.d.ts +4 -0
  82. package/src/commands/mcp/tools/tool-registry.js.map +1 -1
  83. package/src/commands/mcp/utils.d.ts +33 -0
  84. package/src/commands/mcp/utils.js +55 -0
  85. package/src/commands/mcp/utils.js.map +1 -0
  86. package/src/package-managers/host.d.ts +6 -0
  87. package/src/package-managers/host.js +1 -0
  88. package/src/package-managers/host.js.map +1 -1
  89. package/src/package-managers/package-manager-descriptor.d.ts +6 -6
  90. package/src/package-managers/package-manager-descriptor.js +5 -5
  91. package/src/package-managers/package-manager-descriptor.js.map +1 -1
  92. package/src/package-managers/package-manager.d.ts +21 -3
  93. package/src/package-managers/package-manager.js +74 -5
  94. package/src/package-managers/package-manager.js.map +1 -1
  95. package/src/utilities/version.js +1 -1
  96. package/src/commands/mcp/tools/examples.js +0 -657
  97. package/src/commands/mcp/tools/examples.js.map +0 -1
  98. package/src/commands/mcp/tools/onpush-zoneless-migration/migrate_test_file.js.map +0 -1
  99. package/src/package-managers/testing/mock-host.d.ts +0 -26
  100. package/src/package-managers/testing/mock-host.js +0 -53
  101. package/src/package-managers/testing/mock-host.js.map +0 -1
  102. /package/src/commands/mcp/tools/onpush-zoneless-migration/{migrate_test_file.d.ts → migrate-test-file.d.ts} +0 -0
@@ -1,657 +0,0 @@
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
- var __importDefault = (this && this.__importDefault) || function (mod) {
43
- return (mod && mod.__esModule) ? mod : { "default": mod };
44
- };
45
- Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.FIND_EXAMPLE_TOOL = void 0;
47
- exports.escapeSearchQuery = escapeSearchQuery;
48
- const promises_1 = require("node:fs/promises");
49
- const node_module_1 = require("node:module");
50
- const node_path_1 = __importDefault(require("node:path"));
51
- const zod_1 = require("zod");
52
- const tool_registry_1 = require("./tool-registry");
53
- const findExampleInputSchema = zod_1.z.object({
54
- workspacePath: zod_1.z
55
- .string()
56
- .optional()
57
- .describe('The absolute path to the `angular.json` file for the workspace. This is used to find the ' +
58
- 'version-specific code examples that correspond to the installed version of the ' +
59
- 'Angular framework. You **MUST** get this path from the `list_projects` tool. ' +
60
- 'If omitted, the tool will search the generic code examples bundled with the CLI.'),
61
- query: zod_1.z
62
- .string()
63
- .describe(`The primary, conceptual search query. This should capture the user's main goal or question ` +
64
- `(e.g., 'lazy loading a route' or 'how to use signal inputs'). The query will be processed ` +
65
- 'by a powerful full-text search engine.\n\n' +
66
- 'Key Syntax Features (see https://www.sqlite.org/fts5.html for full documentation):\n' +
67
- ' - AND (default): Space-separated terms are combined with AND.\n' +
68
- ' - Example: \'standalone component\' (finds results with both "standalone" and "component")\n' +
69
- ' - OR: Use the OR operator to find results with either term.\n' +
70
- " - Example: 'validation OR validator'\n" +
71
- ' - NOT: Use the NOT operator to exclude terms.\n' +
72
- " - Example: 'forms NOT reactive'\n" +
73
- ' - Grouping: Use parentheses () to group expressions.\n' +
74
- " - Example: '(validation OR validator) AND forms'\n" +
75
- ' - Phrase Search: Use double quotes "" for exact phrases.\n' +
76
- ' - Example: \'"template-driven forms"\'\n' +
77
- ' - Prefix Search: Use an asterisk * for prefix matching.\n' +
78
- ' - Example: \'rout*\' (matches "route", "router", "routing")'),
79
- keywords: zod_1.z
80
- .array(zod_1.z.string())
81
- .optional()
82
- .describe('A list of specific, exact keywords to narrow the search. Use this for precise terms like ' +
83
- 'API names, function names, or decorators (e.g., `ngFor`, `trackBy`, `inject`).'),
84
- required_packages: zod_1.z
85
- .array(zod_1.z.string())
86
- .optional()
87
- .describe("A list of NPM packages that an example must use. Use this when the user's request is " +
88
- 'specific to a feature within a certain package (e.g., if the user asks about `ngModel`, ' +
89
- 'you should filter by `@angular/forms`).'),
90
- related_concepts: zod_1.z
91
- .array(zod_1.z.string())
92
- .optional()
93
- .describe('A list of high-level concepts to filter by. Use this to find examples related to broader ' +
94
- 'architectural ideas or patterns (e.g., `signals`, `dependency injection`, `routing`).'),
95
- includeExperimental: zod_1.z
96
- .boolean()
97
- .optional()
98
- .default(false)
99
- .describe('By default, this tool returns only production-safe examples. Set this to `true` **only if** ' +
100
- 'the user explicitly asks for a bleeding-edge feature or if a stable solution to their ' +
101
- 'problem cannot be found. If you set this to `true`, you **MUST** preface your answer by ' +
102
- 'warning the user that the example uses experimental APIs that are not suitable for production.'),
103
- });
104
- const findExampleOutputSchema = zod_1.z.object({
105
- examples: zod_1.z.array(zod_1.z.object({
106
- title: zod_1.z
107
- .string()
108
- .describe('The title of the example. Use this as a heading when presenting the example to the user.'),
109
- summary: zod_1.z
110
- .string()
111
- .describe("A one-sentence summary of the example's purpose. Use this to help the user decide " +
112
- 'if the example is relevant to them.'),
113
- keywords: zod_1.z
114
- .array(zod_1.z.string())
115
- .optional()
116
- .describe('A list of keywords for the example. You can use these to explain why this example ' +
117
- "was a good match for the user's query."),
118
- required_packages: zod_1.z
119
- .array(zod_1.z.string())
120
- .optional()
121
- .describe('A list of NPM packages required for the example to work. Before presenting the code, ' +
122
- 'you should inform the user if any of these packages need to be installed.'),
123
- related_concepts: zod_1.z
124
- .array(zod_1.z.string())
125
- .optional()
126
- .describe('A list of related concepts. You can suggest these to the user as topics for ' +
127
- 'follow-up questions.'),
128
- related_tools: zod_1.z
129
- .array(zod_1.z.string())
130
- .optional()
131
- .describe('A list of related MCP tools. You can suggest these as potential next steps for the user.'),
132
- content: zod_1.z
133
- .string()
134
- .describe('A complete, self-contained Angular code example in Markdown format. This should be ' +
135
- 'presented to the user inside a markdown code block.'),
136
- snippet: zod_1.z
137
- .string()
138
- .optional()
139
- .describe('A contextual snippet from the content showing the matched search term. This field is ' +
140
- 'critical for efficiently evaluating a result`s relevance. It enables two primary ' +
141
- 'workflows:\n\n' +
142
- '1. For direct questions: You can internally review snippets to select the single best ' +
143
- 'result before generating a comprehensive answer from its full `content`.\n' +
144
- '2. For ambiguous or exploratory questions: You can present a summary of titles and ' +
145
- 'snippets to the user, allowing them to guide the next step.'),
146
- })),
147
- });
148
- exports.FIND_EXAMPLE_TOOL = (0, tool_registry_1.declareTool)({
149
- name: 'find_examples',
150
- title: 'Find Angular Code Examples',
151
- description: `
152
- <Purpose>
153
- Augments your knowledge base with a curated database of official, best-practice code examples,
154
- focusing on **modern, new, and recently updated** Angular features. This tool acts as a RAG
155
- (Retrieval-Augmented Generation) source, providing ground-truth information on the latest Angular
156
- APIs and patterns. You **MUST** use it to understand and apply current standards when working with
157
- new or evolving features.
158
- </Purpose>
159
- <Use Cases>
160
- * **Knowledge Augmentation:** Learning about new or updated Angular features (e.g., query: 'signal input' or 'deferrable views').
161
- * **Modern Implementation:** Finding the correct modern syntax for features
162
- (e.g., query: 'functional route guard' or 'http client with fetch').
163
- * **Refactoring to Modern Patterns:** Upgrading older code by finding examples of new syntax
164
- (e.g., query: 'built-in control flow' to replace "*ngIf").
165
- * **Advanced Filtering:** Combining a full-text search with filters to narrow results.
166
- (e.g., query: 'forms', required_packages: ['@angular/forms'], keywords: ['validation'])
167
- </Use Cases>
168
- <Operational Notes>
169
- * **Project-Specific Use (Recommended):** For tasks inside a user's project, you **MUST** provide the
170
- \`workspacePath\` argument to get examples that match the project's Angular version. Get this
171
- path from \`list_projects\`.
172
- * **General Use:** If no project context is available (e.g., for general questions or learning),
173
- you can call the tool without the \`workspacePath\` argument. It will return the latest
174
- generic examples.
175
- * **Tool Selection:** This database primarily contains examples for new and recently updated Angular
176
- features. For established, core features, the main documentation (via the
177
- \`search_documentation\` tool) may be a better source of information.
178
- * The examples in this database are the single source of truth for modern Angular coding patterns.
179
- * The search query uses a powerful full-text search syntax (FTS5). Refer to the 'query'
180
- parameter description for detailed syntax rules and examples.
181
- * You can combine the main 'query' with optional filters like 'keywords', 'required_packages',
182
- and 'related_concepts' to create highly specific searches.
183
- </Operational Notes>`,
184
- inputSchema: findExampleInputSchema.shape,
185
- outputSchema: findExampleOutputSchema.shape,
186
- isReadOnly: true,
187
- isLocalOnly: true,
188
- shouldRegister: ({ logger }) => {
189
- // sqlite database support requires Node.js 22.16+
190
- const [nodeMajor, nodeMinor] = process.versions.node.split('.', 2).map(Number);
191
- if (nodeMajor < 22 || (nodeMajor === 22 && nodeMinor < 16)) {
192
- logger.warn(`MCP tool 'find_examples' requires Node.js 22.16 (or higher). ` +
193
- ' Registration of this tool has been skipped.');
194
- return false;
195
- }
196
- return true;
197
- },
198
- factory: createFindExampleHandler,
199
- });
200
- /**
201
- * A list of known Angular packages that may contain example databases.
202
- * The tool will attempt to resolve and load example databases from these packages.
203
- */
204
- const KNOWN_EXAMPLE_PACKAGES = ['@angular/core', '@angular/aria', '@angular/forms'];
205
- /**
206
- * Attempts to find version-specific example databases from the user's installed
207
- * versions of known Angular packages. It looks for a custom `angular` metadata property in each
208
- * package's `package.json` to locate the database.
209
- *
210
- * @example A sample `package.json` `angular` field:
211
- * ```json
212
- * {
213
- * "angular": {
214
- * "examples": {
215
- * "format": "sqlite",
216
- * "path": "./resources/code-examples.db"
217
- * }
218
- * }
219
- * }
220
- * ```
221
- *
222
- * @param workspacePath The absolute path to the user's `angular.json` file.
223
- * @param logger The MCP tool context logger for reporting warnings.
224
- * @returns A promise that resolves to an array of objects, each containing a database path and source.
225
- */
226
- async function getVersionSpecificExampleDatabases(workspacePath, logger) {
227
- const workspaceRequire = (0, node_module_1.createRequire)(workspacePath);
228
- const databases = [];
229
- for (const packageName of KNOWN_EXAMPLE_PACKAGES) {
230
- // 1. Resolve the path to package.json
231
- let pkgJsonPath;
232
- try {
233
- pkgJsonPath = workspaceRequire.resolve(`${packageName}/package.json`);
234
- }
235
- catch (e) {
236
- // This is not a warning because the user may not have all known packages installed.
237
- continue;
238
- }
239
- // 2. Read and parse package.json, then find the database.
240
- try {
241
- const pkgJsonContent = await (0, promises_1.readFile)(pkgJsonPath, 'utf-8');
242
- const pkgJson = JSON.parse(pkgJsonContent);
243
- const examplesInfo = pkgJson['angular']?.examples;
244
- if (examplesInfo &&
245
- examplesInfo.format === 'sqlite' &&
246
- typeof examplesInfo.path === 'string') {
247
- const packageDirectory = node_path_1.default.dirname(pkgJsonPath);
248
- const dbPath = node_path_1.default.resolve(packageDirectory, examplesInfo.path);
249
- // Ensure the resolved database path is within the package boundary.
250
- const relativePath = node_path_1.default.relative(packageDirectory, dbPath);
251
- if (relativePath.startsWith('..') || node_path_1.default.isAbsolute(relativePath)) {
252
- logger.warn(`Detected a potential path traversal attempt in '${pkgJsonPath}'. ` +
253
- `The path '${examplesInfo.path}' escapes the package boundary. ` +
254
- 'This database will be skipped.');
255
- continue;
256
- }
257
- // Check the file size to prevent reading a very large file.
258
- const stats = await (0, promises_1.stat)(dbPath);
259
- if (stats.size > 10 * 1024 * 1024) {
260
- // 10MB
261
- logger.warn(`The example database at '${dbPath}' is larger than 10MB (${stats.size} bytes). ` +
262
- 'This is unexpected and the file will not be used.');
263
- continue;
264
- }
265
- const source = `package ${packageName}@${pkgJson.version}`;
266
- databases.push({ dbPath, source });
267
- }
268
- }
269
- catch (e) {
270
- logger.warn(`Failed to read or parse version-specific examples metadata referenced in '${pkgJsonPath}': ${e instanceof Error ? e.message : e}.`);
271
- }
272
- }
273
- return databases;
274
- }
275
- async function createFindExampleHandler({ logger, exampleDatabasePath }) {
276
- const runtimeDb = process.env['NG_MCP_EXAMPLES_DIR']
277
- ? await setupRuntimeExamples(process.env['NG_MCP_EXAMPLES_DIR'])
278
- : undefined;
279
- suppressSqliteWarning();
280
- return async (input) => {
281
- // If the dev-time override is present, use it and bypass all other logic.
282
- if (runtimeDb) {
283
- return queryDatabase([runtimeDb], input);
284
- }
285
- const resolvedDbs = [];
286
- // First, try to get all available version-specific guides.
287
- if (input.workspacePath) {
288
- const versionSpecificDbs = await getVersionSpecificExampleDatabases(input.workspacePath, logger);
289
- for (const db of versionSpecificDbs) {
290
- resolvedDbs.push({ path: db.dbPath, source: db.source });
291
- }
292
- }
293
- // If no version-specific guides were found for any reason, fall back to the bundled version.
294
- if (resolvedDbs.length === 0 && exampleDatabasePath) {
295
- resolvedDbs.push({ path: exampleDatabasePath, source: 'bundled' });
296
- }
297
- if (resolvedDbs.length === 0) {
298
- // This should be prevented by the registration logic in mcp-server.ts
299
- throw new Error('No example databases are available.');
300
- }
301
- const { DatabaseSync } = await Promise.resolve().then(() => __importStar(require('node:sqlite')));
302
- const dbConnections = [];
303
- for (const { path, source } of resolvedDbs) {
304
- const db = new DatabaseSync(path, { readOnly: true });
305
- try {
306
- validateDatabaseSchema(db, source);
307
- dbConnections.push(db);
308
- }
309
- catch (e) {
310
- logger.warn(e.message);
311
- // If a database is invalid, we should not query it, but we should not fail the whole tool.
312
- // We will just skip this database and try to use the others.
313
- continue;
314
- }
315
- }
316
- if (dbConnections.length === 0) {
317
- throw new Error('All available example databases were invalid. Cannot perform query.');
318
- }
319
- return queryDatabase(dbConnections, input);
320
- };
321
- }
322
- function queryDatabase(dbs, input) {
323
- const { query, keywords, required_packages, related_concepts, includeExperimental } = input;
324
- // Build the query dynamically
325
- const params = [];
326
- let sql = `SELECT e.title, e.summary, e.keywords, e.required_packages, e.related_concepts, e.related_tools, e.content, ` +
327
- // The `snippet` function generates a contextual snippet of the matched text.
328
- // Column 6 is the `content` column. We highlight matches with asterisks and limit the snippet size.
329
- "snippet(examples_fts, 6, '**', '**', '...', 15) AS snippet, " +
330
- // The `bm25` function returns the relevance score of the match. The weights
331
- // assigned to each column boost the ranking of documents where the search
332
- // term appears in a more important field.
333
- // Column order: title, summary, keywords, required_packages, related_concepts, related_tools, content
334
- 'bm25(examples_fts, 10.0, 5.0, 5.0, 1.0, 2.0, 1.0, 1.0) AS rank ' +
335
- 'FROM examples e JOIN examples_fts ON e.id = examples_fts.rowid';
336
- const whereClauses = [];
337
- // FTS query
338
- if (query) {
339
- whereClauses.push('examples_fts MATCH ?');
340
- params.push(escapeSearchQuery(query));
341
- }
342
- // JSON array filters
343
- const addJsonFilter = (column, values) => {
344
- if (values?.length) {
345
- for (const value of values) {
346
- whereClauses.push(`e.${column} LIKE ?`);
347
- params.push(`%"${value}"%`);
348
- }
349
- }
350
- };
351
- addJsonFilter('keywords', keywords);
352
- addJsonFilter('required_packages', required_packages);
353
- addJsonFilter('related_concepts', related_concepts);
354
- if (!includeExperimental) {
355
- whereClauses.push('e.experimental = 0');
356
- }
357
- if (whereClauses.length > 0) {
358
- sql += ` WHERE ${whereClauses.join(' AND ')}`;
359
- }
360
- // Query database and return results
361
- const examples = [];
362
- const textContent = [];
363
- for (const db of dbs) {
364
- const queryStatement = db.prepare(sql);
365
- for (const exampleRecord of queryStatement.all(...params)) {
366
- const record = exampleRecord;
367
- const example = {
368
- title: record['title'],
369
- summary: record['summary'],
370
- keywords: JSON.parse(record['keywords'] || '[]'),
371
- required_packages: JSON.parse(record['required_packages'] || '[]'),
372
- related_concepts: JSON.parse(record['related_concepts'] || '[]'),
373
- related_tools: JSON.parse(record['related_tools'] || '[]'),
374
- content: record['content'],
375
- snippet: record['snippet'],
376
- rank: record['rank'],
377
- };
378
- examples.push(example);
379
- }
380
- }
381
- // Order the combined results by relevance.
382
- // The `bm25` algorithm returns a smaller number for a more relevant match.
383
- examples.sort((a, b) => a.rank - b.rank);
384
- // The `rank` field is an internal implementation detail for sorting and should not be
385
- // returned to the user. We create a new array of examples without the `rank`.
386
- const finalExamples = examples.map(({ rank, ...rest }) => rest);
387
- for (const example of finalExamples) {
388
- // Also create a more structured text output
389
- let text = `## Example: ${example.title}\n**Summary:** ${example.summary}`;
390
- if (example.snippet) {
391
- text += `\n**Snippet:** ${example.snippet}`;
392
- }
393
- text += `\n\n---\n\n${example.content}`;
394
- textContent.push({ type: 'text', text });
395
- }
396
- return {
397
- content: textContent,
398
- structuredContent: { examples: finalExamples },
399
- };
400
- }
401
- /**
402
- * Escapes a search query for FTS5 by tokenizing and quoting terms.
403
- *
404
- * This function processes a raw search string and prepares it for an FTS5 full-text search.
405
- * It correctly handles quoted phrases, logical operators (AND, OR, NOT), parentheses,
406
- * and prefix searches (ending with an asterisk), ensuring that individual search
407
- * terms are properly quoted to be treated as literals by the search engine.
408
- * This is primarily intended to avoid unintentional usage of FTS5 query syntax by consumers.
409
- *
410
- * @param query The raw search query string.
411
- * @returns A sanitized query string suitable for FTS5.
412
- */
413
- function escapeSearchQuery(query) {
414
- // This regex tokenizes the query string into parts:
415
- // 1. Quoted phrases (e.g., "foo bar")
416
- // 2. Parentheses ( and )
417
- // 3. FTS5 operators (AND, OR, NOT, NEAR)
418
- // 4. Words, which can include a trailing asterisk for prefix search (e.g., foo*)
419
- const tokenizer = /"([^"]*)"|([()])|\b(AND|OR|NOT|NEAR)\b|([^\s()]+)/g;
420
- let match;
421
- const result = [];
422
- let lastIndex = 0;
423
- while ((match = tokenizer.exec(query)) !== null) {
424
- // Add any whitespace or other characters between tokens
425
- if (match.index > lastIndex) {
426
- result.push(query.substring(lastIndex, match.index));
427
- }
428
- const [, quoted, parenthesis, operator, term] = match;
429
- if (quoted !== undefined) {
430
- // It's a quoted phrase, keep it as is.
431
- result.push(`"${quoted}"`);
432
- }
433
- else if (parenthesis) {
434
- // It's a parenthesis, keep it as is.
435
- result.push(parenthesis);
436
- }
437
- else if (operator) {
438
- // It's an operator, keep it as is.
439
- result.push(operator);
440
- }
441
- else if (term) {
442
- // It's a term that needs to be quoted.
443
- if (term.endsWith('*')) {
444
- result.push(`"${term.slice(0, -1)}"*`);
445
- }
446
- else {
447
- result.push(`"${term}"`);
448
- }
449
- }
450
- lastIndex = tokenizer.lastIndex;
451
- }
452
- // Add any remaining part of the string
453
- if (lastIndex < query.length) {
454
- result.push(query.substring(lastIndex));
455
- }
456
- return result.join('');
457
- }
458
- /**
459
- * Suppresses the experimental warning emitted by Node.js for the `node:sqlite` module.
460
- *
461
- * This is a workaround to prevent the console from being cluttered with warnings
462
- * about the experimental status of the SQLite module, which is used by this tool.
463
- */
464
- function suppressSqliteWarning() {
465
- const originalProcessEmit = process.emit;
466
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
467
- process.emit = function (event, error) {
468
- if (event === 'warning' &&
469
- error instanceof Error &&
470
- error.name === 'ExperimentalWarning' &&
471
- error.message.includes('SQLite')) {
472
- return false;
473
- }
474
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, prefer-rest-params
475
- return originalProcessEmit.apply(process, arguments);
476
- };
477
- }
478
- /**
479
- * A simple YAML front matter parser.
480
- *
481
- * This function extracts the YAML block enclosed by `---` at the beginning of a string
482
- * and parses it into a JavaScript object. It is not a full YAML parser and only
483
- * supports simple key-value pairs and string arrays.
484
- *
485
- * @param content The string content to parse.
486
- * @returns A record containing the parsed front matter data.
487
- */
488
- function parseFrontmatter(content) {
489
- const match = content.match(/^---\r?\n(.*?)\r?\n---/s);
490
- if (!match) {
491
- return {};
492
- }
493
- const frontmatter = match[1];
494
- const data = {};
495
- const lines = frontmatter.split(/\r?\n/);
496
- let currentKey = '';
497
- let isArray = false;
498
- const arrayValues = [];
499
- for (const line of lines) {
500
- const keyValueMatch = line.match(/^([^:]+):\s*(.*)/);
501
- if (keyValueMatch) {
502
- if (currentKey && isArray) {
503
- data[currentKey] = arrayValues.slice();
504
- arrayValues.length = 0;
505
- }
506
- const [, key, value] = keyValueMatch;
507
- currentKey = key.trim();
508
- isArray = value.trim() === '';
509
- if (!isArray) {
510
- const trimmedValue = value.trim();
511
- if (trimmedValue === 'true') {
512
- data[currentKey] = true;
513
- }
514
- else if (trimmedValue === 'false') {
515
- data[currentKey] = false;
516
- }
517
- else {
518
- data[currentKey] = trimmedValue;
519
- }
520
- }
521
- }
522
- else {
523
- const arrayItemMatch = line.match(/^\s*-\s*(.*)/);
524
- if (arrayItemMatch && currentKey && isArray) {
525
- let value = arrayItemMatch[1].trim();
526
- // Unquote if the value is quoted.
527
- if ((value.startsWith("'") && value.endsWith("'")) ||
528
- (value.startsWith('"') && value.endsWith('"'))) {
529
- value = value.slice(1, -1);
530
- }
531
- arrayValues.push(value);
532
- }
533
- }
534
- }
535
- if (currentKey && isArray) {
536
- data[currentKey] = arrayValues;
537
- }
538
- return data;
539
- }
540
- async function setupRuntimeExamples(examplesPath) {
541
- const { DatabaseSync } = await Promise.resolve().then(() => __importStar(require('node:sqlite')));
542
- const db = new DatabaseSync(':memory:');
543
- // Create a relational table to store the structured example data.
544
- db.exec(`
545
- CREATE TABLE metadata (
546
- key TEXT PRIMARY KEY NOT NULL,
547
- value TEXT NOT NULL
548
- );
549
- `);
550
- db.exec(`
551
- INSERT INTO metadata (key, value) VALUES
552
- ('schema_version', '1'),
553
- ('created_at', '${new Date().toISOString()}');
554
- `);
555
- db.exec(`
556
- CREATE TABLE examples (
557
- id INTEGER PRIMARY KEY,
558
- title TEXT NOT NULL,
559
- summary TEXT NOT NULL,
560
- keywords TEXT,
561
- required_packages TEXT,
562
- related_concepts TEXT,
563
- related_tools TEXT,
564
- experimental INTEGER NOT NULL DEFAULT 0,
565
- content TEXT NOT NULL
566
- );
567
- `);
568
- // Create an FTS5 virtual table to provide full-text search capabilities.
569
- db.exec(`
570
- CREATE VIRTUAL TABLE examples_fts USING fts5(
571
- title,
572
- summary,
573
- keywords,
574
- required_packages,
575
- related_concepts,
576
- related_tools,
577
- content,
578
- content='examples',
579
- content_rowid='id',
580
- tokenize = 'porter ascii'
581
- );
582
- `);
583
- // Create triggers to keep the FTS table synchronized with the examples table.
584
- db.exec(`
585
- CREATE TRIGGER examples_after_insert AFTER INSERT ON examples BEGIN
586
- INSERT INTO examples_fts(rowid, title, summary, keywords, required_packages, related_concepts, related_tools, content)
587
- VALUES (
588
- new.id, new.title, new.summary, new.keywords, new.required_packages, new.related_concepts,
589
- new.related_tools, new.content
590
- );
591
- END;
592
- `);
593
- const insertStatement = db.prepare('INSERT INTO examples(' +
594
- 'title, summary, keywords, required_packages, related_concepts, related_tools, experimental, content' +
595
- ') VALUES(?, ?, ?, ?, ?, ?, ?, ?);');
596
- const frontmatterSchema = zod_1.z.object({
597
- title: zod_1.z.string(),
598
- summary: zod_1.z.string(),
599
- keywords: zod_1.z.array(zod_1.z.string()).optional(),
600
- required_packages: zod_1.z.array(zod_1.z.string()).optional(),
601
- related_concepts: zod_1.z.array(zod_1.z.string()).optional(),
602
- related_tools: zod_1.z.array(zod_1.z.string()).optional(),
603
- experimental: zod_1.z.boolean().optional(),
604
- });
605
- db.exec('BEGIN TRANSACTION');
606
- for await (const entry of (0, promises_1.glob)('**/*.md', { cwd: examplesPath, withFileTypes: true })) {
607
- if (!entry.isFile()) {
608
- continue;
609
- }
610
- const content = await (0, promises_1.readFile)(node_path_1.default.join(entry.parentPath, entry.name), 'utf-8');
611
- const frontmatter = parseFrontmatter(content);
612
- const validation = frontmatterSchema.safeParse(frontmatter);
613
- if (!validation.success) {
614
- // eslint-disable-next-line no-console
615
- console.warn(`Skipping invalid example file ${entry.name}:`, validation.error.issues);
616
- continue;
617
- }
618
- const { title, summary, keywords, required_packages, related_concepts, related_tools, experimental, } = validation.data;
619
- insertStatement.run(title, summary, JSON.stringify(keywords ?? []), JSON.stringify(required_packages ?? []), JSON.stringify(related_concepts ?? []), JSON.stringify(related_tools ?? []), experimental ? 1 : 0, content);
620
- }
621
- db.exec('END TRANSACTION');
622
- return db;
623
- }
624
- const EXPECTED_SCHEMA_VERSION = 1;
625
- /**
626
- * Validates the schema version of the example database.
627
- *
628
- * @param db The database connection to validate.
629
- * @param dbSource A string identifying the source of the database (e.g., 'bundled' or a version number).
630
- * @throws An error if the schema version is missing or incompatible.
631
- */
632
- function validateDatabaseSchema(db, dbSource) {
633
- const schemaVersionResult = db
634
- .prepare('SELECT value FROM metadata WHERE key = ?')
635
- .get('schema_version');
636
- const actualSchemaVersion = schemaVersionResult ? Number(schemaVersionResult.value) : undefined;
637
- if (actualSchemaVersion !== EXPECTED_SCHEMA_VERSION) {
638
- db.close();
639
- let errorMessage;
640
- if (actualSchemaVersion === undefined) {
641
- errorMessage = 'The example database is missing a schema version and cannot be used.';
642
- }
643
- else if (actualSchemaVersion > EXPECTED_SCHEMA_VERSION) {
644
- errorMessage =
645
- `This project's example database (version ${actualSchemaVersion})` +
646
- ` is newer than what this version of the Angular CLI supports (version ${EXPECTED_SCHEMA_VERSION}).` +
647
- ' Please update your `@angular/cli` package to a newer version.';
648
- }
649
- else {
650
- errorMessage =
651
- `This version of the Angular CLI (expects schema version ${EXPECTED_SCHEMA_VERSION})` +
652
- ` requires a newer example database than the one found in this project (version ${actualSchemaVersion}).`;
653
- }
654
- throw new Error(`Incompatible example database schema from source '${dbSource}':\n${errorMessage}`);
655
- }
656
- }
657
- //# sourceMappingURL=examples.js.map