@angular-devkit/build-angular 15.0.1 → 15.1.0-next.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.
@@ -37,9 +37,13 @@ const path = __importStar(require("path"));
37
37
  const rxjs_1 = require("rxjs");
38
38
  const operators_1 = require("rxjs/operators");
39
39
  const utils_1 = require("../../utils");
40
+ const color_1 = require("../../utils/color");
41
+ const copy_assets_1 = require("../../utils/copy-assets");
42
+ const error_1 = require("../../utils/error");
40
43
  const i18n_inlining_1 = require("../../utils/i18n-inlining");
41
44
  const output_paths_1 = require("../../utils/output-paths");
42
45
  const purge_cache_1 = require("../../utils/purge-cache");
46
+ const spinner_1 = require("../../utils/spinner");
43
47
  const version_1 = require("../../utils/version");
44
48
  const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
45
49
  const configs_1 = require("../../webpack/configs");
@@ -54,7 +58,7 @@ function execute(options, context, transforms = {}) {
54
58
  (0, version_1.assertCompatibleAngularVersion)(root);
55
59
  const baseOutputPath = path.resolve(root, options.outputPath);
56
60
  let outputPaths;
57
- return (0, rxjs_1.from)(initialize(options, context, transforms.webpackConfiguration)).pipe((0, operators_1.concatMap)(({ config, i18n }) => {
61
+ return (0, rxjs_1.from)(initialize(options, context, transforms.webpackConfiguration)).pipe((0, operators_1.concatMap)(({ config, i18n, projectRoot, projectSourceRoot }) => {
58
62
  return (0, build_webpack_1.runWebpack)(config, context, {
59
63
  webpackFactory: require('webpack'),
60
64
  logging: (stats, config) => {
@@ -63,19 +67,47 @@ function execute(options, context, transforms = {}) {
63
67
  }
64
68
  },
65
69
  }).pipe((0, operators_1.concatMap)(async (output) => {
70
+ var _a;
66
71
  const { emittedFiles = [], outputPath, webpackStats } = output;
67
72
  if (!webpackStats) {
68
73
  throw new Error('Webpack stats build result is required.');
69
74
  }
70
- let success = output.success;
71
- if (success && i18n.shouldInline) {
72
- outputPaths = (0, output_paths_1.ensureOutputPaths)(baseOutputPath, i18n);
73
- success = await (0, i18n_inlining_1.i18nInlineEmittedFiles)(context, emittedFiles, i18n, baseOutputPath, Array.from(outputPaths.values()), [], outputPath, options.i18nMissingTranslation);
75
+ if (!output.success) {
76
+ return output;
77
+ }
78
+ const spinner = new spinner_1.Spinner();
79
+ spinner.enabled = options.progress !== false;
80
+ outputPaths = (0, output_paths_1.ensureOutputPaths)(baseOutputPath, i18n);
81
+ // Copy assets
82
+ if (!options.watch && ((_a = options.assets) === null || _a === void 0 ? void 0 : _a.length)) {
83
+ spinner.start('Copying assets...');
84
+ try {
85
+ await (0, copy_assets_1.copyAssets)((0, utils_1.normalizeAssetPatterns)(options.assets, context.workspaceRoot, projectRoot, projectSourceRoot), Array.from(outputPaths.values()), context.workspaceRoot);
86
+ spinner.succeed('Copying assets complete.');
87
+ }
88
+ catch (err) {
89
+ spinner.fail(color_1.colors.redBright('Copying of assets failed.'));
90
+ (0, error_1.assertIsError)(err);
91
+ return {
92
+ ...output,
93
+ success: false,
94
+ error: 'Unable to copy assets: ' + err.message,
95
+ };
96
+ }
97
+ }
98
+ if (i18n.shouldInline) {
99
+ const success = await (0, i18n_inlining_1.i18nInlineEmittedFiles)(context, emittedFiles, i18n, baseOutputPath, Array.from(outputPaths.values()), [], outputPath, options.i18nMissingTranslation);
100
+ if (!success) {
101
+ return {
102
+ ...output,
103
+ success: false,
104
+ };
105
+ }
74
106
  }
75
107
  (0, stats_1.webpackStatsLogger)(context.logger, webpackStats, config);
76
- return { ...output, success };
108
+ return output;
77
109
  }));
78
- }), (0, operators_1.map)((output) => {
110
+ }), (0, operators_1.concatMap)(async (output) => {
79
111
  if (!output.success) {
80
112
  return output;
81
113
  }
@@ -102,8 +134,10 @@ async function initialize(options, context, webpackConfigurationTransform) {
102
134
  await (0, purge_cache_1.purgeStaleBuildCache)(context);
103
135
  const browserslist = (await Promise.resolve().then(() => __importStar(require('browserslist')))).default;
104
136
  const originalOutputPath = options.outputPath;
105
- const { config, i18n } = await (0, webpack_browser_config_1.generateI18nBrowserWebpackConfigFromContext)({
106
- ...options,
137
+ // Assets are processed directly by the builder except when watching
138
+ const adjustedOptions = options.watch ? options : { ...options, assets: [] };
139
+ const { config, projectRoot, projectSourceRoot, i18n } = await (0, webpack_browser_config_1.generateI18nBrowserWebpackConfigFromContext)({
140
+ ...adjustedOptions,
107
141
  buildOptimizer: false,
108
142
  aot: true,
109
143
  platform: 'server',
@@ -119,7 +153,7 @@ async function initialize(options, context, webpackConfigurationTransform) {
119
153
  (0, utils_1.deleteOutputDir)(context.workspaceRoot, originalOutputPath);
120
154
  }
121
155
  const transformedConfig = (_a = (await (webpackConfigurationTransform === null || webpackConfigurationTransform === void 0 ? void 0 : webpackConfigurationTransform(config)))) !== null && _a !== void 0 ? _a : config;
122
- return { config: transformedConfig, i18n };
156
+ return { config: transformedConfig, i18n, projectRoot, projectSourceRoot };
123
157
  }
124
158
  /**
125
159
  * Add `@angular/platform-server` exports.
@@ -1,4 +1,8 @@
1
1
  export interface Schema {
2
+ /**
3
+ * List of static application assets.
4
+ */
5
+ assets?: AssetPattern[];
2
6
  /**
3
7
  * Delete the output path before building.
4
8
  */
@@ -110,6 +114,30 @@ export interface Schema {
110
114
  */
111
115
  watch?: boolean;
112
116
  }
117
+ export declare type AssetPattern = AssetPatternClass | string;
118
+ export interface AssetPatternClass {
119
+ /**
120
+ * Allow glob patterns to follow symlink directories. This allows subdirectories of the
121
+ * symlink to be searched.
122
+ */
123
+ followSymlinks?: boolean;
124
+ /**
125
+ * The pattern to match.
126
+ */
127
+ glob: string;
128
+ /**
129
+ * An array of globs to ignore.
130
+ */
131
+ ignore?: string[];
132
+ /**
133
+ * The input directory path in which to apply 'glob'. Defaults to the project root.
134
+ */
135
+ input: string;
136
+ /**
137
+ * Absolute path within the output.
138
+ */
139
+ output: string;
140
+ }
113
141
  export interface FileReplacement {
114
142
  replace?: string;
115
143
  replaceWith?: string;
@@ -4,6 +4,14 @@
4
4
  "title": "Universal Target",
5
5
  "type": "object",
6
6
  "properties": {
7
+ "assets": {
8
+ "type": "array",
9
+ "description": "List of static application assets.",
10
+ "default": [],
11
+ "items": {
12
+ "$ref": "#/definitions/assetPattern"
13
+ }
14
+ },
7
15
  "main": {
8
16
  "type": "string",
9
17
  "description": "The name of the main entry-point file."
@@ -212,6 +220,44 @@
212
220
  "additionalProperties": false,
213
221
  "required": ["outputPath", "main", "tsConfig"],
214
222
  "definitions": {
223
+ "assetPattern": {
224
+ "oneOf": [
225
+ {
226
+ "type": "object",
227
+ "properties": {
228
+ "followSymlinks": {
229
+ "type": "boolean",
230
+ "default": false,
231
+ "description": "Allow glob patterns to follow symlink directories. This allows subdirectories of the symlink to be searched."
232
+ },
233
+ "glob": {
234
+ "type": "string",
235
+ "description": "The pattern to match."
236
+ },
237
+ "input": {
238
+ "type": "string",
239
+ "description": "The input directory path in which to apply 'glob'. Defaults to the project root."
240
+ },
241
+ "ignore": {
242
+ "description": "An array of globs to ignore.",
243
+ "type": "array",
244
+ "items": {
245
+ "type": "string"
246
+ }
247
+ },
248
+ "output": {
249
+ "type": "string",
250
+ "description": "Absolute path within the output."
251
+ }
252
+ },
253
+ "additionalProperties": false,
254
+ "required": ["glob", "input", "output"]
255
+ },
256
+ {
257
+ "type": "string"
258
+ }
259
+ ]
260
+ },
215
261
  "fileReplacement": {
216
262
  "oneOf": [
217
263
  {
@@ -6,10 +6,16 @@
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
8
  /// <reference types="node" />
9
- /// <reference types="node" />
10
9
  import { RawSourceMap } from '@ampproject/remapping';
11
- import { Dirent } from 'node:fs';
12
10
  import type { FileImporter, Importer, ImporterResult } from 'sass';
11
+ /**
12
+ * A preprocessed cache entry for the files and directories within a previously searched
13
+ * directory when performing Sass import resolution.
14
+ */
15
+ export interface DirectoryEntry {
16
+ files: Set<string>;
17
+ directories: Set<string>;
18
+ }
13
19
  /**
14
20
  * A Sass Importer base class that provides the load logic to rebase all `url()` functions
15
21
  * within a stylesheet. The rebasing will ensure that the URLs in the output of the Sass compiler
@@ -42,7 +48,7 @@ declare abstract class UrlRebasingImporter implements Importer<'sync'> {
42
48
  */
43
49
  export declare class RelativeUrlRebasingImporter extends UrlRebasingImporter {
44
50
  private directoryCache;
45
- constructor(entryDirectory: string, directoryCache?: Map<string, Dirent[]>, rebaseSourceMaps?: Map<string, RawSourceMap>);
51
+ constructor(entryDirectory: string, directoryCache?: Map<string, DirectoryEntry>, rebaseSourceMaps?: Map<string, RawSourceMap>);
46
52
  canonicalize(url: string, options: {
47
53
  fromImport: boolean;
48
54
  }): URL | null;
@@ -59,7 +65,7 @@ export declare class RelativeUrlRebasingImporter extends UrlRebasingImporter {
59
65
  * Checks an array of potential stylesheet files to determine if there is a valid
60
66
  * stylesheet file. More than one discovered file may indicate an error.
61
67
  * @param found An array of discovered stylesheet files.
62
- * @returns A fully resolved URL for a stylesheet file or `null` if not found.
68
+ * @returns A fully resolved path for a stylesheet file or `null` if not found.
63
69
  * @throws If there are ambiguous files discovered.
64
70
  */
65
71
  private checkFound;
@@ -71,7 +77,7 @@ export declare class RelativeUrlRebasingImporter extends UrlRebasingImporter {
71
77
  */
72
78
  export declare class ModuleUrlRebasingImporter extends RelativeUrlRebasingImporter {
73
79
  private finder;
74
- constructor(entryDirectory: string, directoryCache: Map<string, Dirent[]>, rebaseSourceMaps: Map<string, RawSourceMap> | undefined, finder: FileImporter<'sync'>['findFileUrl']);
80
+ constructor(entryDirectory: string, directoryCache: Map<string, DirectoryEntry>, rebaseSourceMaps: Map<string, RawSourceMap> | undefined, finder: FileImporter<'sync'>['findFileUrl']);
75
81
  canonicalize(url: string, options: {
76
82
  fromImport: boolean;
77
83
  }): URL | null;
@@ -83,7 +89,7 @@ export declare class ModuleUrlRebasingImporter extends RelativeUrlRebasingImport
83
89
  */
84
90
  export declare class LoadPathsUrlRebasingImporter extends RelativeUrlRebasingImporter {
85
91
  private loadPaths;
86
- constructor(entryDirectory: string, directoryCache: Map<string, Dirent[]>, rebaseSourceMaps: Map<string, RawSourceMap> | undefined, loadPaths: Iterable<string>);
92
+ constructor(entryDirectory: string, directoryCache: Map<string, DirectoryEntry>, rebaseSourceMaps: Map<string, RawSourceMap> | undefined, loadPaths: Iterable<string>);
87
93
  canonicalize(url: string, options: {
88
94
  fromImport: boolean;
89
95
  }): URL | null;
@@ -275,17 +275,6 @@ class RelativeUrlRebasingImporter extends UrlRebasingImporter {
275
275
  const hasStyleExtension = extension === '.scss' || extension === '.sass' || extension === '.css';
276
276
  // Remove the style extension if present to allow adding the `.import` suffix
277
277
  const filename = (0, node_path_1.basename)(stylesheetPath, hasStyleExtension ? extension : undefined);
278
- let entries;
279
- try {
280
- entries = this.directoryCache.get(directory);
281
- if (!entries) {
282
- entries = (0, node_fs_1.readdirSync)(directory, { withFileTypes: true });
283
- this.directoryCache.set(directory, entries);
284
- }
285
- }
286
- catch {
287
- return null;
288
- }
289
278
  const importPotentials = new Set();
290
279
  const defaultPotentials = new Set();
291
280
  if (hasStyleExtension) {
@@ -312,37 +301,69 @@ class RelativeUrlRebasingImporter extends UrlRebasingImporter {
312
301
  defaultPotentials.add('_' + filename + '.sass');
313
302
  defaultPotentials.add('_' + filename + '.css');
314
303
  }
315
- const foundDefaults = [];
316
- const foundImports = [];
304
+ let foundDefaults;
305
+ let foundImports;
317
306
  let hasPotentialIndex = false;
318
- for (const entry of entries) {
319
- // Record if the name should be checked as a directory with an index file
320
- if (checkDirectory && !hasStyleExtension && entry.name === filename && entry.isDirectory()) {
321
- hasPotentialIndex = true;
322
- }
323
- if (!entry.isFile()) {
324
- continue;
307
+ let cachedEntries = this.directoryCache.get(directory);
308
+ if (cachedEntries) {
309
+ // If there is a preprocessed cache of the directory, perform an intersection of the potentials
310
+ // and the directory files.
311
+ const { files, directories } = cachedEntries;
312
+ foundDefaults = [...defaultPotentials].filter((potential) => files.has(potential));
313
+ foundImports = [...importPotentials].filter((potential) => files.has(potential));
314
+ hasPotentialIndex = checkDirectory && !hasStyleExtension && directories.has(filename);
315
+ }
316
+ else {
317
+ // If no preprocessed cache exists, get the entries from the file system and, while searching,
318
+ // generate the cache for later requests.
319
+ let entries;
320
+ try {
321
+ entries = (0, node_fs_1.readdirSync)(directory, { withFileTypes: true });
325
322
  }
326
- if (importPotentials.has(entry.name)) {
327
- foundImports.push((0, node_path_1.join)(directory, entry.name));
323
+ catch {
324
+ return null;
328
325
  }
329
- if (defaultPotentials.has(entry.name)) {
330
- foundDefaults.push((0, node_path_1.join)(directory, entry.name));
326
+ foundDefaults = [];
327
+ foundImports = [];
328
+ cachedEntries = { files: new Set(), directories: new Set() };
329
+ for (const entry of entries) {
330
+ const isDirectory = entry.isDirectory();
331
+ if (isDirectory) {
332
+ cachedEntries.directories.add(entry.name);
333
+ }
334
+ // Record if the name should be checked as a directory with an index file
335
+ if (checkDirectory && !hasStyleExtension && entry.name === filename && isDirectory) {
336
+ hasPotentialIndex = true;
337
+ }
338
+ if (!entry.isFile()) {
339
+ continue;
340
+ }
341
+ cachedEntries.files.add(entry.name);
342
+ if (importPotentials.has(entry.name)) {
343
+ foundImports.push(entry.name);
344
+ }
345
+ if (defaultPotentials.has(entry.name)) {
346
+ foundDefaults.push(entry.name);
347
+ }
331
348
  }
349
+ this.directoryCache.set(directory, cachedEntries);
332
350
  }
333
351
  // `foundImports` will only contain elements if `options.fromImport` is true
334
352
  const result = (_a = this.checkFound(foundImports)) !== null && _a !== void 0 ? _a : this.checkFound(foundDefaults);
335
- if (result === null && hasPotentialIndex) {
353
+ if (result !== null) {
354
+ return (0, node_url_1.pathToFileURL)((0, node_path_1.join)(directory, result));
355
+ }
356
+ if (hasPotentialIndex) {
336
357
  // Check for index files using filename as a directory
337
358
  return this.resolveImport(url + '/index', fromImport, false);
338
359
  }
339
- return result;
360
+ return null;
340
361
  }
341
362
  /**
342
363
  * Checks an array of potential stylesheet files to determine if there is a valid
343
364
  * stylesheet file. More than one discovered file may indicate an error.
344
365
  * @param found An array of discovered stylesheet files.
345
- * @returns A fully resolved URL for a stylesheet file or `null` if not found.
366
+ * @returns A fully resolved path for a stylesheet file or `null` if not found.
346
367
  * @throws If there are ambiguous files discovered.
347
368
  */
348
369
  checkFound(found) {
@@ -361,9 +382,9 @@ class RelativeUrlRebasingImporter extends UrlRebasingImporter {
361
382
  }
362
383
  // Return the non-CSS file (sass/scss files have priority)
363
384
  // https://github.com/sass/dart-sass/blob/44d6bb6ac72fe6b93f5bfec371a1fffb18e6b76d/lib/src/importer/utils.dart#L44-L47
364
- return (0, node_url_1.pathToFileURL)(foundWithoutCss[0]);
385
+ return foundWithoutCss[0];
365
386
  }
366
- return (0, node_url_1.pathToFileURL)(found[0]);
387
+ return found[0];
367
388
  }
368
389
  }
369
390
  exports.RelativeUrlRebasingImporter = RelativeUrlRebasingImporter;
@@ -78,13 +78,12 @@ function normalizeAssetPatterns(assetPatterns, workspaceRoot, projectRoot, proje
78
78
  }
79
79
  // Output directory for both is the relative path from source root to input.
80
80
  const output = path.relative(resolvedSourceRoot, path.resolve(workspaceRoot, input));
81
- // Return the asset pattern in object format.
82
- return { glob, input, output };
81
+ assetPattern = { glob, input, output };
83
82
  }
84
- else {
85
- // It's already an AssetPatternObject, no need to convert.
86
- return assetPattern;
83
+ if (assetPattern.output.startsWith('..')) {
84
+ throw new Error('An asset cannot be written to a location outside of the output path.');
87
85
  }
86
+ return assetPattern;
88
87
  });
89
88
  }
90
89
  exports.normalizeAssetPatterns = normalizeAssetPatterns;