@angular/cli 20.2.0-rc.1 → 20.2.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/cli",
3
- "version": "20.2.0-rc.1",
3
+ "version": "20.2.0",
4
4
  "description": "CLI tool for Angular",
5
5
  "main": "lib/cli/index.js",
6
6
  "bin": {
@@ -25,13 +25,13 @@
25
25
  },
26
26
  "homepage": "https://github.com/angular/angular-cli",
27
27
  "dependencies": {
28
- "@angular-devkit/architect": "0.2002.0-rc.1",
29
- "@angular-devkit/core": "20.2.0-rc.1",
30
- "@angular-devkit/schematics": "20.2.0-rc.1",
28
+ "@angular-devkit/architect": "0.2002.0",
29
+ "@angular-devkit/core": "20.2.0",
30
+ "@angular-devkit/schematics": "20.2.0",
31
31
  "@inquirer/prompts": "7.8.2",
32
32
  "@listr2/prompt-adapter-inquirer": "3.0.1",
33
- "@modelcontextprotocol/sdk": "1.17.2",
34
- "@schematics/angular": "20.2.0-rc.1",
33
+ "@modelcontextprotocol/sdk": "1.17.3",
34
+ "@schematics/angular": "20.2.0",
35
35
  "@yarnpkg/lockfile": "1.1.0",
36
36
  "algoliasearch": "5.35.0",
37
37
  "ini": "5.0.0",
@@ -47,14 +47,14 @@
47
47
  "ng-update": {
48
48
  "migrations": "@schematics/angular/migrations/migration-collection.json",
49
49
  "packageGroup": {
50
- "@angular/cli": "20.2.0-rc.1",
51
- "@angular/build": "20.2.0-rc.1",
52
- "@angular/ssr": "20.2.0-rc.1",
53
- "@angular-devkit/architect": "0.2002.0-rc.1",
54
- "@angular-devkit/build-angular": "20.2.0-rc.1",
55
- "@angular-devkit/build-webpack": "0.2002.0-rc.1",
56
- "@angular-devkit/core": "20.2.0-rc.1",
57
- "@angular-devkit/schematics": "20.2.0-rc.1"
50
+ "@angular/cli": "20.2.0",
51
+ "@angular/build": "20.2.0",
52
+ "@angular/ssr": "20.2.0",
53
+ "@angular-devkit/architect": "0.2002.0",
54
+ "@angular-devkit/build-angular": "20.2.0",
55
+ "@angular-devkit/build-webpack": "0.2002.0",
56
+ "@angular-devkit/core": "20.2.0",
57
+ "@angular-devkit/schematics": "20.2.0"
58
58
  }
59
59
  },
60
60
  "packageManager": "pnpm@10.14.0",
@@ -218,11 +218,17 @@ let CommandModule = (() => {
218
218
  ...Object.values(analytics_parameters_1.EventCustomMetric),
219
219
  ]);
220
220
  for (const [name, ua] of this.optionsWithAnalytics) {
221
+ if (!validEventCustomDimensionAndMetrics.has(ua)) {
222
+ continue;
223
+ }
221
224
  const value = options[name];
222
- if ((typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') &&
223
- validEventCustomDimensionAndMetrics.has(ua)) {
225
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
224
226
  parameters[ua] = value;
225
227
  }
228
+ else if (Array.isArray(value)) {
229
+ // GA doesn't allow array as values.
230
+ parameters[ua] = value.sort().join(', ');
231
+ }
226
232
  }
227
233
  return parameters;
228
234
  }
@@ -224,11 +224,13 @@ let SchematicsCommandModule = (() => {
224
224
  ? {
225
225
  name: item,
226
226
  value: item,
227
+ checked: item === definition.default,
227
228
  }
228
229
  : {
229
230
  ...item,
230
231
  name: item.label,
231
232
  value: item.value,
233
+ checked: item.value === definition.default,
232
234
  }),
233
235
  });
234
236
  break;
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { json } from '@angular-devkit/core';
9
9
  import type { Argv, Options as YargsOptions } from 'yargs';
10
+ import { EventCustomDimension } from '../../analytics/analytics-parameters';
10
11
  /**
11
12
  * An option description.
12
13
  */
@@ -48,4 +49,4 @@ export declare function parseJsonSchemaToOptions(registry: json.schema.SchemaReg
48
49
  *
49
50
  * @returns A map from option name to analytics configuration.
50
51
  */
51
- export declare function addSchemaOptionsToCommand<T>(localYargs: Argv<T>, options: Option[], includeDefaultValues: boolean): Map<string, string>;
52
+ export declare function addSchemaOptionsToCommand<T>(localYargs: Argv<T>, options: Option[], includeDefaultValues: boolean): Map<string, EventCustomDimension>;
@@ -10,7 +10,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.parseJsonSchemaToOptions = parseJsonSchemaToOptions;
11
11
  exports.addSchemaOptionsToCommand = addSchemaOptionsToCommand;
12
12
  const core_1 = require("@angular-devkit/core");
13
- function coerceToStringMap(dashedName, value) {
13
+ function checkStringMap(keyValuePairOptions, args) {
14
+ for (const key of keyValuePairOptions) {
15
+ const value = args[key];
16
+ if (!Array.isArray(value)) {
17
+ // Value has been parsed.
18
+ continue;
19
+ }
20
+ for (const pair of value) {
21
+ if (pair === undefined) {
22
+ continue;
23
+ }
24
+ if (!pair.includes('=')) {
25
+ throw new Error(`Invalid value for argument: ${key}, Given: '${pair}', Expected key=value pair`);
26
+ }
27
+ }
28
+ }
29
+ return true;
30
+ }
31
+ function coerceToStringMap(value) {
14
32
  const stringMap = {};
15
33
  for (const pair of value) {
16
34
  // This happens when the flag isn't passed at all.
@@ -19,14 +37,11 @@ function coerceToStringMap(dashedName, value) {
19
37
  }
20
38
  const eqIdx = pair.indexOf('=');
21
39
  if (eqIdx === -1) {
22
- // TODO: Remove workaround once yargs properly handles thrown errors from coerce.
23
- // Right now these sometimes end up as uncaught exceptions instead of proper validation
24
- // errors with usage output.
25
- return Promise.reject(new Error(`Invalid value for argument: ${dashedName}, Given: '${pair}', Expected key=value pair`));
40
+ // In the case it is not valid skip processing this option and handle the error in `checkStringMap`
41
+ return value;
26
42
  }
27
43
  const key = pair.slice(0, eqIdx);
28
- const value = pair.slice(eqIdx + 1);
29
- stringMap[key] = value;
44
+ stringMap[key] = pair.slice(eqIdx + 1);
30
45
  }
31
46
  return stringMap;
32
47
  }
@@ -79,7 +94,7 @@ async function parseJsonSchemaToOptions(registry, schema, interactive = true) {
79
94
  // Only include arrays if they're boolean, string or number.
80
95
  if (core_1.json.isJsonObject(current.items) &&
81
96
  typeof current.items.type == 'string' &&
82
- ['boolean', 'number', 'string'].includes(current.items.type)) {
97
+ isValidTypeForEnum(current.items.type)) {
83
98
  return true;
84
99
  }
85
100
  return false;
@@ -94,20 +109,18 @@ async function parseJsonSchemaToOptions(registry, schema, interactive = true) {
94
109
  return;
95
110
  }
96
111
  // Only keep enum values we support (booleans, numbers and strings).
97
- const enumValues = ((core_1.json.isJsonArray(current.enum) && current.enum) || []).filter((x) => {
98
- switch (typeof x) {
99
- case 'boolean':
100
- case 'number':
101
- case 'string':
102
- return true;
103
- default:
104
- return false;
105
- }
106
- });
112
+ const enumValues = ((core_1.json.isJsonArray(current.enum) && current.enum) ||
113
+ (core_1.json.isJsonObject(current.items) &&
114
+ core_1.json.isJsonArray(current.items.enum) &&
115
+ current.items.enum) ||
116
+ [])
117
+ .filter((value) => isValidTypeForEnum(typeof value))
118
+ .sort();
107
119
  let defaultValue = undefined;
108
120
  if (current.default !== undefined) {
109
121
  switch (types[0]) {
110
122
  case 'string':
123
+ case 'array':
111
124
  if (typeof current.default == 'string') {
112
125
  defaultValue = current.default;
113
126
  }
@@ -199,7 +212,7 @@ function addSchemaOptionsToCommand(localYargs, options, includeDefaultValues) {
199
212
  booleanOptionsWithNoPrefix.add(dashedName);
200
213
  }
201
214
  if (itemValueType) {
202
- keyValuePairOptions.add(name);
215
+ keyValuePairOptions.add(dashedName);
203
216
  }
204
217
  const sharedOptions = {
205
218
  alias,
@@ -207,7 +220,7 @@ function addSchemaOptionsToCommand(localYargs, options, includeDefaultValues) {
207
220
  description,
208
221
  deprecated,
209
222
  choices,
210
- coerce: itemValueType ? coerceToStringMap.bind(null, dashedName) : undefined,
223
+ coerce: itemValueType ? coerceToStringMap : undefined,
211
224
  // This should only be done when `--help` is used otherwise default will override options set in angular.json.
212
225
  ...(includeDefaultValues ? { default: defaultVal } : {}),
213
226
  };
@@ -229,6 +242,10 @@ function addSchemaOptionsToCommand(localYargs, options, includeDefaultValues) {
229
242
  optionsWithAnalytics.set(name, userAnalytics);
230
243
  }
231
244
  }
245
+ // Valid key/value options
246
+ if (keyValuePairOptions.size) {
247
+ localYargs.check(checkStringMap.bind(null, keyValuePairOptions), false);
248
+ }
232
249
  // Handle options which have been defined in the schema with `no` prefix.
233
250
  if (booleanOptionsWithNoPrefix.size) {
234
251
  localYargs.middleware((options) => {
@@ -242,3 +259,7 @@ function addSchemaOptionsToCommand(localYargs, options, includeDefaultValues) {
242
259
  }
243
260
  return optionsWithAnalytics;
244
261
  }
262
+ const VALID_ENUM_TYPES = new Set(['boolean', 'number', 'string']);
263
+ function isValidTypeForEnum(value) {
264
+ return VALID_ENUM_TYPES.has(value);
265
+ }
@@ -24,6 +24,8 @@ To start using the Angular CLI MCP Server, add this configuration to your host:
24
24
  }
25
25
 
26
26
  Exact configuration may differ depending on the host.
27
+
28
+ For more information and documentation, visit: https://angular.dev/ai/mcp
27
29
  `;
28
30
  class McpCommandModule extends command_module_1.CommandModule {
29
31
  command = 'mcp';
@@ -46,6 +48,8 @@ class McpCommandModule extends command_module_1.CommandModule {
46
48
  alias: 'E',
47
49
  array: true,
48
50
  describe: 'Enable an experimental tool.',
51
+ choices: mcp_server_1.EXPERIMENTAL_TOOLS.map(({ name }) => name),
52
+ hidden: true,
49
53
  });
50
54
  }
51
55
  async run(options) {
@@ -8,6 +8,17 @@
8
8
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
9
  import type { AngularWorkspace } from '../../utilities/config';
10
10
  import { AnyMcpToolDeclaration } from './tools/tool-registry';
11
+ /**
12
+ * The set of tools that are available but not enabled by default.
13
+ * These tools are considered experimental and may have limitations.
14
+ */
15
+ export declare const EXPERIMENTAL_TOOLS: readonly [import("./tools/tool-registry").McpToolDeclaration<{
16
+ query: import("zod").ZodString;
17
+ }, import("zod").ZodRawShape>, import("./tools/tool-registry").McpToolDeclaration<{
18
+ transformations: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodEnum<[string, ...string[]]>, "many">>;
19
+ }, {
20
+ instructions: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
21
+ }>];
11
22
  export declare function createMcpServer(options: {
12
23
  workspace?: AngularWorkspace;
13
24
  readOnly?: boolean;
@@ -10,6 +10,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  return (mod && mod.__esModule) ? mod : { "default": mod };
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.EXPERIMENTAL_TOOLS = void 0;
13
14
  exports.createMcpServer = createMcpServer;
14
15
  exports.assembleToolDeclarations = assembleToolDeclarations;
15
16
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
@@ -31,7 +32,7 @@ const STABLE_TOOLS = [best_practices_1.BEST_PRACTICES_TOOL, doc_search_1.DOC_SEA
31
32
  * The set of tools that are available but not enabled by default.
32
33
  * These tools are considered experimental and may have limitations.
33
34
  */
34
- const EXPERIMENTAL_TOOLS = [examples_1.FIND_EXAMPLE_TOOL, modernize_1.MODERNIZE_TOOL];
35
+ exports.EXPERIMENTAL_TOOLS = [examples_1.FIND_EXAMPLE_TOOL, modernize_1.MODERNIZE_TOOL];
35
36
  async function createMcpServer(options, logger) {
36
37
  const server = new mcp_js_1.McpServer({
37
38
  name: 'angular-cli-server',
@@ -46,7 +47,7 @@ async function createMcpServer(options, logger) {
46
47
  'When writing or modifying Angular code, use the MCP server and its tools instead of direct shell commands where possible.',
47
48
  });
48
49
  (0, instructions_1.registerInstructionsResource)(server);
49
- const toolDeclarations = assembleToolDeclarations(STABLE_TOOLS, EXPERIMENTAL_TOOLS, {
50
+ const toolDeclarations = assembleToolDeclarations(STABLE_TOOLS, exports.EXPERIMENTAL_TOOLS, {
50
51
  ...options,
51
52
  logger,
52
53
  });
@@ -112,7 +112,7 @@ function createDocSearchHandler() {
112
112
  const response = await fetch(url);
113
113
  if (response.ok) {
114
114
  const html = await response.text();
115
- const mainContent = extractBodyContent(html);
115
+ const mainContent = extractMainContent(html);
116
116
  if (mainContent) {
117
117
  topText += `\n\n--- DOCUMENTATION CONTENT ---\n${mainContent}`;
118
118
  }
@@ -138,22 +138,21 @@ function createDocSearchHandler() {
138
138
  };
139
139
  }
140
140
  /**
141
- * Extracts the content of the `<body>` element from an HTML string.
141
+ * Extracts the content of the `<main>` element from an HTML string.
142
142
  *
143
143
  * @param html The HTML content of a page.
144
- * @returns The content of the `<body>` element, or `undefined` if not found.
144
+ * @returns The content of the `<main>` element, or `undefined` if not found.
145
145
  */
146
- function extractBodyContent(html) {
147
- // TODO: Use '<main>' element instead of '<body>' when available in angular.dev HTML.
148
- const mainTagStart = html.indexOf('<body');
146
+ function extractMainContent(html) {
147
+ const mainTagStart = html.indexOf('<main');
149
148
  if (mainTagStart === -1) {
150
149
  return undefined;
151
150
  }
152
- const mainTagEnd = html.lastIndexOf('</body>');
151
+ const mainTagEnd = html.lastIndexOf('</main>');
153
152
  if (mainTagEnd <= mainTagStart) {
154
153
  return undefined;
155
154
  }
156
- // Add 7 to include '</body>'
155
+ // Add 7 to include '</main>'
157
156
  return html.substring(mainTagStart, mainTagEnd + 7);
158
157
  }
159
158
  /**
@@ -171,7 +171,7 @@ let PackageManagerUtils = (() => {
171
171
  const { cwd = process.cwd(), silent = false } = options;
172
172
  return new Promise((resolve) => {
173
173
  const bufferedOutput = [];
174
- const childProcess = (0, node_child_process_1.spawn)(this.name, args, {
174
+ const childProcess = (0, node_child_process_1.spawn)(`${this.name} ${args.join(' ')}`, {
175
175
  // Always pipe stderr to allow for failures to be reported
176
176
  stdio: silent ? ['ignore', 'ignore', 'pipe'] : 'pipe',
177
177
  shell: true,
@@ -22,4 +22,4 @@ class Version {
22
22
  this.patch = patch;
23
23
  }
24
24
  }
25
- exports.VERSION = new Version('20.2.0-rc.1');
25
+ exports.VERSION = new Version('20.2.0');