diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index 0cc2265fb..eea65ef86 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -117,6 +117,46 @@ function importStyles(): Rule { }; } +export function addAIConfig(): Rule { + return (tree: Tree) => { + const mcpFilePath = "/.vscode/mcp.json"; + const igniteuiServer = { + command: "npx", + args: ["-y", "igniteui-cli@next", "mcp"] + }; + const igniteuiThemingServer = { + command: "npx", + args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] + }; + + if (tree.exists(mcpFilePath)) { + const content = JSON.parse(tree.read(mcpFilePath)!.toString()); + const servers = content.servers ?? {}; + let modified = false; + if (!servers["igniteui"]) { + servers["igniteui"] = igniteuiServer; + modified = true; + } + if (!servers["igniteui-theming"]) { + servers["igniteui-theming"] = igniteuiThemingServer; + modified = true; + } + if (modified) { + content.servers = servers; + tree.overwrite(mcpFilePath, JSON.stringify(content, null, 2)); + } + } else { + const mcpConfig = { + servers: { + "igniteui": igniteuiServer, + "igniteui-theming": igniteuiThemingServer + } + }; + tree.create(mcpFilePath, JSON.stringify(mcpConfig, null, 2)); + } + }; +} + export default function (): Rule { return (tree: Tree) => { setVirtual(tree); @@ -125,7 +165,8 @@ export default function (): Rule { addTypographyToProj(), importBrowserAnimations(), createCliConfig(), - displayVersionMismatch() + displayVersionMismatch(), + addAIConfig() ]); }; } diff --git a/packages/ng-schematics/src/cli-config/index_spec.ts b/packages/ng-schematics/src/cli-config/index_spec.ts index afdc825db..128a22c32 100644 --- a/packages/ng-schematics/src/cli-config/index_spec.ts +++ b/packages/ng-schematics/src/cli-config/index_spec.ts @@ -309,4 +309,85 @@ export const appConfig: ApplicationConfig = { await runner.runSchematic("cli-config", {}, tree); expect(warns).toContain(jasmine.stringMatching(pattern)); }); + + describe("addAIConfig", () => { + const mcpFilePath = "/.vscode/mcp.json"; + + it("should create .vscode/mcp.json with both servers when file does not exist", async () => { + await runner.runSchematic("cli-config", {}, tree); + + expect(tree.exists(mcpFilePath)).toBeTruthy(); + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }); + expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); + }); + + it("should add both servers to existing .vscode/mcp.json that has no servers", async () => { + tree.create(mcpFilePath, JSON.stringify({ servers: {} })); + + await runner.runSchematic("cli-config", {}, tree); + + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }); + expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); + }); + + it("should add missing igniteui-theming server if only igniteui is already present", async () => { + tree.create(mcpFilePath, JSON.stringify({ + servers: { + "igniteui": { command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] } + } + })); + + await runner.runSchematic("cli-config", {}, tree); + + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }); + expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); + }); + + it("should add missing igniteui server if only igniteui-theming is already present", async () => { + tree.create(mcpFilePath, JSON.stringify({ + servers: { + "igniteui-theming": { command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] } + } + })); + + await runner.runSchematic("cli-config", {}, tree); + + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }); + expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); + }); + + it("should not modify .vscode/mcp.json if both servers are already present", async () => { + const existing = { + servers: { + "igniteui": { command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }, + "igniteui-theming": { command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] } + } + }; + tree.create(mcpFilePath, JSON.stringify(existing)); + + await runner.runSchematic("cli-config", {}, tree); + + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content).toEqual(existing); + }); + + it("should preserve existing servers when adding igniteui servers", async () => { + tree.create(mcpFilePath, JSON.stringify({ + servers: { + "other-server": { command: "node", args: ["server.js"] } + } + })); + + await runner.runSchematic("cli-config", {}, tree); + + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.servers["other-server"]).toEqual({ command: "node", args: ["server.js"] }); + expect(content.servers["igniteui"]).toBeDefined(); + expect(content.servers["igniteui-theming"]).toBeDefined(); + }); + }); }); diff --git a/packages/ng-schematics/src/ng-new/index.ts b/packages/ng-schematics/src/ng-new/index.ts index 01f844173..bb4deb766 100644 --- a/packages/ng-schematics/src/ng-new/index.ts +++ b/packages/ng-schematics/src/ng-new/index.ts @@ -17,6 +17,7 @@ import { defer, Observable } from "rxjs"; import { NewProjectOptions } from "../app-projects/schema"; import { SchematicsPromptSession } from "../prompt/SchematicsPromptSession"; import { SchematicsTemplateManager } from "../SchematicsTemplateManager"; +import { addAIConfig } from "../cli-config/index"; import { setVirtual } from "../utils/NgFileSystem"; import { OptionsSchema } from "./schema"; @@ -150,6 +151,7 @@ export function newProject(options: OptionsSchema): Rule { }); } }, + addAIConfig(), (_tree: Tree, _context: IgxSchematicContext) => { return move(options.name!); } diff --git a/packages/ng-schematics/src/ng-new/index_spec.ts b/packages/ng-schematics/src/ng-new/index_spec.ts index 64e41ff27..702651cb7 100644 --- a/packages/ng-schematics/src/ng-new/index_spec.ts +++ b/packages/ng-schematics/src/ng-new/index_spec.ts @@ -136,7 +136,7 @@ describe("Schematics ng-new", () => { expect(mockFunc[1]).toHaveBeenCalled(); } expect(AppProjectSchematic.default).toHaveBeenCalled(); - expect(e.files.length).toEqual(1); + expect(e.files.length).toEqual(2); expect(e.exists(`${workingDirectory}/.gitignore`)).toBeTruthy(); const taskOptions = runner.tasks.map(task => task.options); const expectedInstall: NodePackageTaskOptions = { @@ -189,7 +189,7 @@ describe("Schematics ng-new", () => { runner.runSchematic("ng-new", { version: "8.0.3", name: workingDirectory }, myTree) .then((e: UnitTestTree) => { expect(AppProjectSchematic.default).toHaveBeenCalled(); - expect(e.files.length).toEqual(1); + expect(e.files.length).toEqual(2); expect(e.exists(`${workingDirectory}/.gitignore`)).toBeTruthy(); const taskOptions = runner.tasks.map(task => task.options); const expectedInstall: NodePackageTaskOptions = { @@ -212,4 +212,34 @@ describe("Schematics ng-new", () => { expect(taskOptions).toContain(expectedInit); }); }); + + describe("addAIConfig via ng-new", () => { + const workingDirectory = "my-test-project"; + const mcpFilePath = `${workingDirectory}/.vscode/mcp.json`; + + function setupAndRun(runner: SchematicTestRunner, myTree: Tree): Promise { + spyOn(AppProjectSchematic, "default").and.returnValue((currentTree: Tree, _context: SchematicContext) => { + currentTree.create("gitignore", ""); + return currentTree; + }); + + const userAnswers = new Map(); + userAnswers.set("upgradePackages", false); + spyOnProperty(SchematicsPromptSession.prototype, "userAnswers", "get").and.returnValue(userAnswers); + + return runner.runSchematic("ng-new", { version: "8.0.3", name: workingDirectory, skipInstall: true, skipGit: true }, myTree); + } + + it("should create .vscode/mcp.json with both servers during ng-new", async () => { + const runner = new SchematicTestRunner("schematics", collectionPath); + const myTree = Tree.empty(); + + const e = await setupAndRun(runner, myTree); + + expect(e.exists(mcpFilePath)).toBeTruthy(); + const content = JSON.parse(e.readContent(mcpFilePath)); + expect(content.servers["igniteui"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli@next", "mcp"] }); + expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); + }); + }); });