@angular/build 20.0.0-next.7 → 20.0.0-next.9

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/.browserslistrc CHANGED
@@ -1,6 +1,6 @@
1
- Chrome >= 105
2
- ChromeAndroid >= 105
3
- Edge >= 105
1
+ Chrome >= 107
2
+ ChromeAndroid >= 107
3
+ Edge >= 107
4
4
  Firefox >= 104
5
5
  FirefoxAndroid >= 104
6
6
  Safari >= 16
package/builders.json CHANGED
@@ -24,6 +24,11 @@
24
24
  "implementation": "./src/builders/ng-packagr/index",
25
25
  "schema": "./src/builders/ng-packagr/schema.json",
26
26
  "description": "Build a library with ng-packagr."
27
+ },
28
+ "unit-test": {
29
+ "implementation": "./src/builders/unit-test",
30
+ "schema": "./src/builders/unit-test/schema.json",
31
+ "description": "[EXPERIMENTAL] Run application unit tests."
27
32
  }
28
33
  }
29
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular/build",
3
- "version": "20.0.0-next.7",
3
+ "version": "20.0.0-next.9",
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-next.7",
26
+ "@angular-devkit/architect": "0.2000.0-next.9",
27
27
  "@babel/core": "7.26.10",
28
28
  "@babel/helper-annotate-as-pure": "7.25.9",
29
29
  "@babel/helper-split-export-declaration": "7.24.7",
@@ -41,12 +41,12 @@
41
41
  "parse5-html-rewriting-stream": "7.1.0",
42
42
  "picomatch": "4.0.2",
43
43
  "piscina": "4.9.2",
44
- "rollup": "4.40.0",
44
+ "rollup": "4.40.1",
45
45
  "sass": "1.87.0",
46
46
  "semver": "7.7.1",
47
47
  "source-map-support": "0.5.21",
48
48
  "tinyglobby": "0.2.13",
49
- "vite": "6.3.2",
49
+ "vite": "6.3.4",
50
50
  "watchpack": "2.4.2"
51
51
  },
52
52
  "optionalDependencies": {
@@ -60,14 +60,15 @@
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-next.7",
63
+ "@angular/ssr": "^20.0.0-next.9",
64
64
  "karma": "^6.4.0",
65
65
  "less": "^4.2.0",
66
66
  "ng-packagr": "^20.0.0 || ^20.0.0-next.0",
67
67
  "postcss": "^8.4.0",
68
68
  "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0",
69
69
  "tslib": "^2.3.0",
70
- "typescript": ">=5.8 <5.9"
70
+ "typescript": ">=5.8 <5.9",
71
+ "vitest": "^3.1.1"
71
72
  },
72
73
  "peerDependenciesMeta": {
73
74
  "@angular/core": {
@@ -102,6 +103,9 @@
102
103
  },
103
104
  "tailwindcss": {
104
105
  "optional": true
106
+ },
107
+ "vitest": {
108
+ "optional": true
105
109
  }
106
110
  },
107
111
  "repository": {
@@ -23,6 +23,7 @@ const environment_options_1 = require("../../utils/environment-options");
23
23
  const i18n_options_1 = require("../../utils/i18n-options");
24
24
  const normalize_cache_1 = require("../../utils/normalize-cache");
25
25
  const postcss_configuration_1 = require("../../utils/postcss-configuration");
26
+ const project_metadata_1 = require("../../utils/project-metadata");
26
27
  const url_1 = require("../../utils/url");
27
28
  const schema_1 = require("./schema");
28
29
  /**
@@ -58,8 +59,7 @@ async function normalizeOptions(context, projectName, options, extensions) {
58
59
  // ref: https://github.com/nodejs/node/issues/7726
59
60
  (0, node_fs_1.realpathSync)(context.workspaceRoot);
60
61
  const projectMetadata = await context.getProjectMetadata(projectName);
61
- const projectRoot = normalizeDirectoryPath(node_path_1.default.join(workspaceRoot, projectMetadata.root ?? ''));
62
- const projectSourceRoot = normalizeDirectoryPath(node_path_1.default.join(workspaceRoot, projectMetadata.sourceRoot ?? 'src'));
62
+ const { projectRoot, projectSourceRoot } = (0, project_metadata_1.getProjectRootPaths)(workspaceRoot, projectMetadata);
63
63
  // Gather persistent caching option and provide a project specific cache location
64
64
  const cacheOptions = (0, normalize_cache_1.normalizeCacheOptions)(projectMetadata, workspaceRoot);
65
65
  cacheOptions.path = node_path_1.default.join(cacheOptions.path, projectName);
@@ -169,7 +169,7 @@ async function normalizeOptions(context, projectName, options, extensions) {
169
169
  server: 'server',
170
170
  media: 'media',
171
171
  ...(typeof outputPath === 'string' ? undefined : outputPath),
172
- base: normalizeDirectoryPath(node_path_1.default.resolve(workspaceRoot, typeof outputPath === 'string' ? outputPath : outputPath.base)),
172
+ base: (0, project_metadata_1.normalizeDirectoryPath)(node_path_1.default.resolve(workspaceRoot, typeof outputPath === 'string' ? outputPath : outputPath.base)),
173
173
  clean: options.deleteOutputPath ?? true,
174
174
  // For app-shell and SSG server files are not required by users.
175
175
  // Omit these when SSR is not enabled.
@@ -406,19 +406,6 @@ function normalizeEntryPoints(workspaceRoot, browser, entryPoints = new Set()) {
406
406
  return entryPointPaths;
407
407
  }
408
408
  }
409
- /**
410
- * Normalize a directory path string.
411
- * Currently only removes a trailing slash if present.
412
- * @param path A path string.
413
- * @returns A normalized path string.
414
- */
415
- function normalizeDirectoryPath(path) {
416
- const last = path[path.length - 1];
417
- if (last === '/' || last === '\\') {
418
- return path.slice(0, -1);
419
- }
420
- return path;
421
- }
422
409
  function normalizeGlobalEntries(rawEntries, defaultName) {
423
410
  if (!rawEntries?.length) {
424
411
  return [];
@@ -50,6 +50,7 @@ const tinyglobby_1 = require("tinyglobby");
50
50
  const bundler_context_1 = require("../../tools/esbuild/bundler-context");
51
51
  const utils_1 = require("../../tools/esbuild/utils");
52
52
  const virtual_module_plugin_1 = require("../../tools/esbuild/virtual-module-plugin");
53
+ const project_metadata_1 = require("../../utils/project-metadata");
53
54
  const index_1 = require("../application/index");
54
55
  const results_1 = require("../application/results");
55
56
  const schema_1 = require("../application/schema");
@@ -266,8 +267,8 @@ async function getProjectSourceRoot(context) {
266
267
  return context.workspaceRoot;
267
268
  }
268
269
  const projectMetadata = await context.getProjectMetadata(projectName);
269
- const sourceRoot = (projectMetadata.sourceRoot ?? projectMetadata.root ?? '');
270
- return path.join(context.workspaceRoot, sourceRoot);
270
+ const { projectSourceRoot } = (0, project_metadata_1.getProjectRootPaths)(context.workspaceRoot, projectMetadata);
271
+ return projectSourceRoot;
271
272
  }
272
273
  function normalizePolyfills(polyfills) {
273
274
  if (typeof polyfills === 'string') {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
9
+ import type { ApplicationBuilderExtensions } from '../application/options';
10
+ import type { Schema as UnitTestOptions } from './schema';
11
+ export type { UnitTestOptions };
12
+ /**
13
+ * @experimental Direct usage of this function is considered experimental.
14
+ */
15
+ export declare function execute(options: UnitTestOptions, context: BuilderContext, extensions?: ApplicationBuilderExtensions): AsyncIterable<BuilderOutput>;
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.execute = execute;
14
+ const node_assert_1 = __importDefault(require("node:assert"));
15
+ const node_crypto_1 = require("node:crypto");
16
+ const node_module_1 = require("node:module");
17
+ const node_path_1 = __importDefault(require("node:path"));
18
+ const virtual_module_plugin_1 = require("../../tools/esbuild/virtual-module-plugin");
19
+ const load_esm_1 = require("../../utils/load-esm");
20
+ const application_1 = require("../application");
21
+ const results_1 = require("../application/results");
22
+ const schema_1 = require("../application/schema");
23
+ const application_builder_1 = require("../karma/application_builder");
24
+ const find_tests_1 = require("../karma/find-tests");
25
+ const karma_bridge_1 = require("./karma-bridge");
26
+ const options_1 = require("./options");
27
+ /**
28
+ * @experimental Direct usage of this function is considered experimental.
29
+ */
30
+ async function* execute(options, context, extensions = {}) {
31
+ // Determine project name from builder context target
32
+ const projectName = context.target?.project;
33
+ if (!projectName) {
34
+ context.logger.error(`The "${context.builder.builderName}" builder requires a target to be specified.`);
35
+ return;
36
+ }
37
+ context.logger.warn(`NOTE: The "${context.builder.builderName}" builder is currently EXPERIMENTAL and not ready for production use.`);
38
+ const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, options);
39
+ const { projectSourceRoot, workspaceRoot, runnerName } = normalizedOptions;
40
+ // Translate options and use karma builder directly if specified
41
+ if (runnerName === 'karma') {
42
+ const karmaBridge = await (0, karma_bridge_1.useKarmaBuilder)(context, normalizedOptions);
43
+ yield* karmaBridge;
44
+ return;
45
+ }
46
+ if (runnerName !== 'vitest') {
47
+ context.logger.error('Unknown test runner: ' + runnerName);
48
+ return;
49
+ }
50
+ // Find test files
51
+ const testFiles = await (0, find_tests_1.findTests)(normalizedOptions.include, normalizedOptions.exclude, workspaceRoot, projectSourceRoot);
52
+ if (testFiles.length === 0) {
53
+ context.logger.error('No tests found.');
54
+ return { success: false };
55
+ }
56
+ const entryPoints = (0, find_tests_1.getTestEntrypoints)(testFiles, { projectSourceRoot, workspaceRoot });
57
+ entryPoints.set('init-testbed', 'angular:test-bed-init');
58
+ const { startVitest } = await (0, load_esm_1.loadEsmModule)('vitest/node');
59
+ // Setup test file build options based on application build target options
60
+ const buildTargetOptions = (await context.validateOptions(await context.getTargetOptions(normalizedOptions.buildTarget), await context.getBuilderNameForTarget(normalizedOptions.buildTarget)));
61
+ if (buildTargetOptions.polyfills?.includes('zone.js')) {
62
+ buildTargetOptions.polyfills.push('zone.js/testing');
63
+ }
64
+ const outputPath = node_path_1.default.join(context.workspaceRoot, 'dist/test-out', (0, node_crypto_1.randomUUID)());
65
+ const buildOptions = {
66
+ ...buildTargetOptions,
67
+ watch: normalizedOptions.watch,
68
+ outputPath,
69
+ index: false,
70
+ browser: undefined,
71
+ server: undefined,
72
+ localize: false,
73
+ budgets: [],
74
+ serviceWorker: false,
75
+ appShell: false,
76
+ ssr: false,
77
+ prerender: false,
78
+ sourceMap: { scripts: true, vendor: false, styles: false },
79
+ outputHashing: schema_1.OutputHashing.None,
80
+ optimization: false,
81
+ tsConfig: normalizedOptions.tsConfig,
82
+ entryPoints,
83
+ externalDependencies: ['vitest', ...(buildTargetOptions.externalDependencies ?? [])],
84
+ };
85
+ extensions ??= {};
86
+ extensions.codePlugins ??= [];
87
+ const virtualTestBedInit = (0, virtual_module_plugin_1.createVirtualModulePlugin)({
88
+ namespace: 'angular:test-bed-init',
89
+ loadContent: async () => {
90
+ const contents = [
91
+ // Initialize the Angular testing environment
92
+ `import { getTestBed } from '@angular/core/testing';`,
93
+ `import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';`,
94
+ `getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting(), {`,
95
+ ` errorOnUnknownElements: true,`,
96
+ ` errorOnUnknownProperties: true,`,
97
+ '});',
98
+ ];
99
+ return {
100
+ contents: contents.join('\n'),
101
+ loader: 'js',
102
+ resolveDir: projectSourceRoot,
103
+ };
104
+ },
105
+ });
106
+ extensions.codePlugins.unshift(virtualTestBedInit);
107
+ let instance;
108
+ // Setup vitest browser options if configured
109
+ let browser;
110
+ if (normalizedOptions.browsers) {
111
+ const provider = findBrowserProvider(projectSourceRoot);
112
+ if (!provider) {
113
+ context.logger.error('The "browsers" option requires either "playwright" or "webdriverio" to be installed within the project.' +
114
+ ' Please install one of these packages and rerun the test command.');
115
+ return { success: false };
116
+ }
117
+ browser = {
118
+ enabled: true,
119
+ provider,
120
+ instances: normalizedOptions.browsers.map((browserName) => ({
121
+ browser: browserName,
122
+ })),
123
+ };
124
+ }
125
+ for await (const result of (0, application_1.buildApplicationInternal)(buildOptions, context, extensions)) {
126
+ if (result.kind === results_1.ResultKind.Failure) {
127
+ continue;
128
+ }
129
+ else if (result.kind !== results_1.ResultKind.Full) {
130
+ node_assert_1.default.fail('A full build result is required from the application builder.');
131
+ }
132
+ (0, node_assert_1.default)(result.files, 'Builder did not provide result files.');
133
+ await (0, application_builder_1.writeTestFiles)(result.files, outputPath);
134
+ const setupFiles = ['init-testbed.js'];
135
+ if (buildTargetOptions?.polyfills?.length) {
136
+ setupFiles.push('polyfills.js');
137
+ }
138
+ instance ??= await startVitest('test', undefined /* cliFilters */, undefined /* options */, {
139
+ test: {
140
+ root: outputPath,
141
+ setupFiles,
142
+ // Use `jsdom` if no browsers are explicitly configured.
143
+ // `node` is effectively no "environment" and the default.
144
+ environment: browser ? 'node' : 'jsdom',
145
+ watch: normalizedOptions.watch,
146
+ browser,
147
+ coverage: {
148
+ enabled: normalizedOptions.codeCoverage,
149
+ exclude: normalizedOptions.codeCoverageExclude,
150
+ excludeAfterRemap: true,
151
+ },
152
+ },
153
+ });
154
+ // Check if all the tests pass to calculate the result
155
+ const testModules = instance.state.getTestModules();
156
+ yield { success: testModules.every((testModule) => testModule.ok()) };
157
+ }
158
+ }
159
+ function findBrowserProvider(projectSourceRoot) {
160
+ const projectResolver = (0, node_module_1.createRequire)(projectSourceRoot + '/').resolve;
161
+ // These must be installed in the project to be used
162
+ const vitestBuiltinProviders = ['playwright', 'webdriverio'];
163
+ for (const providerName of vitestBuiltinProviders) {
164
+ try {
165
+ projectResolver(providerName);
166
+ return providerName;
167
+ }
168
+ catch { }
169
+ }
170
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ import { type Builder } from '@angular-devkit/architect';
9
+ import { type UnitTestOptions, execute } from './builder';
10
+ export { type UnitTestOptions, execute };
11
+ declare const builder: Builder<UnitTestOptions>;
12
+ export default builder;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.execute = void 0;
11
+ const architect_1 = require("@angular-devkit/architect");
12
+ const builder_1 = require("./builder");
13
+ Object.defineProperty(exports, "execute", { enumerable: true, get: function () { return builder_1.execute; } });
14
+ const builder = (0, architect_1.createBuilder)(builder_1.execute);
15
+ exports.default = builder;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
9
+ import type { NormalizedUnitTestOptions } from './options';
10
+ export declare function useKarmaBuilder(context: BuilderContext, unitTestOptions: NormalizedUnitTestOptions): Promise<AsyncIterable<BuilderOutput>>;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.useKarmaBuilder = useKarmaBuilder;
44
+ async function useKarmaBuilder(context, unitTestOptions) {
45
+ const buildTargetOptions = (await context.validateOptions(await context.getTargetOptions(unitTestOptions.buildTarget), await context.getBuilderNameForTarget(unitTestOptions.buildTarget)));
46
+ const options = {
47
+ tsConfig: unitTestOptions.tsConfig,
48
+ polyfills: buildTargetOptions.polyfills,
49
+ assets: buildTargetOptions.assets,
50
+ scripts: buildTargetOptions.scripts,
51
+ styles: buildTargetOptions.styles,
52
+ inlineStyleLanguage: buildTargetOptions.inlineStyleLanguage,
53
+ stylePreprocessorOptions: buildTargetOptions.stylePreprocessorOptions,
54
+ externalDependencies: buildTargetOptions.externalDependencies,
55
+ loader: buildTargetOptions.loader,
56
+ define: buildTargetOptions.define,
57
+ include: unitTestOptions.include,
58
+ exclude: unitTestOptions.exclude,
59
+ sourceMap: buildTargetOptions.sourceMap,
60
+ progress: buildTargetOptions.progress,
61
+ watch: unitTestOptions.watch,
62
+ poll: buildTargetOptions.poll,
63
+ preserveSymlinks: buildTargetOptions.preserveSymlinks,
64
+ browsers: unitTestOptions.browsers?.join(','),
65
+ codeCoverage: unitTestOptions.codeCoverage,
66
+ codeCoverageExclude: unitTestOptions.codeCoverageExclude,
67
+ fileReplacements: buildTargetOptions.fileReplacements,
68
+ reporters: unitTestOptions.reporters,
69
+ webWorkerTsConfig: buildTargetOptions.webWorkerTsConfig,
70
+ aot: buildTargetOptions.aot,
71
+ };
72
+ const { execute } = await Promise.resolve().then(() => __importStar(require('../karma')));
73
+ return execute(options, context);
74
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ import { type BuilderContext } from '@angular-devkit/architect';
9
+ import type { Schema as UnitTestOptions } from './schema';
10
+ export type NormalizedUnitTestOptions = Awaited<ReturnType<typeof normalizeOptions>>;
11
+ export declare function normalizeOptions(context: BuilderContext, projectName: string, options: UnitTestOptions): Promise<{
12
+ workspaceRoot: string;
13
+ projectRoot: string;
14
+ projectSourceRoot: string;
15
+ cacheOptions: import("../../utils/normalize-cache").NormalizedCachedOptions;
16
+ buildTarget: import("@angular-devkit/architect").Target;
17
+ include: string[];
18
+ exclude: string[];
19
+ runnerName: import("./schema").Runner;
20
+ codeCoverage: boolean | undefined;
21
+ codeCoverageExclude: string[] | undefined;
22
+ tsConfig: string;
23
+ reporters: string[] | undefined;
24
+ browsers: string[] | undefined;
25
+ watch: boolean;
26
+ }>;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.normalizeOptions = normalizeOptions;
14
+ const architect_1 = require("@angular-devkit/architect");
15
+ const node_path_1 = __importDefault(require("node:path"));
16
+ const normalize_cache_1 = require("../../utils/normalize-cache");
17
+ const project_metadata_1 = require("../../utils/project-metadata");
18
+ async function normalizeOptions(context, projectName, options) {
19
+ // Setup base paths based on workspace root and project information
20
+ const workspaceRoot = context.workspaceRoot;
21
+ const projectMetadata = await context.getProjectMetadata(projectName);
22
+ const { projectRoot, projectSourceRoot } = (0, project_metadata_1.getProjectRootPaths)(workspaceRoot, projectMetadata);
23
+ // Gather persistent caching option and provide a project specific cache location
24
+ const cacheOptions = (0, normalize_cache_1.normalizeCacheOptions)(projectMetadata, workspaceRoot);
25
+ cacheOptions.path = node_path_1.default.join(cacheOptions.path, projectName);
26
+ // Target specifier defaults to the current project's build target using a development configuration
27
+ const buildTargetSpecifier = options.buildTarget ?? `::development`;
28
+ const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
29
+ const { codeCoverage, codeCoverageExclude, tsConfig, runner, reporters, browsers } = options;
30
+ return {
31
+ // Project/workspace information
32
+ workspaceRoot,
33
+ projectRoot,
34
+ projectSourceRoot,
35
+ cacheOptions,
36
+ // Target/configuration specified options
37
+ buildTarget,
38
+ include: options.include ?? ['**/*.spec.ts'],
39
+ exclude: options.exclude ?? [],
40
+ runnerName: runner,
41
+ codeCoverage,
42
+ codeCoverageExclude,
43
+ tsConfig,
44
+ reporters,
45
+ browsers,
46
+ // TODO: Implement watch support
47
+ watch: false,
48
+ };
49
+ }
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
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Unit testing options for Angular applications.
3
+ */
4
+ export type Schema = {
5
+ /**
6
+ * A list of browsers to use for test execution. If undefined, jsdom on Node.js will be used
7
+ * instead of a browser.
8
+ */
9
+ browsers?: string[];
10
+ /**
11
+ * A build builder target to serve in the format of `project:target[:configuration]`. You
12
+ * can also pass in more than one configuration name as a comma-separated list. Example:
13
+ * `project:target:production,staging`.
14
+ */
15
+ buildTarget: string;
16
+ /**
17
+ * Output a code coverage report.
18
+ */
19
+ codeCoverage?: boolean;
20
+ /**
21
+ * Globs to exclude from code coverage.
22
+ */
23
+ codeCoverageExclude?: string[];
24
+ /**
25
+ * Globs of files to exclude, relative to the project root.
26
+ */
27
+ exclude?: string[];
28
+ /**
29
+ * Globs of files to include, relative to project root.
30
+ * There are 2 special cases:
31
+ * - when a path to directory is provided, all spec files ending ".spec.@(ts|tsx)" will be
32
+ * included
33
+ * - when a path to a file is provided, and a matching spec file exists it will be included
34
+ * instead.
35
+ */
36
+ include?: string[];
37
+ /**
38
+ * Test runner reporters to use. Directly passed to the test runner.
39
+ */
40
+ reporters?: string[];
41
+ /**
42
+ * The name of the test runner to use for test execution.
43
+ */
44
+ runner: Runner;
45
+ /**
46
+ * The name of the TypeScript configuration file.
47
+ */
48
+ tsConfig: string;
49
+ /**
50
+ * Run build when files change.
51
+ */
52
+ watch?: boolean;
53
+ };
54
+ /**
55
+ * The name of the test runner to use for test execution.
56
+ */
57
+ export declare enum Runner {
58
+ Karma = "karma",
59
+ Vitest = "vitest"
60
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ // THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
3
+ // CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.Runner = void 0;
6
+ /**
7
+ * The name of the test runner to use for test execution.
8
+ */
9
+ var Runner;
10
+ (function (Runner) {
11
+ Runner["Karma"] = "karma";
12
+ Runner["Vitest"] = "vitest";
13
+ })(Runner || (exports.Runner = Runner = {}));
@@ -0,0 +1,72 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema",
3
+ "title": "Unit testing",
4
+ "description": "Unit testing options for Angular applications.",
5
+ "type": "object",
6
+ "properties": {
7
+ "buildTarget": {
8
+ "type": "string",
9
+ "description": "A build builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
10
+ "pattern": "^[^:\\s]*:[^:\\s]*(:[^\\s]+)?$"
11
+ },
12
+ "tsConfig": {
13
+ "type": "string",
14
+ "description": "The name of the TypeScript configuration file."
15
+ },
16
+ "runner": {
17
+ "type": "string",
18
+ "description": "The name of the test runner to use for test execution.",
19
+ "enum": ["karma", "vitest"]
20
+ },
21
+ "browsers": {
22
+ "description": "A list of browsers to use for test execution. If undefined, jsdom on Node.js will be used instead of a browser.",
23
+ "type": "array",
24
+ "items": {
25
+ "type": "string"
26
+ },
27
+ "minItems": 1
28
+ },
29
+ "include": {
30
+ "type": "array",
31
+ "items": {
32
+ "type": "string"
33
+ },
34
+ "default": ["**/*.spec.ts"],
35
+ "description": "Globs of files to include, relative to project root. \nThere are 2 special cases:\n - when a path to directory is provided, all spec files ending \".spec.@(ts|tsx)\" will be included\n - when a path to a file is provided, and a matching spec file exists it will be included instead."
36
+ },
37
+ "exclude": {
38
+ "type": "array",
39
+ "items": {
40
+ "type": "string"
41
+ },
42
+ "default": [],
43
+ "description": "Globs of files to exclude, relative to the project root."
44
+ },
45
+ "watch": {
46
+ "type": "boolean",
47
+ "description": "Run build when files change."
48
+ },
49
+ "codeCoverage": {
50
+ "type": "boolean",
51
+ "description": "Output a code coverage report.",
52
+ "default": false
53
+ },
54
+ "codeCoverageExclude": {
55
+ "type": "array",
56
+ "description": "Globs to exclude from code coverage.",
57
+ "items": {
58
+ "type": "string"
59
+ },
60
+ "default": []
61
+ },
62
+ "reporters": {
63
+ "type": "array",
64
+ "description": "Test runner reporters to use. Directly passed to the test runner.",
65
+ "items": {
66
+ "type": "string"
67
+ }
68
+ }
69
+ },
70
+ "additionalProperties": false,
71
+ "required": ["buildTarget", "tsConfig", "runner"]
72
+ }
@@ -37,8 +37,8 @@ const wasm_plugin_1 = require("./wasm-plugin");
37
37
  function createBrowserCodeBundleOptions(options, target, sourceFileCache, stylesheetBundler, angularCompilation, templateUpdates) {
38
38
  return (loadCache) => {
39
39
  const { entryPoints, outputNames, polyfills } = options;
40
- const pluginOptions = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, sourceFileCache, loadCache, templateUpdates);
41
40
  const zoneless = (0, utils_1.isZonelessApp)(polyfills);
41
+ const pluginOptions = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, sourceFileCache, loadCache, templateUpdates);
42
42
  const buildOptions = {
43
43
  ...getEsBuildCommonOptions(options),
44
44
  platform: 'browser',
@@ -51,22 +51,16 @@ function createBrowserCodeBundleOptions(options, target, sourceFileCache, styles
51
51
  entryPoints,
52
52
  target,
53
53
  supported: (0, utils_1.getFeatureSupport)(target, zoneless),
54
- plugins: [
55
- (0, loader_import_attribute_plugin_1.createLoaderImportAttributePlugin)(),
56
- (0, wasm_plugin_1.createWasmPlugin)({ allowAsync: zoneless, cache: loadCache }),
57
- (0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)(),
58
- (0, angular_localize_init_warning_plugin_1.createAngularLocalizeInitWarningPlugin)(),
59
- (0, compiler_plugin_1.createCompilerPlugin)(
60
- // JS/TS options
61
- pluginOptions, angularCompilation,
62
- // Component stylesheet bundler
63
- stylesheetBundler),
64
- ],
65
54
  };
55
+ buildOptions.plugins ??= [];
56
+ buildOptions.plugins.push((0, wasm_plugin_1.createWasmPlugin)({ allowAsync: zoneless, cache: loadCache }), (0, angular_localize_init_warning_plugin_1.createAngularLocalizeInitWarningPlugin)(), (0, compiler_plugin_1.createCompilerPlugin)(
57
+ // JS/TS options
58
+ pluginOptions, angularCompilation,
59
+ // Component stylesheet bundler
60
+ stylesheetBundler));
66
61
  if (options.plugins) {
67
- buildOptions.plugins?.push(...options.plugins);
62
+ buildOptions.plugins.push(...options.plugins);
68
63
  }
69
- appendOptionsForExternalPackages(options, buildOptions);
70
64
  return buildOptions;
71
65
  };
72
66
  }
@@ -186,20 +180,15 @@ function createServerMainCodeBundleOptions(options, target, sourceFileCache, sty
186
180
  },
187
181
  entryPoints,
188
182
  supported: (0, utils_1.getFeatureSupport)(target, zoneless),
189
- plugins: [
190
- (0, wasm_plugin_1.createWasmPlugin)({ allowAsync: zoneless, cache: loadResultCache }),
191
- (0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)(),
192
- (0, angular_localize_init_warning_plugin_1.createAngularLocalizeInitWarningPlugin)(),
193
- (0, compiler_plugin_1.createCompilerPlugin)(
194
- // JS/TS options
195
- pluginOptions,
196
- // Browser compilation handles the actual Angular code compilation
197
- new compilation_1.NoopCompilation(),
198
- // Component stylesheet bundler
199
- stylesheetBundler),
200
- ],
201
183
  };
202
184
  buildOptions.plugins ??= [];
185
+ buildOptions.plugins.push((0, wasm_plugin_1.createWasmPlugin)({ allowAsync: zoneless, cache: loadResultCache }), (0, angular_localize_init_warning_plugin_1.createAngularLocalizeInitWarningPlugin)(), (0, compiler_plugin_1.createCompilerPlugin)(
186
+ // JS/TS options
187
+ pluginOptions,
188
+ // Browser compilation handles the actual Angular code compilation
189
+ new compilation_1.NoopCompilation(),
190
+ // Component stylesheet bundler
191
+ stylesheetBundler));
203
192
  if (!externalPackages) {
204
193
  buildOptions.plugins.push((0, rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin)());
205
194
  }
@@ -259,7 +248,6 @@ function createServerMainCodeBundleOptions(options, target, sourceFileCache, sty
259
248
  if (options.plugins) {
260
249
  buildOptions.plugins.push(...options.plugins);
261
250
  }
262
- appendOptionsForExternalPackages(options, buildOptions);
263
251
  return buildOptions;
264
252
  };
265
253
  }
@@ -291,19 +279,15 @@ function createSsrEntryCodeBundleOptions(options, target, sourceFileCache, style
291
279
  'server': ssrEntryNamespace,
292
280
  },
293
281
  supported: (0, utils_1.getFeatureSupport)(target, true),
294
- plugins: [
295
- (0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)(),
296
- (0, angular_localize_init_warning_plugin_1.createAngularLocalizeInitWarningPlugin)(),
297
- (0, compiler_plugin_1.createCompilerPlugin)(
298
- // JS/TS options
299
- pluginOptions,
300
- // Browser compilation handles the actual Angular code compilation
301
- new compilation_1.NoopCompilation(),
302
- // Component stylesheet bundler
303
- stylesheetBundler),
304
- ],
305
282
  };
306
283
  buildOptions.plugins ??= [];
284
+ buildOptions.plugins.push((0, angular_localize_init_warning_plugin_1.createAngularLocalizeInitWarningPlugin)(), (0, compiler_plugin_1.createCompilerPlugin)(
285
+ // JS/TS options
286
+ pluginOptions,
287
+ // Browser compilation handles the actual Angular code compilation
288
+ new compilation_1.NoopCompilation(),
289
+ // Component stylesheet bundler
290
+ stylesheetBundler));
307
291
  if (!externalPackages) {
308
292
  buildOptions.plugins.push((0, rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin)());
309
293
  }
@@ -360,17 +344,16 @@ function createSsrEntryCodeBundleOptions(options, target, sourceFileCache, style
360
344
  if (options.plugins) {
361
345
  buildOptions.plugins.push(...options.plugins);
362
346
  }
363
- appendOptionsForExternalPackages(options, buildOptions);
364
347
  return buildOptions;
365
348
  };
366
349
  }
367
350
  function getEsBuildServerCommonOptions(options) {
368
351
  const isNodePlatform = options.ssrOptions?.platform !== schema_1.ExperimentalPlatform.Neutral;
369
- const commonOptons = getEsBuildCommonOptions(options);
370
- commonOptons.define ??= {};
371
- commonOptons.define['ngServerMode'] = 'true';
352
+ const commonOptions = getEsBuildCommonOptions(options);
353
+ commonOptions.define ??= {};
354
+ commonOptions.define['ngServerMode'] = 'true';
372
355
  return {
373
- ...commonOptons,
356
+ ...commonOptions,
374
357
  platform: isNodePlatform ? 'node' : 'neutral',
375
358
  outExtension: { '.js': '.mjs' },
376
359
  // Note: `es2015` is needed for RxJS v6. If not specified, `module` would
@@ -413,14 +396,32 @@ function getEsBuildCommonOptions(options) {
413
396
  }
414
397
  else {
415
398
  // Include default conditions
416
- conditions.push('module');
417
- conditions.push(optimizationOptions.scripts ? 'production' : 'development');
399
+ conditions.push('module', optimizationOptions.scripts ? 'production' : 'development');
400
+ }
401
+ const plugins = [
402
+ (0, loader_import_attribute_plugin_1.createLoaderImportAttributePlugin)(),
403
+ (0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)(),
404
+ ];
405
+ let packages = 'bundle';
406
+ if (options.externalPackages) {
407
+ // Package files affected by a customized loader should not be implicitly marked as external
408
+ if (options.loaderExtensions ||
409
+ options.plugins ||
410
+ typeof options.externalPackages === 'object') {
411
+ // Plugin must be added after custom plugins to ensure any added loader options are considered
412
+ plugins.push((0, external_packages_plugin_1.createExternalPackagesPlugin)(options.externalPackages !== true ? options.externalPackages : undefined));
413
+ packages = 'bundle';
414
+ }
415
+ else {
416
+ // Safe to use the packages external option directly
417
+ packages = 'external';
418
+ }
418
419
  }
419
420
  return {
420
421
  absWorkingDir: workspaceRoot,
421
422
  format: 'esm',
422
423
  bundle: true,
423
- packages: 'bundle',
424
+ packages,
424
425
  assetNames: outputNames.media,
425
426
  conditions,
426
427
  resolveExtensions: ['.ts', '.tsx', '.mjs', '.js', '.cjs'],
@@ -453,15 +454,14 @@ function getEsBuildCommonOptions(options) {
453
454
  },
454
455
  loader: loaderExtensions,
455
456
  footer,
457
+ plugins,
456
458
  };
457
459
  }
458
460
  function getEsBuildCommonPolyfillsOptions(options, namespace, tryToResolvePolyfillsAsRelative, loadResultCache) {
459
461
  const { jit, workspaceRoot, i18nOptions } = options;
460
- const buildOptions = {
461
- ...getEsBuildCommonOptions(options),
462
- splitting: false,
463
- plugins: [(0, sourcemap_ignorelist_plugin_1.createSourcemapIgnorelistPlugin)()],
464
- };
462
+ const buildOptions = getEsBuildCommonOptions(options);
463
+ buildOptions.splitting = false;
464
+ buildOptions.plugins ??= [];
465
465
  let polyfills = options.polyfills ? [...options.polyfills] : [];
466
466
  // Angular JIT mode requires the runtime compiler
467
467
  if (jit) {
@@ -486,12 +486,12 @@ function getEsBuildCommonPolyfillsOptions(options, namespace, tryToResolvePolyfi
486
486
  needLocaleDataPlugin = true;
487
487
  }
488
488
  if (needLocaleDataPlugin) {
489
- buildOptions.plugins?.push((0, i18n_locale_plugin_1.createAngularLocaleDataPlugin)());
489
+ buildOptions.plugins.push((0, i18n_locale_plugin_1.createAngularLocaleDataPlugin)());
490
490
  }
491
491
  if (polyfills.length === 0) {
492
492
  return;
493
493
  }
494
- buildOptions.plugins?.push((0, virtual_module_plugin_1.createVirtualModulePlugin)({
494
+ buildOptions.plugins.push((0, virtual_module_plugin_1.createVirtualModulePlugin)({
495
495
  namespace,
496
496
  cache: loadResultCache,
497
497
  loadContent: async (_, build) => {
@@ -539,19 +539,3 @@ function entryFileToWorkspaceRelative(workspaceRoot, entryFile) {
539
539
  .replace(/.[mc]?ts$/, '')
540
540
  .replace(/\\/g, '/'));
541
541
  }
542
- function appendOptionsForExternalPackages(options, buildOptions) {
543
- if (!options.externalPackages) {
544
- return;
545
- }
546
- buildOptions.plugins ??= [];
547
- // Package files affected by a customized loader should not be implicitly marked as external
548
- if (options.loaderExtensions || options.plugins || typeof options.externalPackages === 'object') {
549
- // Plugin must be added after custom plugins to ensure any added loader options are considered
550
- buildOptions.plugins.push((0, external_packages_plugin_1.createExternalPackagesPlugin)(options.externalPackages !== true ? options.externalPackages : undefined));
551
- buildOptions.packages = undefined;
552
- }
553
- else {
554
- // Safe to use the packages external option directly
555
- buildOptions.packages = 'external';
556
- }
557
- }
@@ -27,7 +27,7 @@ function createChromeDevtoolsMiddleware(cacheDir, projectRoot) {
27
27
  if (!devtoolsConfig) {
28
28
  // We store the UUID and re-use it to ensure Chrome does not repeatedly ask for permissions when restarting the dev server.
29
29
  try {
30
- const devtoolsConfig = (0, node_fs_1.readFileSync)(devtoolsConfigPath, 'utf-8');
30
+ devtoolsConfig = (0, node_fs_1.readFileSync)(devtoolsConfigPath, 'utf-8');
31
31
  const devtoolsConfigJson = JSON.parse(devtoolsConfig);
32
32
  node_assert_1.default.equal(projectRoot, devtoolsConfigJson?.workspace.root);
33
33
  }
@@ -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-next.7';
13
+ const VERSION = '20.0.0-next.9';
14
14
  function hasCacheMetadata(value) {
15
15
  return (!!value &&
16
16
  typeof value === 'object' &&
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ /**
9
+ * Normalize a directory path string.
10
+ * Currently only removes a trailing slash if present.
11
+ * @param path A path string.
12
+ * @returns A normalized path string.
13
+ */
14
+ export declare function normalizeDirectoryPath(path: string): string;
15
+ export declare function getProjectRootPaths(workspaceRoot: string, projectMetadata: {
16
+ root?: string;
17
+ sourceRoot?: string;
18
+ }): {
19
+ projectRoot: string;
20
+ projectSourceRoot: string;
21
+ };
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.normalizeDirectoryPath = normalizeDirectoryPath;
11
+ exports.getProjectRootPaths = getProjectRootPaths;
12
+ const node_path_1 = require("node:path");
13
+ /**
14
+ * Normalize a directory path string.
15
+ * Currently only removes a trailing slash if present.
16
+ * @param path A path string.
17
+ * @returns A normalized path string.
18
+ */
19
+ function normalizeDirectoryPath(path) {
20
+ const last = path[path.length - 1];
21
+ if (last === '/' || last === '\\') {
22
+ return path.slice(0, -1);
23
+ }
24
+ return path;
25
+ }
26
+ function getProjectRootPaths(workspaceRoot, projectMetadata) {
27
+ const projectRoot = normalizeDirectoryPath((0, node_path_1.join)(workspaceRoot, projectMetadata.root ?? ''));
28
+ const rawSourceRoot = projectMetadata.sourceRoot;
29
+ const projectSourceRoot = normalizeDirectoryPath(rawSourceRoot === undefined ? (0, node_path_1.join)(projectRoot, 'src') : (0, node_path_1.join)(workspaceRoot, rawSourceRoot));
30
+ return { projectRoot, projectSourceRoot };
31
+ }