diff --git a/package.json b/package.json
index db8c805d18c0..6bbaa569b9ff 100644
--- a/package.json
+++ b/package.json
@@ -82,7 +82,7 @@
"@types/less": "^3.0.3",
"@types/loader-utils": "^2.0.0",
"@types/lodash": "^4.17.0",
- "@types/node": "^20.19.7",
+ "@types/node": "^22.12.0",
"@types/npm-package-arg": "^6.1.0",
"@types/pacote": "^11.1.3",
"@types/picomatch": "^4.0.0",
diff --git a/packages/angular/cli/BUILD.bazel b/packages/angular/cli/BUILD.bazel
index 2eacbb6b4ebf..0c1655e5cedb 100644
--- a/packages/angular/cli/BUILD.bazel
+++ b/packages/angular/cli/BUILD.bazel
@@ -5,6 +5,7 @@
load("@npm//:defs.bzl", "npm_link_all_packages")
load("//tools:defaults.bzl", "jasmine_test", "npm_package", "ts_project")
+load("//tools:example_db_generator.bzl", "cli_example_db")
load("//tools:ng_cli_schema_generator.bzl", "cli_json_schema")
load("//tools:ts_json_schema.bzl", "ts_json_schema")
@@ -25,6 +26,7 @@ RUNTIME_ASSETS = glob(
],
) + [
"//packages/angular/cli:lib/config/schema.json",
+ "//packages/angular/cli:lib/code-examples.db",
]
ts_project(
@@ -74,6 +76,17 @@ ts_project(
],
)
+cli_example_db(
+ name = "cli_example_database",
+ srcs = glob(
+ include = [
+ "lib/examples/**/*.md",
+ ],
+ ),
+ out = "lib/code-examples.db",
+ path = "packages/angular/cli/lib/examples",
+)
+
CLI_SCHEMA_DATA = [
"//packages/angular/build:schemas",
"//packages/angular_devkit/build_angular:schemas",
diff --git a/packages/angular/cli/lib/examples/if-block.md b/packages/angular/cli/lib/examples/if-block.md
new file mode 100644
index 000000000000..ca516cfb3725
--- /dev/null
+++ b/packages/angular/cli/lib/examples/if-block.md
@@ -0,0 +1,28 @@
+# Angular @if Control Flow Example
+
+This example demonstrates how to use the `@if` control flow block in an Angular template. The visibility of a `
` element is controlled by a boolean field in the component's TypeScript code.
+
+## Angular Template
+
+```html
+
+@if (isVisible) {
+
This content is conditionally displayed.
+}
+```
+
+## Component TypeScript
+
+```typescript
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-example',
+ templateUrl: './example.component.html',
+ styleUrls: ['./example.component.css'],
+})
+export class ExampleComponent {
+ // This boolean field controls the visibility of the element in the template.
+ isVisible: boolean = true;
+}
+```
diff --git a/packages/angular/cli/src/commands/mcp/cli.ts b/packages/angular/cli/src/commands/mcp/cli.ts
index 81260f09f6b6..363136525f37 100644
--- a/packages/angular/cli/src/commands/mcp/cli.ts
+++ b/packages/angular/cli/src/commands/mcp/cli.ts
@@ -43,7 +43,10 @@ export default class McpCommandModule extends CommandModule implements CommandMo
return;
}
- const server = await createMcpServer({ workspace: this.context.workspace });
+ const server = await createMcpServer(
+ { workspace: this.context.workspace },
+ this.context.logger,
+ );
const transport = new StdioServerTransport();
await server.connect(transport);
}
diff --git a/packages/angular/cli/src/commands/mcp/mcp-server.ts b/packages/angular/cli/src/commands/mcp/mcp-server.ts
index 6a51515a7014..47231ff4e0e4 100644
--- a/packages/angular/cli/src/commands/mcp/mcp-server.ts
+++ b/packages/angular/cli/src/commands/mcp/mcp-server.ts
@@ -13,11 +13,15 @@ import type { AngularWorkspace } from '../../utilities/config';
import { VERSION } from '../../utilities/version';
import { registerBestPracticesTool } from './tools/best-practices';
import { registerDocSearchTool } from './tools/doc-search';
+import { registerFindExampleTool } from './tools/examples';
import { registerListProjectsTool } from './tools/projects';
-export async function createMcpServer(context: {
- workspace?: AngularWorkspace;
-}): Promise
{
+export async function createMcpServer(
+ context: {
+ workspace?: AngularWorkspace;
+ },
+ logger: { warn(text: string): void },
+): Promise {
const server = new McpServer({
name: 'angular-cli-server',
version: VERSION.full,
@@ -54,5 +58,18 @@ export async function createMcpServer(context: {
await registerDocSearchTool(server);
+ if (process.env['NG_MCP_CODE_EXAMPLES'] === '1') {
+ // sqlite database support requires Node.js 22.16+
+ const [nodeMajor, nodeMinor] = process.versions.node.split('.', 2).map(Number);
+ if (nodeMajor < 22 || (nodeMajor === 22 && nodeMinor < 16)) {
+ logger.warn(
+ `MCP tool 'find_examples' requires Node.js 22.16 (or higher). ` +
+ ' Registration of this tool has been skipped.',
+ );
+ } else {
+ registerFindExampleTool(server, path.join(__dirname, '../../../lib/code-examples.db'));
+ }
+ }
+
return server;
}
diff --git a/packages/angular/cli/src/commands/mcp/tools/examples.ts b/packages/angular/cli/src/commands/mcp/tools/examples.ts
new file mode 100644
index 000000000000..bcc0a5050369
--- /dev/null
+++ b/packages/angular/cli/src/commands/mcp/tools/examples.ts
@@ -0,0 +1,173 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import { z } from 'zod';
+
+/**
+ * Registers the `find_examples` tool with the MCP server.
+ *
+ * This tool allows users to search for best-practice Angular code examples
+ * from a local SQLite database.
+ *
+ * @param server The MCP server instance.
+ * @param exampleDatabasePath The path to the SQLite database file containing the examples.
+ */
+export function registerFindExampleTool(server: McpServer, exampleDatabasePath: string): void {
+ let db: import('node:sqlite').DatabaseSync | undefined;
+ let queryStatement: import('node:sqlite').StatementSync | undefined;
+
+ server.registerTool(
+ 'find_examples',
+ {
+ title: 'Find Angular Code Examples',
+ description:
+ 'Before writing or modifying any Angular code including templates, ' +
+ '**ALWAYS** use this tool to find current best-practice examples. ' +
+ 'This is critical for ensuring code quality and adherence to modern Angular standards. ' +
+ 'This tool searches a curated database of approved Angular code examples and returns the most relevant results for your query. ' +
+ 'Example Use Cases: ' +
+ "1) Creating new components, directives, or services (e.g., query: 'standalone component' or 'signal input'). " +
+ "2) Implementing core features (e.g., query: 'lazy load route', 'httpinterceptor', or 'route guard'). " +
+ "3) Refactoring existing code to use modern patterns (e.g., query: 'ngfor trackby' or 'form validation').",
+ inputSchema: {
+ query: z.string().describe(
+ `Performs a full-text search using FTS5 syntax. The query should target relevant Angular concepts.
+
+Key Syntax Features (see https://www.sqlite.org/fts5.html for full documentation):
+ - AND (default): Space-separated terms are combined with AND.
+ - Example: 'standalone component' (finds results with both "standalone" and "component")
+ - OR: Use the OR operator to find results with either term.
+ - Example: 'validation OR validator'
+ - NOT: Use the NOT operator to exclude terms.
+ - Example: 'forms NOT reactive'
+ - Grouping: Use parentheses () to group expressions.
+ - Example: '(validation OR validator) AND forms'
+ - Phrase Search: Use double quotes "" for exact phrases.
+ - Example: '"template-driven forms"'
+ - Prefix Search: Use an asterisk * for prefix matching.
+ - Example: 'rout*' (matches "route", "router", "routing")
+
+Examples of queries:
+ - Find standalone components: 'standalone component'
+ - Find ngFor with trackBy: 'ngFor trackBy'
+ - Find signal inputs: 'signal input'
+ - Find lazy loading a route: 'lazy load route'
+ - Find forms with validation: 'form AND (validation OR validator)'`,
+ ),
+ },
+ annotations: {
+ readOnlyHint: true,
+ openWorldHint: false,
+ },
+ },
+ async ({ query }) => {
+ if (!db || !queryStatement) {
+ suppressSqliteWarning();
+
+ const { DatabaseSync } = await import('node:sqlite');
+ db = new DatabaseSync(exampleDatabasePath, { readOnly: true });
+ queryStatement = db.prepare('SELECT * from examples WHERE examples MATCH ? ORDER BY rank;');
+ }
+
+ const sanitizedQuery = sanitizeSearchQuery(query);
+
+ // Query database and return results as text content
+ const content = [];
+ for (const exampleRecord of queryStatement.all(sanitizedQuery)) {
+ content.push({ type: 'text' as const, text: exampleRecord['content'] as string });
+ }
+
+ return {
+ content,
+ };
+ },
+ );
+}
+
+/**
+ * Sanitizes a search query for FTS5 by tokenizing and quoting terms.
+ *
+ * This function processes a raw search string and prepares it for an FTS5 full-text search.
+ * It correctly handles quoted phrases, logical operators (AND, OR, NOT), parentheses,
+ * and prefix searches (ending with an asterisk), ensuring that individual search
+ * terms are properly quoted to be treated as literals by the search engine.
+ *
+ * @param query The raw search query string.
+ * @returns A sanitized query string suitable for FTS5.
+ */
+export function sanitizeSearchQuery(query: string): string {
+ // This regex tokenizes the query string into parts:
+ // 1. Quoted phrases (e.g., "foo bar")
+ // 2. Parentheses ( and )
+ // 3. FTS5 operators (AND, OR, NOT, NEAR)
+ // 4. Words, which can include a trailing asterisk for prefix search (e.g., foo*)
+ const tokenizer = /"([^"]*)"|([()])|\b(AND|OR|NOT|NEAR)\b|([^\s()]+)/g;
+ let match;
+ const result: string[] = [];
+ let lastIndex = 0;
+
+ while ((match = tokenizer.exec(query)) !== null) {
+ // Add any whitespace or other characters between tokens
+ if (match.index > lastIndex) {
+ result.push(query.substring(lastIndex, match.index));
+ }
+
+ const [, quoted, parenthesis, operator, term] = match;
+
+ if (quoted !== undefined) {
+ // It's a quoted phrase, keep it as is.
+ result.push(`"${quoted}"`);
+ } else if (parenthesis) {
+ // It's a parenthesis, keep it as is.
+ result.push(parenthesis);
+ } else if (operator) {
+ // It's an operator, keep it as is.
+ result.push(operator);
+ } else if (term) {
+ // It's a term that needs to be quoted.
+ if (term.endsWith('*')) {
+ result.push(`"${term.slice(0, -1)}"*`);
+ } else {
+ result.push(`"${term}"`);
+ }
+ }
+ lastIndex = tokenizer.lastIndex;
+ }
+
+ // Add any remaining part of the string
+ if (lastIndex < query.length) {
+ result.push(query.substring(lastIndex));
+ }
+
+ return result.join('');
+}
+
+/**
+ * Suppresses the experimental warning emitted by Node.js for the `node:sqlite` module.
+ *
+ * This is a workaround to prevent the console from being cluttered with warnings
+ * about the experimental status of the SQLite module, which is used by this tool.
+ */
+function suppressSqliteWarning() {
+ const originalProcessEmit = process.emit;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ process.emit = function (event: string, error?: unknown): any {
+ if (
+ event === 'warning' &&
+ error instanceof Error &&
+ error.name === 'ExperimentalWarning' &&
+ error.message.includes('SQLite')
+ ) {
+ return false;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, prefer-rest-params
+ return originalProcessEmit.apply(process, arguments as any);
+ };
+}
diff --git a/packages/angular/cli/src/commands/mcp/tools/examples_spec.ts b/packages/angular/cli/src/commands/mcp/tools/examples_spec.ts
new file mode 100644
index 000000000000..f76b0eacbd1b
--- /dev/null
+++ b/packages/angular/cli/src/commands/mcp/tools/examples_spec.ts
@@ -0,0 +1,53 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+import { sanitizeSearchQuery } from './examples';
+
+describe('sanitizeSearchQuery', () => {
+ it('should wrap single terms in double quotes', () => {
+ expect(sanitizeSearchQuery('foo')).toBe('"foo"');
+ });
+
+ it('should wrap multiple terms in double quotes', () => {
+ expect(sanitizeSearchQuery('foo bar')).toBe('"foo" "bar"');
+ });
+
+ it('should not wrap FTS5 operators', () => {
+ expect(sanitizeSearchQuery('foo AND bar')).toBe('"foo" AND "bar"');
+ expect(sanitizeSearchQuery('foo OR bar')).toBe('"foo" OR "bar"');
+ expect(sanitizeSearchQuery('foo NOT bar')).toBe('"foo" NOT "bar"');
+ expect(sanitizeSearchQuery('foo NEAR bar')).toBe('"foo" NEAR "bar"');
+ });
+
+ it('should not wrap terms that are already quoted', () => {
+ expect(sanitizeSearchQuery('"foo" bar')).toBe('"foo" "bar"');
+ expect(sanitizeSearchQuery('"foo bar"')).toBe('"foo bar"');
+ });
+
+ it('should handle prefix searches', () => {
+ expect(sanitizeSearchQuery('foo*')).toBe('"foo"*');
+ expect(sanitizeSearchQuery('foo* bar')).toBe('"foo"* "bar"');
+ });
+
+ it('should handle multi-word quoted phrases', () => {
+ expect(sanitizeSearchQuery('"foo bar" baz')).toBe('"foo bar" "baz"');
+ expect(sanitizeSearchQuery('foo "bar baz"')).toBe('"foo" "bar baz"');
+ });
+
+ it('should handle complex queries', () => {
+ expect(sanitizeSearchQuery('("foo bar" OR baz) AND qux*')).toBe(
+ '("foo bar" OR "baz") AND "qux"*',
+ );
+ });
+
+ it('should handle multi-word quoted phrases with three or more words', () => {
+ expect(sanitizeSearchQuery('"foo bar baz" qux')).toBe('"foo bar baz" "qux"');
+ expect(sanitizeSearchQuery('foo "bar baz qux"')).toBe('"foo" "bar baz qux"');
+ expect(sanitizeSearchQuery('foo "bar baz qux" quux')).toBe('"foo" "bar baz qux" "quux"');
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e829f37c25a5..3cb6dd0550ce 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -123,8 +123,8 @@ importers:
specifier: ^4.17.0
version: 4.17.20
'@types/node':
- specifier: ^20.19.7
- version: 20.19.9
+ specifier: ^22.12.0
+ version: 22.16.3
'@types/npm-package-arg':
specifier: ^6.1.0
version: 6.1.4
@@ -277,7 +277,7 @@ importers:
version: 6.2.1(rollup@4.45.1)(typescript@5.9.0-beta)
rollup-plugin-sourcemaps2:
specifier: 0.5.3
- version: 0.5.3(@types/node@20.19.9)(rollup@4.45.1)
+ version: 0.5.3(@types/node@22.16.3)(rollup@4.45.1)
semver:
specifier: 7.7.2
version: 7.7.2
@@ -292,7 +292,7 @@ importers:
version: 7.4.3
ts-node:
specifier: ^10.9.1
- version: 10.9.2(@types/node@20.19.9)(typescript@5.9.0-beta)
+ version: 10.9.2(@types/node@22.16.3)(typescript@5.9.0-beta)
tslib:
specifier: 2.8.1
version: 2.8.1
@@ -342,7 +342,7 @@ importers:
version: 7.8.2
vitest:
specifier: 3.2.4
- version: 3.2.4(@types/node@20.19.9)(jiti@1.21.7)(jsdom@26.1.0)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
+ version: 3.2.4(@types/node@22.16.3)(jiti@1.21.7)(jsdom@26.1.0)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
packages/angular/build:
dependencies:
@@ -363,10 +363,10 @@ importers:
version: 7.24.7
'@inquirer/confirm':
specifier: 5.1.14
- version: 5.1.14(@types/node@20.19.9)
+ version: 5.1.14(@types/node@22.16.3)
'@vitejs/plugin-basic-ssl':
specifier: 2.1.0
- version: 2.1.0(vite@7.0.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
+ version: 2.1.0(vite@7.0.5(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
beasties:
specifier: 0.3.5
version: 0.3.5
@@ -420,7 +420,7 @@ importers:
version: 0.2.14
vite:
specifier: 7.0.5
- version: 7.0.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
+ version: 7.0.5(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
watchpack:
specifier: 2.4.4
version: 2.4.4
@@ -452,7 +452,7 @@ importers:
version: 7.8.2
vitest:
specifier: 3.2.4
- version: 3.2.4(@types/node@20.19.9)(jiti@1.21.7)(jsdom@26.1.0)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
+ version: 3.2.4(@types/node@22.16.3)(jiti@1.21.7)(jsdom@26.1.0)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
packages/angular/cli:
dependencies:
@@ -467,10 +467,10 @@ importers:
version: link:../../angular_devkit/schematics
'@inquirer/prompts':
specifier: 7.7.1
- version: 7.7.1(@types/node@20.19.9)
+ version: 7.7.1(@types/node@22.16.3)
'@listr2/prompt-adapter-inquirer':
specifier: 3.0.1
- version: 3.0.1(@inquirer/prompts@7.7.1(@types/node@20.19.9))(@types/node@20.19.9)(listr2@9.0.1)
+ version: 3.0.1(@inquirer/prompts@7.7.1(@types/node@22.16.3))(@types/node@22.16.3)(listr2@9.0.1)
'@modelcontextprotocol/sdk':
specifier: 1.16.0
version: 1.16.0
@@ -848,7 +848,7 @@ importers:
version: link:../schematics
'@inquirer/prompts':
specifier: 7.7.1
- version: 7.7.1(@types/node@20.19.9)
+ version: 7.7.1(@types/node@22.16.3)
ansi-colors:
specifier: 4.1.3
version: 4.1.3
@@ -2028,6 +2028,15 @@ packages:
'@types/node':
optional: true
+ '@inquirer/type@3.0.7':
+ resolution: {integrity: sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
'@inquirer/type@3.0.8':
resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==}
engines: {node: '>=18'}
@@ -2902,6 +2911,9 @@ packages:
'@types/node@20.19.9':
resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==}
+ '@types/node@22.16.3':
+ resolution: {integrity: sha512-sr4Xz74KOUeYadexo1r8imhRtlVXcs+j3XK3TcoiYk7B1t3YRVJgtaD3cwX73NYb71pmVuMLNRhJ9XKdoDB74g==}
+
'@types/npm-package-arg@6.1.4':
resolution: {integrity: sha512-vDgdbMy2QXHnAruzlv68pUtXCjmqUk3WrBAsRboRovsOmxbfn/WiYCjmecyKjGztnMps5dWp4Uq2prp+Ilo17Q==}
@@ -6869,7 +6881,6 @@ packages:
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
deprecated: |-
You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.
-
(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
qjobs@1.2.0:
@@ -7954,6 +7965,46 @@ packages:
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
+ vite@7.0.2:
+ resolution: {integrity: sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ lightningcss: ^1.21.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
vite@7.0.5:
resolution: {integrity: sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -9538,27 +9589,27 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
- '@inquirer/checkbox@4.2.0(@types/node@20.19.9)':
+ '@inquirer/checkbox@4.2.0(@types/node@22.16.3)':
dependencies:
- '@inquirer/core': 10.1.15(@types/node@20.19.9)
+ '@inquirer/core': 10.1.15(@types/node@22.16.3)
'@inquirer/figures': 1.0.13
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/type': 3.0.8(@types/node@22.16.3)
ansi-escapes: 4.3.2
yoctocolors-cjs: 2.1.2
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
- '@inquirer/confirm@5.1.14(@types/node@20.19.9)':
+ '@inquirer/confirm@5.1.14(@types/node@22.16.3)':
dependencies:
- '@inquirer/core': 10.1.15(@types/node@20.19.9)
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/core': 10.1.15(@types/node@22.16.3)
+ '@inquirer/type': 3.0.8(@types/node@22.16.3)
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
- '@inquirer/core@10.1.15(@types/node@20.19.9)':
+ '@inquirer/core@10.1.15(@types/node@22.16.3)':
dependencies:
'@inquirer/figures': 1.0.13
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/type': 3.0.8(@types/node@22.16.3)
ansi-escapes: 4.3.2
cli-width: 4.1.0
mute-stream: 2.0.0
@@ -9566,93 +9617,97 @@ snapshots:
wrap-ansi: 6.2.0
yoctocolors-cjs: 2.1.2
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
- '@inquirer/editor@4.2.15(@types/node@20.19.9)':
+ '@inquirer/editor@4.2.15(@types/node@22.16.3)':
dependencies:
- '@inquirer/core': 10.1.15(@types/node@20.19.9)
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/core': 10.1.15(@types/node@22.16.3)
+ '@inquirer/type': 3.0.8(@types/node@22.16.3)
external-editor: 3.1.0
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
- '@inquirer/expand@4.0.17(@types/node@20.19.9)':
+ '@inquirer/expand@4.0.17(@types/node@22.16.3)':
dependencies:
- '@inquirer/core': 10.1.15(@types/node@20.19.9)
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/core': 10.1.15(@types/node@22.16.3)
+ '@inquirer/type': 3.0.8(@types/node@22.16.3)
yoctocolors-cjs: 2.1.2
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@inquirer/figures@1.0.13': {}
- '@inquirer/input@4.2.1(@types/node@20.19.9)':
+ '@inquirer/input@4.2.1(@types/node@22.16.3)':
dependencies:
- '@inquirer/core': 10.1.15(@types/node@20.19.9)
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/core': 10.1.15(@types/node@22.16.3)
+ '@inquirer/type': 3.0.8(@types/node@22.16.3)
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
- '@inquirer/number@3.0.17(@types/node@20.19.9)':
+ '@inquirer/number@3.0.17(@types/node@22.16.3)':
dependencies:
- '@inquirer/core': 10.1.15(@types/node@20.19.9)
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/core': 10.1.15(@types/node@22.16.3)
+ '@inquirer/type': 3.0.8(@types/node@22.16.3)
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
- '@inquirer/password@4.0.17(@types/node@20.19.9)':
+ '@inquirer/password@4.0.17(@types/node@22.16.3)':
dependencies:
- '@inquirer/core': 10.1.15(@types/node@20.19.9)
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/core': 10.1.15(@types/node@22.16.3)
+ '@inquirer/type': 3.0.8(@types/node@22.16.3)
ansi-escapes: 4.3.2
optionalDependencies:
- '@types/node': 20.19.9
-
- '@inquirer/prompts@7.7.1(@types/node@20.19.9)':
- dependencies:
- '@inquirer/checkbox': 4.2.0(@types/node@20.19.9)
- '@inquirer/confirm': 5.1.14(@types/node@20.19.9)
- '@inquirer/editor': 4.2.15(@types/node@20.19.9)
- '@inquirer/expand': 4.0.17(@types/node@20.19.9)
- '@inquirer/input': 4.2.1(@types/node@20.19.9)
- '@inquirer/number': 3.0.17(@types/node@20.19.9)
- '@inquirer/password': 4.0.17(@types/node@20.19.9)
- '@inquirer/rawlist': 4.1.5(@types/node@20.19.9)
- '@inquirer/search': 3.0.17(@types/node@20.19.9)
- '@inquirer/select': 4.3.1(@types/node@20.19.9)
+ '@types/node': 22.16.3
+
+ '@inquirer/prompts@7.7.1(@types/node@22.16.3)':
+ dependencies:
+ '@inquirer/checkbox': 4.2.0(@types/node@22.16.3)
+ '@inquirer/confirm': 5.1.14(@types/node@22.16.3)
+ '@inquirer/editor': 4.2.15(@types/node@22.16.3)
+ '@inquirer/expand': 4.0.17(@types/node@22.16.3)
+ '@inquirer/input': 4.2.1(@types/node@22.16.3)
+ '@inquirer/number': 3.0.17(@types/node@22.16.3)
+ '@inquirer/password': 4.0.17(@types/node@22.16.3)
+ '@inquirer/rawlist': 4.1.5(@types/node@22.16.3)
+ '@inquirer/search': 3.0.17(@types/node@22.16.3)
+ '@inquirer/select': 4.3.1(@types/node@22.16.3)
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
- '@inquirer/rawlist@4.1.5(@types/node@20.19.9)':
+ '@inquirer/rawlist@4.1.5(@types/node@22.16.3)':
dependencies:
- '@inquirer/core': 10.1.15(@types/node@20.19.9)
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/core': 10.1.15(@types/node@22.16.3)
+ '@inquirer/type': 3.0.8(@types/node@22.16.3)
yoctocolors-cjs: 2.1.2
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
- '@inquirer/search@3.0.17(@types/node@20.19.9)':
+ '@inquirer/search@3.0.17(@types/node@22.16.3)':
dependencies:
- '@inquirer/core': 10.1.15(@types/node@20.19.9)
+ '@inquirer/core': 10.1.15(@types/node@22.16.3)
'@inquirer/figures': 1.0.13
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/type': 3.0.8(@types/node@22.16.3)
yoctocolors-cjs: 2.1.2
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
- '@inquirer/select@4.3.1(@types/node@20.19.9)':
+ '@inquirer/select@4.3.1(@types/node@22.16.3)':
dependencies:
- '@inquirer/core': 10.1.15(@types/node@20.19.9)
+ '@inquirer/core': 10.1.15(@types/node@22.16.3)
'@inquirer/figures': 1.0.13
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/type': 3.0.8(@types/node@22.16.3)
ansi-escapes: 4.3.2
yoctocolors-cjs: 2.1.2
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
- '@inquirer/type@3.0.8(@types/node@20.19.9)':
+ '@inquirer/type@3.0.7(@types/node@22.16.3)':
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
+
+ '@inquirer/type@3.0.8(@types/node@22.16.3)':
+ optionalDependencies:
+ '@types/node': 22.16.3
'@isaacs/balanced-match@4.0.1': {}
@@ -9719,10 +9774,10 @@ snapshots:
'@leichtgewicht/ip-codec@2.0.5': {}
- '@listr2/prompt-adapter-inquirer@3.0.1(@inquirer/prompts@7.7.1(@types/node@20.19.9))(@types/node@20.19.9)(listr2@9.0.1)':
+ '@listr2/prompt-adapter-inquirer@3.0.1(@inquirer/prompts@7.7.1(@types/node@22.16.3))(@types/node@22.16.3)(listr2@9.0.1)':
dependencies:
- '@inquirer/prompts': 7.7.1(@types/node@20.19.9)
- '@inquirer/type': 3.0.8(@types/node@20.19.9)
+ '@inquirer/prompts': 7.7.1(@types/node@22.16.3)
+ '@inquirer/type': 3.0.7(@types/node@22.16.3)
listr2: 9.0.1
transitivePeerDependencies:
- '@types/node'
@@ -10280,7 +10335,7 @@ snapshots:
'@types/accepts@1.3.7':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/babel__code-frame@7.0.6': {}
@@ -10310,16 +10365,16 @@ snapshots:
'@types/body-parser@1.19.6':
dependencies:
'@types/connect': 3.4.38
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/bonjour@3.5.13':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/browser-sync@2.29.0':
dependencies:
'@types/micromatch': 2.3.35
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/serve-static': 1.15.8
chokidar: 3.6.0
@@ -10331,7 +10386,7 @@ snapshots:
'@types/co-body@6.1.3':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/qs': 6.14.0
'@types/command-line-args@5.2.3': {}
@@ -10339,11 +10394,11 @@ snapshots:
'@types/connect-history-api-fallback@1.5.4':
dependencies:
'@types/express-serve-static-core': 4.19.6
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/connect@3.4.38':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/content-disposition@0.5.9': {}
@@ -10354,11 +10409,11 @@ snapshots:
'@types/connect': 3.4.38
'@types/express': 5.0.3
'@types/keygrip': 1.0.6
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/cors@2.8.19':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/debounce@1.2.4': {}
@@ -10366,7 +10421,7 @@ snapshots:
'@types/duplexify@3.6.4':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/eslint-scope@3.7.7':
dependencies:
@@ -10382,14 +10437,14 @@ snapshots:
'@types/express-serve-static-core@4.19.6':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/qs': 6.14.0
'@types/range-parser': 1.2.7
'@types/send': 0.17.5
'@types/express-serve-static-core@5.0.7':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/qs': 6.14.0
'@types/range-parser': 1.2.7
'@types/send': 0.17.5
@@ -10409,7 +10464,7 @@ snapshots:
'@types/graceful-fs@4.1.9':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/http-assert@1.5.6': {}
@@ -10417,7 +10472,7 @@ snapshots:
'@types/http-proxy@1.17.16':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/ini@4.1.1': {}
@@ -10443,7 +10498,7 @@ snapshots:
'@types/karma@6.3.9':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
log4js: 6.9.1
transitivePeerDependencies:
- supports-color
@@ -10463,13 +10518,13 @@ snapshots:
'@types/http-errors': 2.0.5
'@types/keygrip': 1.0.6
'@types/koa-compose': 3.2.8
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/less@3.0.8': {}
'@types/loader-utils@2.0.6':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/webpack': 4.41.40
'@types/lodash@4.17.20': {}
@@ -10482,22 +10537,26 @@ snapshots:
'@types/node-fetch@2.6.12':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
form-data: 4.0.4
'@types/node-forge@1.3.13':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/node@20.19.9':
dependencies:
undici-types: 6.21.0
+ '@types/node@22.16.3':
+ dependencies:
+ undici-types: 6.21.0
+
'@types/npm-package-arg@6.1.4': {}
'@types/npm-registry-fetch@8.0.8':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/node-fetch': 2.6.12
'@types/npm-package-arg': 6.1.4
'@types/npmlog': 7.0.0
@@ -10505,11 +10564,11 @@ snapshots:
'@types/npmlog@7.0.0':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/pacote@11.1.8':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/npm-registry-fetch': 8.0.8
'@types/npmlog': 7.0.0
'@types/ssri': 7.1.5
@@ -10527,7 +10586,7 @@ snapshots:
'@types/pumpify@1.4.4':
dependencies:
'@types/duplexify': 3.6.4
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/q@0.0.32': {}
@@ -10538,7 +10597,7 @@ snapshots:
'@types/request@2.48.12':
dependencies:
'@types/caseless': 0.12.5
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/tough-cookie': 4.0.5
form-data: 2.5.5
@@ -10555,7 +10614,7 @@ snapshots:
'@types/send@0.17.5':
dependencies:
'@types/mime': 1.3.5
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/serve-index@1.9.4':
dependencies:
@@ -10564,23 +10623,23 @@ snapshots:
'@types/serve-static@1.15.8':
dependencies:
'@types/http-errors': 2.0.5
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/send': 0.17.5
'@types/shelljs@0.8.17':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
glob: 11.0.3
'@types/sockjs@0.3.36':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/source-list-map@0.1.6': {}
'@types/ssri@7.1.5':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/stack-trace@0.0.33': {}
@@ -10599,17 +10658,17 @@ snapshots:
'@types/watchpack@2.4.4':
dependencies:
'@types/graceful-fs': 4.1.9
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/webpack-sources@3.2.3':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/source-list-map': 0.1.6
source-map: 0.7.4
'@types/webpack@4.41.40':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/tapable': 1.0.12
'@types/uglify-js': 3.17.5
'@types/webpack-sources': 3.2.3
@@ -10618,11 +10677,11 @@ snapshots:
'@types/ws@7.4.7':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/ws@8.18.1':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
'@types/yargs-parser@21.0.3': {}
@@ -10634,7 +10693,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
optional: true
'@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.9.0-beta))(eslint@9.31.0(jiti@1.21.7))(typescript@5.9.0-beta)':
@@ -10883,9 +10942,9 @@ snapshots:
lodash: 4.17.21
minimatch: 7.4.6
- '@vitejs/plugin-basic-ssl@2.1.0(vite@7.0.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))':
+ '@vitejs/plugin-basic-ssl@2.1.0(vite@7.0.5(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))':
dependencies:
- vite: 7.0.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
+ vite: 7.0.5(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
'@vitest/expect@3.2.4':
dependencies:
@@ -10895,13 +10954,13 @@ snapshots:
chai: 5.2.1
tinyrainbow: 2.0.0
- '@vitest/mocker@3.2.4(vite@7.0.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))':
+ '@vitest/mocker@3.2.4(vite@7.0.2(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
- vite: 7.0.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
+ vite: 7.0.2(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -11799,7 +11858,7 @@ snapshots:
chrome-launcher@0.15.2:
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
escape-string-regexp: 4.0.0
is-wsl: 2.2.0
lighthouse-logger: 1.4.2
@@ -12351,7 +12410,7 @@ snapshots:
engine.io@6.6.4:
dependencies:
'@types/cors': 2.8.19
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.7.2
@@ -13824,7 +13883,7 @@ snapshots:
jest-worker@27.5.1:
dependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
merge-stream: 2.0.0
supports-color: 8.1.1
@@ -15077,7 +15136,7 @@ snapshots:
'@protobufjs/path': 1.1.2
'@protobufjs/pool': 1.1.0
'@protobufjs/utf8': 1.1.0
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
long: 5.3.2
protractor@7.0.0:
@@ -15440,12 +15499,12 @@ snapshots:
optionalDependencies:
'@babel/code-frame': 7.27.1
- rollup-plugin-sourcemaps2@0.5.3(@types/node@20.19.9)(rollup@4.45.1):
+ rollup-plugin-sourcemaps2@0.5.3(@types/node@22.16.3)(rollup@4.45.1):
dependencies:
'@rollup/pluginutils': 5.2.0(rollup@4.45.1)
rollup: 4.45.1
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
rollup@4.45.1:
dependencies:
@@ -16249,14 +16308,14 @@ snapshots:
dependencies:
typescript: 5.9.0-beta
- ts-node@10.9.2(@types/node@20.19.9)(typescript@5.9.0-beta):
+ ts-node@10.9.2(@types/node@22.16.3)(typescript@5.9.0-beta):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
acorn: 8.15.0
acorn-walk: 8.3.4
arg: 4.1.3
@@ -16539,13 +16598,13 @@ snapshots:
core-util-is: 1.0.2
extsprintf: 1.3.0
- vite-node@3.2.4(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0):
+ vite-node@3.2.4(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0):
dependencies:
cac: 6.7.14
debug: 4.4.1(supports-color@10.0.0)
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 7.0.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
+ vite: 7.0.5(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -16560,7 +16619,7 @@ snapshots:
- tsx
- yaml
- vite@7.0.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0):
+ vite@7.0.2(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0):
dependencies:
esbuild: 0.25.8
fdir: 6.4.6(picomatch@4.0.3)
@@ -16569,7 +16628,7 @@ snapshots:
rollup: 4.45.1
tinyglobby: 0.2.14
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
fsevents: 2.3.3
jiti: 1.21.7
less: 4.4.0
@@ -16577,11 +16636,28 @@ snapshots:
terser: 5.43.1
yaml: 2.8.0
- vitest@3.2.4(@types/node@20.19.9)(jiti@1.21.7)(jsdom@26.1.0)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0):
+ vite@7.0.5(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0):
+ dependencies:
+ esbuild: 0.25.8
+ fdir: 6.4.6(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.45.1
+ tinyglobby: 0.2.14
+ optionalDependencies:
+ '@types/node': 22.16.3
+ fsevents: 2.3.3
+ jiti: 1.21.7
+ less: 4.4.0
+ sass: 1.89.2
+ terser: 5.43.1
+ yaml: 2.8.0
+
+ vitest@3.2.4(@types/node@22.16.3)(jiti@1.21.7)(jsdom@26.1.0)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
- '@vitest/mocker': 3.2.4(vite@7.0.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
+ '@vitest/mocker': 3.2.4(vite@7.0.2(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
@@ -16599,11 +16675,11 @@ snapshots:
tinyglobby: 0.2.14
tinypool: 1.1.1
tinyrainbow: 2.0.0
- vite: 7.0.5(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
- vite-node: 3.2.4(@types/node@20.19.9)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
+ vite: 7.0.2(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
+ vite-node: 3.2.4(@types/node@22.16.3)(jiti@1.21.7)(less@4.4.0)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
why-is-node-running: 2.3.0
optionalDependencies:
- '@types/node': 20.19.9
+ '@types/node': 22.16.3
jsdom: 26.1.0
transitivePeerDependencies:
- jiti
diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel
index 4804a9ee59a6..65954dc5e6bc 100644
--- a/tools/BUILD.bazel
+++ b/tools/BUILD.bazel
@@ -30,3 +30,11 @@ js_binary(
],
entry_point = "quicktype_runner.js",
)
+
+js_binary(
+ name = "ng_example_db",
+ data = [
+ "example_db_generator.js",
+ ],
+ entry_point = "example_db_generator.js",
+)
diff --git a/tools/example_db_generator.bzl b/tools/example_db_generator.bzl
new file mode 100644
index 000000000000..7a899e3d8de4
--- /dev/null
+++ b/tools/example_db_generator.bzl
@@ -0,0 +1,12 @@
+load("@aspect_rules_js//js:defs.bzl", "js_run_binary")
+
+def cli_example_db(name, srcs, path, out, data = []):
+ js_run_binary(
+ name = name,
+ outs = [out],
+ srcs = srcs + data,
+ tool = "//tools:ng_example_db",
+ progress_message = "Generating code example database from %s" % path,
+ mnemonic = "NgExampleSqliteDb",
+ args = [path, "$(rootpath %s)" % out],
+ )
diff --git a/tools/example_db_generator.js b/tools/example_db_generator.js
new file mode 100644
index 000000000000..f55303ce6d46
--- /dev/null
+++ b/tools/example_db_generator.js
@@ -0,0 +1,64 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.dev/license
+ */
+
+const { readdirSync, readFileSync, mkdirSync, existsSync, rmSync } = require('node:fs');
+const { resolve, dirname } = require('node:path');
+const { DatabaseSync } = require('node:sqlite');
+
+function generate(inPath, outPath) {
+ const examples = [];
+
+ const entries = readdirSync(resolve(inPath), { withFileTypes: true });
+ for (const entry of entries) {
+ if (!entry.isFile()) {
+ continue;
+ }
+
+ examples.push(readFileSync(resolve(inPath, entry.name), 'utf-8'));
+ }
+
+ const dbPath = outPath;
+ mkdirSync(dirname(outPath), { recursive: true });
+
+ if (existsSync(dbPath)) {
+ rmSync(dbPath);
+ }
+ const db = new DatabaseSync(dbPath);
+
+ db.exec(`CREATE VIRTUAL TABLE examples USING fts5(content, tokenize = 'porter ascii');`);
+
+ const insertStatement = db.prepare('INSERT INTO examples(content) VALUES(?);');
+
+ db.exec('BEGIN TRANSACTION');
+ for (const example of examples) {
+ insertStatement.run(example);
+ }
+ db.exec('END TRANSACTION');
+
+ db.close();
+}
+
+if (require.main === module) {
+ const argv = process.argv.slice(2);
+ if (argv.length !== 2) {
+ console.error('Must include 2 arguments.');
+ process.exit(1);
+ }
+
+ const [inPath, outPath] = argv;
+
+ try {
+ generate(inPath, outPath);
+ } catch (error) {
+ console.error('An error happened:');
+ console.error(error);
+ process.exit(127);
+ }
+}
+
+exports.generate = generate;