@angular/build 20.0.0-rc.2 → 20.0.0-rc.3

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/build",
3
- "version": "20.0.0-rc.2",
3
+ "version": "20.0.0-rc.3",
4
4
  "description": "Official build system for Angular",
5
5
  "keywords": [
6
6
  "Angular CLI",
@@ -23,7 +23,7 @@
23
23
  "builders": "builders.json",
24
24
  "dependencies": {
25
25
  "@ampproject/remapping": "2.3.0",
26
- "@angular-devkit/architect": "0.2000.0-rc.2",
26
+ "@angular-devkit/architect": "0.2000.0-rc.3",
27
27
  "@babel/core": "7.27.1",
28
28
  "@babel/helper-annotate-as-pure": "7.27.1",
29
29
  "@babel/helper-split-export-declaration": "7.24.7",
@@ -60,7 +60,7 @@
60
60
  "@angular/platform-browser": "^20.0.0 || ^20.0.0-next.0",
61
61
  "@angular/platform-server": "^20.0.0 || ^20.0.0-next.0",
62
62
  "@angular/service-worker": "^20.0.0 || ^20.0.0-next.0",
63
- "@angular/ssr": "^20.0.0-rc.2",
63
+ "@angular/ssr": "^20.0.0-rc.3",
64
64
  "karma": "^6.4.0",
65
65
  "less": "^4.2.0",
66
66
  "ng-packagr": "^20.0.0 || ^20.0.0-next.0",
@@ -114,7 +114,7 @@
114
114
  },
115
115
  "packageManager": "pnpm@9.15.9",
116
116
  "engines": {
117
- "node": "^20.11.1 || ^22.11.0 || >=24.0.0",
117
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0",
118
118
  "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
119
119
  "yarn": ">= 1.13.0"
120
120
  },
@@ -100,13 +100,20 @@ async function execute(options, context, extensions) {
100
100
  return node_path_1.default.relative(from, to);
101
101
  },
102
102
  };
103
+ const duplicateTranslationBehavior = normalizedOptions.i18nOptions.duplicateTranslationBehavior;
103
104
  const diagnostics = checkDuplicateMessages(
104
105
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
- checkFileSystem, extractionResult.messages, normalizedOptions.i18nOptions.i18nDuplicateTranslation || 'warning',
106
+ checkFileSystem, extractionResult.messages, duplicateTranslationBehavior,
106
107
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
108
  extractionResult.basePath);
108
- if (diagnostics.messages.length > 0) {
109
- context.logger.warn(diagnostics.formatDiagnostics(''));
109
+ if (diagnostics.messages.length > 0 && duplicateTranslationBehavior !== 'ignore') {
110
+ if (duplicateTranslationBehavior === 'error') {
111
+ context.logger.error(`Extraction Failed: ${diagnostics.formatDiagnostics('')}`);
112
+ return { success: false };
113
+ }
114
+ else {
115
+ context.logger.warn(diagnostics.formatDiagnostics(''));
116
+ }
110
117
  }
111
118
  // Serialize all extracted messages
112
119
  const serializer = await createSerializer(localizeToolsModule, normalizedOptions.format, normalizedOptions.i18nOptions.sourceLocale, extractionResult.basePath, extractionResult.useLegacyIds, diagnostics);
@@ -5,7 +5,9 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
+ import { type DiagnosticHandlingStrategy } from '@angular/localize/tools';
8
9
  import { BuilderContext } from '@angular-devkit/architect';
10
+ import { type I18nOptions } from '../../utils/i18n-options';
9
11
  import { Schema as ExtractI18nOptions, Format } from './schema';
10
12
  export type NormalizedExtractI18nOptions = Awaited<ReturnType<typeof normalizeOptions>>;
11
13
  /**
@@ -22,7 +24,9 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
22
24
  workspaceRoot: string;
23
25
  projectRoot: string;
24
26
  buildTarget: import("@angular-devkit/architect").Target;
25
- i18nOptions: import("../../utils/i18n-options").I18nOptions;
27
+ i18nOptions: I18nOptions & {
28
+ duplicateTranslationBehavior: DiagnosticHandlingStrategy;
29
+ };
26
30
  format: Format.Arb | Format.Json | Format.LegacyMigrate | Format.Xliff | Format.Xliff2 | Format.Xmb;
27
31
  outFile: string;
28
32
  progress: boolean;
@@ -33,7 +33,10 @@ async function normalizeOptions(context, projectName, options) {
33
33
  // Target specifier defaults to the current project's build target with no specified configuration
34
34
  const buildTargetSpecifier = options.buildTarget ?? ':';
35
35
  const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
36
- const i18nOptions = (0, i18n_options_1.createI18nOptions)(projectMetadata, /** inline */ false, context.logger);
36
+ const i18nOptions = {
37
+ ...(0, i18n_options_1.createI18nOptions)(projectMetadata, /** inline */ false, context.logger),
38
+ duplicateTranslationBehavior: options.i18nDuplicateTranslation || 'warning',
39
+ };
37
40
  // Normalize xliff format extensions
38
41
  let format = options.format;
39
42
  switch (format) {
@@ -12,6 +12,10 @@ export type Schema = {
12
12
  * Output format for the generated file.
13
13
  */
14
14
  format?: Format;
15
+ /**
16
+ * How to handle duplicate translations.
17
+ */
18
+ i18nDuplicateTranslation?: I18NDuplicateTranslation;
15
19
  /**
16
20
  * Name of the file to output.
17
21
  */
@@ -39,3 +43,11 @@ export declare enum Format {
39
43
  Xliff2 = "xliff2",
40
44
  Xmb = "xmb"
41
45
  }
46
+ /**
47
+ * How to handle duplicate translations.
48
+ */
49
+ export declare enum I18NDuplicateTranslation {
50
+ Error = "error",
51
+ Ignore = "ignore",
52
+ Warning = "warning"
53
+ }
@@ -2,7 +2,7 @@
2
2
  // THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
3
3
  // CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.Format = void 0;
5
+ exports.I18NDuplicateTranslation = exports.Format = void 0;
6
6
  /**
7
7
  * Output format for the generated file.
8
8
  */
@@ -18,3 +18,12 @@ var Format;
18
18
  Format["Xliff2"] = "xliff2";
19
19
  Format["Xmb"] = "xmb";
20
20
  })(Format || (exports.Format = Format = {}));
21
+ /**
22
+ * How to handle duplicate translations.
23
+ */
24
+ var I18NDuplicateTranslation;
25
+ (function (I18NDuplicateTranslation) {
26
+ I18NDuplicateTranslation["Error"] = "error";
27
+ I18NDuplicateTranslation["Ignore"] = "ignore";
28
+ I18NDuplicateTranslation["Warning"] = "warning";
29
+ })(I18NDuplicateTranslation || (exports.I18NDuplicateTranslation = I18NDuplicateTranslation = {}));
@@ -27,6 +27,11 @@
27
27
  "outFile": {
28
28
  "type": "string",
29
29
  "description": "Name of the file to output."
30
+ },
31
+ "i18nDuplicateTranslation": {
32
+ "type": "string",
33
+ "description": "How to handle duplicate translations.",
34
+ "enum": ["error", "warning", "ignore"]
30
35
  }
31
36
  },
32
37
  "additionalProperties": false
@@ -107,7 +107,6 @@ async function* execute(options, context, extensions = {}) {
107
107
  `import { NgModule } from '@angular/core';`,
108
108
  `import { getTestBed, ɵgetCleanupHook as getCleanupHook } from '@angular/core/testing';`,
109
109
  `import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';`,
110
- `import { beforeEach, afterEach } from 'vitest';`,
111
110
  '',
112
111
  normalizedOptions.providersFile
113
112
  ? `import providers from './${node_path_1.default
@@ -140,7 +139,7 @@ async function* execute(options, context, extensions = {}) {
140
139
  extensions.codePlugins.unshift(virtualTestBedInit);
141
140
  let instance;
142
141
  // Setup vitest browser options if configured
143
- const { browser, errors } = setupBrowserConfiguration(normalizedOptions.browsers, projectSourceRoot);
142
+ const { browser, errors } = setupBrowserConfiguration(normalizedOptions.browsers, normalizedOptions.debug, projectSourceRoot);
144
143
  if (errors?.length) {
145
144
  errors.forEach((error) => context.logger.error(error));
146
145
  return { success: false };
@@ -150,6 +149,13 @@ async function* execute(options, context, extensions = {}) {
150
149
  if (buildTargetOptions?.polyfills?.length) {
151
150
  setupFiles.push('polyfills.js');
152
151
  }
152
+ const debugOptions = normalizedOptions.debug
153
+ ? {
154
+ inspectBrk: true,
155
+ isolate: false,
156
+ fileParallelism: false,
157
+ }
158
+ : {};
153
159
  try {
154
160
  for await (const result of (0, application_1.buildApplicationInternal)(buildOptions, context, extensions)) {
155
161
  if (result.kind === results_1.ResultKind.Failure) {
@@ -160,7 +166,10 @@ async function* execute(options, context, extensions = {}) {
160
166
  }
161
167
  (0, node_assert_1.default)(result.files, 'Builder did not provide result files.');
162
168
  await (0, application_builder_1.writeTestFiles)(result.files, outputPath);
163
- instance ??= await startVitest('test', undefined /* cliFilters */, undefined /* options */, {
169
+ instance ??= await startVitest('test', undefined /* cliFilters */, {
170
+ // Disable configuration file resolution/loading
171
+ config: false,
172
+ }, {
164
173
  test: {
165
174
  root: outputPath,
166
175
  globals: true,
@@ -173,10 +182,25 @@ async function* execute(options, context, extensions = {}) {
173
182
  reporters: normalizedOptions.reporters ?? ['default'],
174
183
  coverage: {
175
184
  enabled: normalizedOptions.codeCoverage,
176
- exclude: normalizedOptions.codeCoverageExclude,
177
185
  excludeAfterRemap: true,
178
186
  },
187
+ ...debugOptions,
179
188
  },
189
+ plugins: [
190
+ {
191
+ name: 'angular-coverage-exclude',
192
+ configureVitest(context) {
193
+ // Adjust coverage excludes to not include the otherwise automatically inserted included unit tests.
194
+ // Vite does this as a convenience but is problematic for the bundling strategy employed by the
195
+ // builder's test setup. To workaround this, the excludes are adjusted here to only automatically
196
+ // exclude the TypeScript source test files.
197
+ context.project.config.coverage.exclude = [
198
+ ...(normalizedOptions.codeCoverageExclude ?? []),
199
+ '**/*.{test,spec}.?(c|m)ts',
200
+ ];
201
+ },
202
+ },
203
+ ],
180
204
  });
181
205
  // Check if all the tests pass to calculate the result
182
206
  const testModules = instance.state.getTestModules();
@@ -201,7 +225,7 @@ function findBrowserProvider(projectResolver) {
201
225
  catch { }
202
226
  }
203
227
  }
204
- function setupBrowserConfiguration(browsers, projectSourceRoot) {
228
+ function setupBrowserConfiguration(browsers, debug, projectSourceRoot) {
205
229
  if (browsers === undefined) {
206
230
  return {};
207
231
  }
@@ -221,6 +245,12 @@ function setupBrowserConfiguration(browsers, projectSourceRoot) {
221
245
  errors.push('The "browsers" option requires either "playwright" or "webdriverio" to be installed within the project.' +
222
246
  ' Please install one of these packages and rerun the test command.');
223
247
  }
248
+ // Vitest current requires the playwright browser provider to use the inspect-brk option used by "debug"
249
+ if (debug && provider !== 'playwright') {
250
+ errors ??= [];
251
+ errors.push('Debugging browser mode tests currently requires the use of "playwright".' +
252
+ ' Please install this package and rerun the test command.');
253
+ }
224
254
  if (errors) {
225
255
  return { errors };
226
256
  }
@@ -42,6 +42,9 @@ var __importStar = (this && this.__importStar) || (function () {
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
43
  exports.useKarmaBuilder = useKarmaBuilder;
44
44
  async function useKarmaBuilder(context, unitTestOptions) {
45
+ if (unitTestOptions.debug) {
46
+ context.logger.warn('The "karma" test runner does not support the "debug" option. The option will be ignored.');
47
+ }
45
48
  const buildTargetOptions = (await context.validateOptions(await context.getTargetOptions(unitTestOptions.buildTarget), await context.getBuilderNameForTarget(unitTestOptions.buildTarget)));
46
49
  const options = {
47
50
  tsConfig: unitTestOptions.tsConfig,
@@ -22,6 +22,7 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
22
22
  tsConfig: string;
23
23
  reporters: string[] | undefined;
24
24
  browsers: string[] | undefined;
25
- watch: boolean | undefined;
25
+ watch: boolean;
26
+ debug: boolean;
26
27
  providersFile: string | undefined;
27
28
  }>;
@@ -15,6 +15,7 @@ const architect_1 = require("@angular-devkit/architect");
15
15
  const node_path_1 = __importDefault(require("node:path"));
16
16
  const normalize_cache_1 = require("../../utils/normalize-cache");
17
17
  const project_metadata_1 = require("../../utils/project-metadata");
18
+ const tty_1 = require("../../utils/tty");
18
19
  async function normalizeOptions(context, projectName, options) {
19
20
  // Setup base paths based on workspace root and project information
20
21
  const workspaceRoot = context.workspaceRoot;
@@ -26,7 +27,7 @@ async function normalizeOptions(context, projectName, options) {
26
27
  // Target specifier defaults to the current project's build target using a development configuration
27
28
  const buildTargetSpecifier = options.buildTarget ?? `::development`;
28
29
  const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
29
- const { codeCoverage, codeCoverageExclude, tsConfig, runner, reporters, browsers, watch } = options;
30
+ const { codeCoverage, codeCoverageExclude, tsConfig, runner, reporters, browsers } = options;
30
31
  return {
31
32
  // Project/workspace information
32
33
  workspaceRoot,
@@ -43,20 +44,8 @@ async function normalizeOptions(context, projectName, options) {
43
44
  tsConfig,
44
45
  reporters,
45
46
  browsers,
46
- watch,
47
+ watch: options.watch ?? (0, tty_1.isTTY)(),
48
+ debug: options.debug ?? false,
47
49
  providersFile: options.providersFile && node_path_1.default.join(workspaceRoot, options.providersFile),
48
50
  };
49
51
  }
50
- /**
51
- * Normalize a directory path string.
52
- * Currently only removes a trailing slash if present.
53
- * @param path A path string.
54
- * @returns A normalized path string.
55
- */
56
- function normalizeDirectoryPath(path) {
57
- const last = path[path.length - 1];
58
- if (last === '/' || last === '\\') {
59
- return path.slice(0, -1);
60
- }
61
- return path;
62
- }
@@ -21,6 +21,10 @@ export type Schema = {
21
21
  * Globs to exclude from code coverage.
22
22
  */
23
23
  codeCoverageExclude?: string[];
24
+ /**
25
+ * Initialize the test runner to support using the Node Inspector for test debugging.
26
+ */
27
+ debug?: boolean;
24
28
  /**
25
29
  * Globs of files to exclude, relative to the project root.
26
30
  */
@@ -46,6 +46,11 @@
46
46
  "type": "boolean",
47
47
  "description": "Run build when files change."
48
48
  },
49
+ "debug": {
50
+ "type": "boolean",
51
+ "description": "Initialize the test runner to support using the Node Inspector for test debugging.",
52
+ "default": false
53
+ },
49
54
  "codeCoverage": {
50
55
  "type": "boolean",
51
56
  "description": "Output a code coverage report.",
@@ -315,7 +315,7 @@ function transformSupportedBrowsersToTargets(supportedBrowsers) {
315
315
  }
316
316
  return transformed;
317
317
  }
318
- const SUPPORTED_NODE_VERSIONS = '^20.11.1 || ^22.11.0 || >=24.0.0';
318
+ const SUPPORTED_NODE_VERSIONS = '^20.19.0 || ^22.12.0 || >=24.0.0';
319
319
  /**
320
320
  * Transform supported Node.js versions to esbuild target.
321
321
  * @see https://esbuild.github.io/api/#target
@@ -5,7 +5,6 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
- import { DiagnosticHandlingStrategy } from '@angular/localize/tools';
9
8
  import type { TranslationLoader } from './load-translations';
10
9
  export interface LocaleDescription {
11
10
  files: {
@@ -25,7 +24,6 @@ export interface I18nOptions {
25
24
  flatOutput?: boolean;
26
25
  readonly shouldInline: boolean;
27
26
  hasDefinedSourceLocale?: boolean;
28
- i18nDuplicateTranslation?: DiagnosticHandlingStrategy;
29
27
  }
30
28
  export declare function createI18nOptions(projectMetadata: {
31
29
  i18n?: unknown;
@@ -78,8 +78,7 @@ async function loadProxyConfiguration(root, proxyConfig) {
78
78
  // Load the ESM configuration file using the TypeScript dynamic import workaround.
79
79
  // Once TypeScript provides support for keeping the dynamic import this workaround can be
80
80
  // changed to a direct dynamic import.
81
- proxyConfiguration = (await (0, load_esm_1.loadEsmModule)((0, node_url_1.pathToFileURL)(proxyPath)))
82
- .default;
81
+ proxyConfiguration = await (0, load_esm_1.loadEsmModule)((0, node_url_1.pathToFileURL)(proxyPath));
83
82
  break;
84
83
  case '.cjs':
85
84
  proxyConfiguration = require(proxyPath);
@@ -97,13 +96,15 @@ async function loadProxyConfiguration(root, proxyConfig) {
97
96
  // Load the ESM configuration file using the TypeScript dynamic import workaround.
98
97
  // Once TypeScript provides support for keeping the dynamic import this workaround can be
99
98
  // changed to a direct dynamic import.
100
- proxyConfiguration = (await (0, load_esm_1.loadEsmModule)((0, node_url_1.pathToFileURL)(proxyPath)))
101
- .default;
99
+ proxyConfiguration = await (0, load_esm_1.loadEsmModule)((0, node_url_1.pathToFileURL)(proxyPath));
102
100
  break;
103
101
  }
104
102
  throw e;
105
103
  }
106
104
  }
105
+ if ('default' in proxyConfiguration) {
106
+ proxyConfiguration = proxyConfiguration.default;
107
+ }
107
108
  return normalizeProxyConfiguration(proxyConfiguration);
108
109
  }
109
110
  /**
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.normalizeCacheOptions = normalizeCacheOptions;
11
11
  const node_path_1 = require("node:path");
12
12
  /** Version placeholder is replaced during the build process with actual package version */
13
- const VERSION = '20.0.0-rc.2';
13
+ const VERSION = '20.0.0-rc.3';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&