Skip to content

Commit a9acc2e

Browse files
committed
fix(@angular/cli): add schema versioning and metadata to example database
This commit introduces schema versioning and metadata to the Angular CLI's example database. A new `metadata` table has been added to both the runtime and build-time example databases. This table stores key-value pairs, initially including `schema_version` and `created_at`. The `find_examples` tool now validates the `schema_version` of the loaded database. If a schema mismatch is detected, the tool throws a descriptive error message that guides the user on how to resolve the incompatibility (e.g., by updating their CLI or project's `@angular/core` package). This change improves the robustness and maintainability of the example database by: - Ensuring compatibility between the CLI and the example database schema. - Providing actionable feedback to users when schema versions are incompatible. - Laying the groundwork for future metadata additions and database evolution.
1 parent 42a6da9 commit a9acc2e

File tree

2 files changed

+67
-8
lines changed

2 files changed

+67
-8
lines changed

packages/angular/cli/src/commands/mcp/tools/examples.ts

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ const findExampleInputSchema = z.object({
2020
.describe(
2121
'The absolute path to the `angular.json` file for the workspace. This is used to find the ' +
2222
'version-specific code examples that correspond to the installed version of the ' +
23-
'Angular framework. You **MUST** get this path from the `list_projects` tool. If omitted, ' +
24-
'the tool will search the generic code examples bundled with the CLI.',
23+
'Angular framework. You **MUST** get this path from the `list_projects` tool. ' +
24+
'If omitted, the tool will search the generic code examples bundled with the CLI.',
2525
),
2626
query: z
2727
.string()
@@ -306,28 +306,60 @@ async function createFindExampleHandler({ logger, exampleDatabasePath }: McpTool
306306
return queryDatabase(runtimeDb, input);
307307
}
308308

309-
let dbPath: string | undefined;
309+
let resolvedDbPath: string | undefined;
310+
let dbSource: string | undefined;
310311

311312
// First, try to get the version-specific guide.
312313
if (input.workspacePath) {
313314
const versionSpecific = await getVersionSpecificExampleDatabase(input.workspacePath, logger);
314315
if (versionSpecific) {
315-
dbPath = versionSpecific.dbPath;
316+
resolvedDbPath = versionSpecific.dbPath;
317+
dbSource = versionSpecific.source;
316318
}
317319
}
318320

319321
// If the version-specific guide was not found for any reason, fall back to the bundled version.
320-
if (!dbPath) {
321-
dbPath = exampleDatabasePath;
322+
if (!resolvedDbPath) {
323+
resolvedDbPath = exampleDatabasePath;
324+
dbSource = 'bundled';
322325
}
323326

324-
if (!dbPath) {
327+
if (!resolvedDbPath) {
325328
// This should be prevented by the registration logic in mcp-server.ts
326329
throw new Error('Example database path is not available.');
327330
}
328331

329332
const { DatabaseSync } = await import('node:sqlite');
330-
const db = new DatabaseSync(dbPath, { readOnly: true });
333+
const db = new DatabaseSync(resolvedDbPath, { readOnly: true });
334+
335+
// Validate the schema version of the database.
336+
const EXPECTED_SCHEMA_VERSION = 1;
337+
const schemaVersionResult = db
338+
.prepare('SELECT value FROM metadata WHERE key = ?')
339+
.get('schema_version') as { value: string } | undefined;
340+
const actualSchemaVersion = schemaVersionResult ? Number(schemaVersionResult.value) : undefined;
341+
342+
if (actualSchemaVersion !== EXPECTED_SCHEMA_VERSION) {
343+
db.close();
344+
345+
let errorMessage: string;
346+
if (actualSchemaVersion === undefined) {
347+
errorMessage = 'The example database is missing a schema version and cannot be used.';
348+
} else if (actualSchemaVersion > EXPECTED_SCHEMA_VERSION) {
349+
errorMessage =
350+
`This project's example database (version ${actualSchemaVersion})` +
351+
` is newer than what this version of the Angular CLI supports (version ${EXPECTED_SCHEMA_VERSION}).` +
352+
' Please update your `@angular/cli` package to a newer version.';
353+
} else {
354+
errorMessage =
355+
`This version of the Angular CLI (expects schema version ${EXPECTED_SCHEMA_VERSION})` +
356+
` requires a newer example database than the one found in this project (version ${actualSchemaVersion}).`;
357+
}
358+
359+
throw new Error(
360+
`Incompatible example database schema from source '${dbSource}':\n${errorMessage}`,
361+
);
362+
}
331363

332364
return queryDatabase(db, input);
333365
};
@@ -571,6 +603,19 @@ async function setupRuntimeExamples(examplesPath: string): Promise<DatabaseSync>
571603
const db = new DatabaseSync(':memory:');
572604

573605
// Create a relational table to store the structured example data.
606+
db.exec(`
607+
CREATE TABLE metadata (
608+
key TEXT PRIMARY KEY NOT NULL,
609+
value TEXT NOT NULL
610+
);
611+
`);
612+
613+
db.exec(`
614+
INSERT INTO metadata (key, value) VALUES
615+
('schema_version', '1'),
616+
('created_at', '${new Date().toISOString()}');
617+
`);
618+
574619
db.exec(`
575620
CREATE TABLE examples (
576621
id INTEGER PRIMARY KEY,

tools/example_db_generator.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,20 @@ function generate(inPath, outPath) {
8989
}
9090
const db = new DatabaseSync(dbPath);
9191

92+
// Create a table to store metadata.
93+
db.exec(`
94+
CREATE TABLE metadata (
95+
key TEXT PRIMARY KEY NOT NULL,
96+
value TEXT NOT NULL
97+
);
98+
`);
99+
100+
db.exec(`
101+
INSERT INTO metadata (key, value) VALUES
102+
('schema_version', '1'),
103+
('created_at', '${new Date().toISOString()}');
104+
`);
105+
92106
// Create a relational table to store the structured example data.
93107
db.exec(`
94108
CREATE TABLE examples (

0 commit comments

Comments
 (0)