From b447fa55eaebe877ca3d3b638194cd67d6450e93 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 25 Aug 2025 13:10:30 +0000 Subject: [PATCH 1/3] fix(@schematics/angular): prevent AI config schematic from failing when 'none' and other AI tools are selected When using 'ng new' with the interactive AI tool prompt, the 'none' option remains selected even after a user selects another tool. This results in a validation error because the schematic receives an array with both 'none' and the selected tool. This fix filters out the 'none' value from the `tool` option if any other tool is also present, allowing the schematic to proceed. Closes: #30987 --- .../transformers/replace_resources_spec.ts | 2 +- .../schematics/angular/ai-config/index.ts | 37 +++++++++---------- .../angular/ai-config/index_spec.ts | 9 +++-- .../schematics/angular/ai-config/schema.json | 18 +-------- .../schematics/angular/ng-new/index_spec.ts | 2 +- 5 files changed, 25 insertions(+), 43 deletions(-) diff --git a/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts b/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts index e4c905f86430..102d6e1e0879 100644 --- a/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts +++ b/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts @@ -105,7 +105,7 @@ describe('@ngtools/webpack transformers', () => { expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); }); - it('should should support svg as templates', () => { + it('should support svg as templates', () => { const input = tags.stripIndent` import { Component } from '@angular/core'; diff --git a/packages/schematics/angular/ai-config/index.ts b/packages/schematics/angular/ai-config/index.ts index 6790bc4a1c1d..4d234b35e5d4 100644 --- a/packages/schematics/angular/ai-config/index.ts +++ b/packages/schematics/angular/ai-config/index.ts @@ -19,9 +19,7 @@ import { } from '@angular-devkit/schematics'; import { Schema as ConfigOptions, Tool } from './schema'; -type ToolWithoutNone = Exclude; - -const AI_TOOLS: { [key in ToolWithoutNone]: ContextFileInfo } = { +const AI_TOOLS: { [key in Exclude]: ContextFileInfo } = { gemini: { rulesName: 'GEMINI.md', directory: '.gemini', @@ -57,26 +55,25 @@ interface ContextFileInfo { } export default function ({ tool }: ConfigOptions): Rule { - if (!tool || tool.includes(Tool.None)) { + if (!tool) { return noop(); } - const files: ContextFileInfo[] = (tool as ToolWithoutNone[]).map( - (selectedTool) => AI_TOOLS[selectedTool], - ); - - const rules = files.map(({ rulesName, directory, frontmatter }) => - mergeWith( - apply(url('./files'), [ - applyTemplates({ - ...strings, - rulesName, - frontmatter, - }), - move(directory), - ]), - ), - ); + const rules = tool + .filter((tool) => tool !== Tool.None) + .map((selectedTool) => AI_TOOLS[selectedTool]) + .map(({ rulesName, directory, frontmatter }) => + mergeWith( + apply(url('./files'), [ + applyTemplates({ + ...strings, + rulesName, + frontmatter, + }), + move(directory), + ]), + ), + ); return chain(rules); } diff --git a/packages/schematics/angular/ai-config/index_spec.ts b/packages/schematics/angular/ai-config/index_spec.ts index 45518f7d17d6..d21186be408a 100644 --- a/packages/schematics/angular/ai-config/index_spec.ts +++ b/packages/schematics/angular/ai-config/index_spec.ts @@ -72,13 +72,14 @@ describe('Ai Config Schematic', () => { expect(tree.exists('.cursor/rules/cursor.mdc')).toBeTruthy(); }); - it('should error is None is associated with other values', () => { - return expectAsync(runConfigSchematic([ConfigTool.None, ConfigTool.Cursor])).toBeRejected(); - }); - it('should not create any files if None is selected', async () => { const filesCount = workspaceTree.files.length; const tree = await runConfigSchematic([ConfigTool.None]); expect(tree.files.length).toBe(filesCount); }); + + it('should create for tool if None and Gemini are selected', async () => { + const tree = await runConfigSchematic([ConfigTool.Gemini, ConfigTool.None]); + expect(tree.exists('.gemini/GEMINI.md')).toBeTruthy(); + }); }); diff --git a/packages/schematics/angular/ai-config/schema.json b/packages/schematics/angular/ai-config/schema.json index 8595f9f7c558..3f46fbc6dede 100644 --- a/packages/schematics/angular/ai-config/schema.json +++ b/packages/schematics/angular/ai-config/schema.json @@ -9,7 +9,7 @@ "tool": { "type": "array", "uniqueItems": true, - "default": "none", + "default": ["none"], "x-prompt": { "message": "Which AI tools do you want to configure with Angular best practices? https://angular.dev/ai/develop-with-ai", "type": "list", @@ -50,21 +50,5 @@ "enum": ["none", "gemini", "copilot", "claude", "cursor", "jetbrains", "windsurf"] } } - }, - "if": { - "properties": { - "tool": { - "contains": { - "const": "none" - } - } - } - }, - "then": { - "properties": { - "tool": { - "maxItems": 1 - } - } } } diff --git a/packages/schematics/angular/ng-new/index_spec.ts b/packages/schematics/angular/ng-new/index_spec.ts index 0b0334ba3432..0d202bffa590 100644 --- a/packages/schematics/angular/ng-new/index_spec.ts +++ b/packages/schematics/angular/ng-new/index_spec.ts @@ -58,7 +58,7 @@ describe('Ng New Schematic', () => { ); }); - it('should should set the prefix in angular.json and in app.ts', async () => { + it('should set the prefix in angular.json and in app.ts', async () => { const options = { ...defaultOptions, prefix: 'pre' }; const tree = await schematicRunner.runSchematic('ng-new', options); From f7edab01044df16c30c9a938ad7ddaa68868a30b Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 25 Aug 2025 13:31:47 +0000 Subject: [PATCH 2/3] fix(@angular/cli): correctly set default array values Previously, default array values were being incorrectly treated as strings. --- .../src/command-builder/schematics-command-module.ts | 11 +++++++++-- .../cli/src/command-builder/utilities/json-schema.ts | 8 ++++++-- .../src/command-builder/utilities/json-schema_spec.ts | 4 ++-- .../browser/specs/unused-files-warning_spec.ts | 2 +- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/angular/cli/src/command-builder/schematics-command-module.ts b/packages/angular/cli/src/command-builder/schematics-command-module.ts index 529d47b078f1..ef317700d1a6 100644 --- a/packages/angular/cli/src/command-builder/schematics-command-module.ts +++ b/packages/angular/cli/src/command-builder/schematics-command-module.ts @@ -204,13 +204,20 @@ export abstract class SchematicsCommandModule ? { name: item, value: item, - checked: item === definition.default, + checked: + definition.multiselect && Array.isArray(definition.default) + ? definition.default?.includes(item) + : item === definition.default, } : { ...item, name: item.label, value: item.value, - checked: item.value === definition.default, + checked: + definition.multiselect && Array.isArray(definition.default) + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + definition.default?.includes(item.value as any) + : item.value === definition.default, }, ), }); diff --git a/packages/angular/cli/src/command-builder/utilities/json-schema.ts b/packages/angular/cli/src/command-builder/utilities/json-schema.ts index 90c619dc024e..869cab6abe4d 100644 --- a/packages/angular/cli/src/command-builder/utilities/json-schema.ts +++ b/packages/angular/cli/src/command-builder/utilities/json-schema.ts @@ -197,15 +197,19 @@ export async function parseJsonSchemaToOptions( .filter((value) => isValidTypeForEnum(typeof value)) .sort() as (string | true | number)[]; - let defaultValue: string | number | boolean | undefined = undefined; + let defaultValue: string | number | boolean | unknown[] | undefined = undefined; if (current.default !== undefined) { switch (types[0]) { case 'string': - case 'array': if (typeof current.default == 'string') { defaultValue = current.default; } break; + case 'array': + if (Array.isArray(current.default)) { + defaultValue = current.default; + } + break; case 'number': if (typeof current.default == 'number') { defaultValue = current.default; diff --git a/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts b/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts index cc86cc99dddc..ea7043339d65 100644 --- a/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts +++ b/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts @@ -37,7 +37,7 @@ describe('parseJsonSchemaToOptions', () => { }, 'arrayWithChoices': { 'type': 'array', - 'default': 'default-array', + 'default': ['default-array'], 'items': { 'type': 'string', 'enum': ['always', 'never', 'default-array'], @@ -91,7 +91,7 @@ describe('parseJsonSchemaToOptions', () => { }); it('should add default value to help', async () => { - expect(await localYargs.getHelp()).toContain('[default: "default-array"]'); + expect(await localYargs.getHelp()).toContain('[default: ["default-array"]]'); }); }); diff --git a/packages/angular_devkit/build_angular/src/builders/browser/specs/unused-files-warning_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/specs/unused-files-warning_spec.ts index 6e08bedfb5b6..b25d599f18a4 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser/specs/unused-files-warning_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/specs/unused-files-warning_spec.ts @@ -238,7 +238,7 @@ describe('Browser Builder unused files warnings', () => { host.appendToFile('src/main.ts', ''); break; case 2: - // The second should should have type.ts as unused but shouldn't warn. + // The second should have type.ts as unused but shouldn't warn. expect(logs.join().includes(warningMessageSuffix)).toBe( false, `Case ${buildNumber} failed.`, From 08f4212ae70b8f76e88b20d344104ec114aa9f4e Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 25 Aug 2025 13:32:10 +0000 Subject: [PATCH 3/3] fix(@angular-devkit/schematics-cli): correctly set default array values Previously, default array values were being incorrectly treated as strings. --- .../schematics_cli/bin/schematics.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/angular_devkit/schematics_cli/bin/schematics.ts b/packages/angular_devkit/schematics_cli/bin/schematics.ts index 2c71e0698ad4..8e9779728a77 100644 --- a/packages/angular_devkit/schematics_cli/bin/schematics.ts +++ b/packages/angular_devkit/schematics_cli/bin/schematics.ts @@ -93,7 +93,6 @@ function _createPromptProvider(): schema.PromptProvider { definition.multiselect ? prompts.checkbox : prompts.select )({ message: definition.message, - default: definition.default, validate: (values) => { if (!definition.validator) { return true; @@ -101,15 +100,26 @@ function _createPromptProvider(): schema.PromptProvider { return definition.validator(Object.values(values).map(({ value }) => value)); }, - choices: definition.items.map((item) => + default: definition.multiselect ? undefined : definition.default, + choices: definition.items?.map((item) => typeof item == 'string' ? { name: item, value: item, + checked: + definition.multiselect && Array.isArray(definition.default) + ? definition.default?.includes(item) + : item === definition.default, } : { + ...item, name: item.label, value: item.value, + checked: + definition.multiselect && Array.isArray(definition.default) + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + definition.default?.includes(item.value as any) + : item.value === definition.default, }, ), });