Skip to content

Commit 655d8bb

Browse files
committed
refactor(@angular/build): normalize library builder paths to POSIX format
Normalize paths at option and compilation boundaries in the library builder to POSIX format and update TSDoc comments across the library builder pipeline.
1 parent 639d31f commit 655d8bb

11 files changed

Lines changed: 159 additions & 116 deletions

File tree

‎packages/angular/build/src/builders/library/builder.ts‎

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,10 @@ export async function* executeLibraryBuilder(
111111
);
112112

113113
// Track all referenced compilation files for watch mode
114-
const watchedCompilationFiles = new Set<string>([
115-
toPosixPath(tsConfigPath),
116-
toPosixPath(packageJsonPath),
117-
]);
114+
const watchedCompilationFiles = new Set<string>([tsConfigPath, packageJsonPath]);
118115

119116
for (const entryPoint of normalizedOptions.entryPoints.values()) {
120-
watchedCompilationFiles.add(toPosixPath(entryPoint.entryFilePath));
117+
watchedCompilationFiles.add(entryPoint.entryFilePath);
121118
}
122119

123120
if (isWatchMode) {
@@ -237,6 +234,17 @@ async function executeBuild(
237234

238235
/**
239236
* Runs the watch loop, rebuilding the library as watched files are modified.
237+
*
238+
* @param watcher The file watcher instance.
239+
* @param options The normalized library builder options with POSIX paths.
240+
* @param stylesheetBundler The component stylesheet bundler instance.
241+
* @param watchedCompilationFiles Set of POSIX file paths tracked for compilation.
242+
* @param context The Architect builder context.
243+
* @param withProgress Progress wrapper function (`withSpinner` or `withNoProgress`).
244+
* @param buildState The state preserved across incremental builds.
245+
* @param buildAction The library build action function to execute.
246+
* @param hasModifiedWatchedFile Helper to check whether watched compilation files changed.
247+
* @param signal Optional abort signal to terminate the watch loop.
240248
*/
241249
async function* runWatchLoop(
242250
watcher: BuildWatcher,
@@ -253,7 +261,6 @@ async function* runWatchLoop(
253261
const { collectAssetsToEmit } = await import('./pipeline/assets');
254262

255263
const { workspaceRoot, packageJsonPath, assets, clearScreen } = options;
256-
const posixPackageJsonPath = toPosixPath(packageJsonPath);
257264

258265
for await (const changes of watcher) {
259266
if (signal?.aborted) {
@@ -293,13 +300,13 @@ async function* runWatchLoop(
293300

294301
// Check if package.json was modified
295302
let hasPackageJsonChanges = false;
296-
if (changedFiles.has(posixPackageJsonPath)) {
303+
if (changedFiles.has(packageJsonPath)) {
297304
try {
298305
const packageJson = await loadPackageJson(packageJsonPath);
299-
options.packageJson = packageJson;
300306
if (!packageJson.name) {
301307
throw new Error(`The package.json at '${packageJsonPath}' must contain a 'name'.`);
302308
}
309+
options.packageJson = packageJson;
303310
options.packageName = packageJson.name;
304311

305312
updateWatchedEntryPoints(
@@ -328,7 +335,7 @@ async function* runWatchLoop(
328335
!buildState.singleProgramCache ||
329336
buildState.hasCompilationError ||
330337
buildState.hasEntryPointsChanges ||
331-
hasModifiedWatchedFile(changedFiles, watchedCompilationFiles, posixPackageJsonPath);
338+
hasModifiedWatchedFile(changedFiles, watchedCompilationFiles, packageJsonPath);
332339

333340
const assetsToEmit = await collectAssetsToEmit(
334341
assets,
@@ -388,7 +395,7 @@ function logMessages(
388395
* @param logger The Architect logger API.
389396
* @param startTime High-resolution timestamp recorded when the build iteration started.
390397
* @param success Whether the build iteration succeeded without errors.
391-
* @param outputPath Optional output directory path to log on successful builds.
398+
* @param outputPath Optional POSIX output directory path to log on successful builds.
392399
*/
393400
function logBuildResult(
394401
logger: logging.LoggerApi,

‎packages/angular/build/src/builders/library/options.ts‎

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { normalizeAssetPatterns } from '../../utils';
1313
import { supportColor } from '../../utils/color';
1414
import { assertIsError } from '../../utils/error';
1515
import { normalizeCacheOptions } from '../../utils/normalize-cache';
16+
import { toPosixPath } from '../../utils/path';
1617
import {
1718
generateSearchDirectories,
1819
getTailwindConfig,
@@ -24,14 +25,25 @@ import { loadPackageJson } from './pipeline/utils';
2425
import type { Schema as LibraryBuilderOptions } from './schema';
2526
import type { NormalizedLibraryOptions, PackageJsonData } from './types';
2627

28+
/**
29+
* Normalizes raw library builder options into {@link NormalizedLibraryOptions} with all
30+
* file and directory paths normalized to POSIX format.
31+
*
32+
* @param context The Architect builder context.
33+
* @param projectName The name of the library project.
34+
* @param options The raw library builder schema options.
35+
* @returns A promise resolving to the normalized library options.
36+
*/
2737
export async function normalizeLibraryOptions(
2838
context: BuilderContext,
2939
projectName: string,
3040
options: LibraryBuilderOptions,
3141
): Promise<NormalizedLibraryOptions> {
32-
const { workspaceRoot } = context;
42+
const workspaceRoot = toPosixPath(context.workspaceRoot);
3343
const projectMetadata = await context.getProjectMetadata(projectName);
34-
const { projectRoot, projectSourceRoot } = getProjectRootPaths(workspaceRoot, projectMetadata);
44+
const projectRootPaths = getProjectRootPaths(workspaceRoot, projectMetadata);
45+
const projectRoot = toPosixPath(projectRootPaths.projectRoot);
46+
const projectSourceRoot = toPosixPath(projectRootPaths.projectSourceRoot);
3547

3648
const {
3749
tsConfig,
@@ -50,8 +62,8 @@ export async function normalizeLibraryOptions(
5062
clearScreen,
5163
} = options;
5264

53-
const resolvedTsConfigPath = path.resolve(workspaceRoot, tsConfig);
54-
const packageJsonPath = path.join(projectRoot, 'package.json');
65+
const resolvedTsConfigPath = toPosixPath(path.resolve(workspaceRoot, tsConfig));
66+
const packageJsonPath = path.posix.join(projectRoot, 'package.json');
5567

5668
let packageJson: PackageJsonData;
5769
try {
@@ -99,7 +111,7 @@ export async function normalizeLibraryOptions(
99111
}
100112
defaultAssets.push({
101113
glob: 'README.md',
102-
input: path.dirname(entryPoint.entryFilePath),
114+
input: path.posix.dirname(entryPoint.entryFilePath),
103115
output: entryPoint.name,
104116
});
105117
}
@@ -109,7 +121,11 @@ export async function normalizeLibraryOptions(
109121
workspaceRoot,
110122
projectRoot,
111123
projectSourceRoot,
112-
);
124+
).map((asset) => ({
125+
...asset,
126+
input: toPosixPath(path.resolve(workspaceRoot, asset.input)),
127+
output: toPosixPath(asset.output),
128+
}));
113129

114130
const cacheOptions = normalizeCacheOptions(
115131
projectMetadata,
@@ -119,7 +135,7 @@ export async function normalizeLibraryOptions(
119135
);
120136

121137
const styleIncludePaths = (stylePreprocessorOptions?.includePaths ?? []).map((p: string) =>
122-
path.resolve(workspaceRoot, p),
138+
toPosixPath(path.resolve(workspaceRoot, p)),
123139
);
124140

125141
const searchDirectories = await generateSearchDirectories([projectRoot, workspaceRoot]);
@@ -133,7 +149,9 @@ export async function normalizeLibraryOptions(
133149
projectRoot,
134150
packageName,
135151
packageJson,
136-
outputPath: path.resolve(workspaceRoot, options.outputPath ?? path.join('dist', projectName)),
152+
outputPath: toPosixPath(
153+
path.resolve(workspaceRoot, options.outputPath ?? path.posix.join('dist', projectName)),
154+
),
137155
deleteOutputPath,
138156
packageJsonPath,
139157
tsConfigPath: resolvedTsConfigPath,

‎packages/angular/build/src/builders/library/pipeline/assets.ts‎

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,16 @@
99
import { statSync } from 'node:fs';
1010
import path from 'node:path';
1111
import picomatch from 'picomatch';
12-
import { toPosixPath } from '../../../utils/path';
1312
import { DEFAULT_ASSET_IGNORE, resolveAssets } from '../../../utils/resolve-assets';
1413
import type { NormalizedLibraryOptions } from '../types';
1514
import { type DiskOutputFile, createDiskOutputFile } from './utils';
1615

1716
/**
1817
* Resolves and collects configured library assets to be emitted to disk.
1918
*
20-
* @param assets The normalized asset patterns.
21-
* @param workspaceRoot The workspace root directory path.
22-
* @param modifiedFiles Optional set of modified file paths for incremental copying in watch mode.
19+
* @param assets The normalized asset patterns with POSIX paths.
20+
* @param workspaceRoot The normalized POSIX workspace root directory path.
21+
* @param modifiedFiles Optional set of modified POSIX file paths for incremental copying in watch mode.
2322
* @returns An array of disk file emission descriptors.
2423
*/
2524
export async function collectAssetsToEmit(
@@ -32,25 +31,22 @@ export async function collectAssetsToEmit(
3231
}
3332

3433
if (modifiedFiles) {
35-
const matchers = createAssetMatchers(assets, workspaceRoot);
34+
const matchers = createAssetMatchers(assets);
3635
const filesToEmit: DiskOutputFile[] = [];
3736

3837
for (const file of modifiedFiles) {
39-
const resolvedFile = path.isAbsolute(file) ? file : path.resolve(workspaceRoot, file);
40-
const posixFile = toPosixPath(resolvedFile);
41-
42-
for (const { asset, posixInputPrefix, isMatch } of matchers) {
43-
if (!posixFile.startsWith(posixInputPrefix)) {
38+
for (const { asset, isMatch } of matchers) {
39+
const relative = path.posix.relative(asset.input, file);
40+
if (!relative || relative.startsWith('..') || path.posix.isAbsolute(relative)) {
4441
continue;
4542
}
4643

47-
const relative = posixFile.slice(posixInputPrefix.length);
4844
if (!isMatch(relative)) {
4945
continue;
5046
}
5147

52-
if (statSync(resolvedFile, { throwIfNoEntry: false })?.isFile()) {
53-
filesToEmit.push(createDiskOutputFile(resolvedFile, path.join(asset.output, relative)));
48+
if (statSync(file, { throwIfNoEntry: false })?.isFile()) {
49+
filesToEmit.push(createDiskOutputFile(file, path.posix.join(asset.output, relative)));
5450
}
5551
}
5652
}
@@ -63,15 +59,13 @@ export async function collectAssetsToEmit(
6359
return resolvedAssets.map(({ source, destination }) => createDiskOutputFile(source, destination));
6460
}
6561

66-
function createAssetMatchers(assets: NormalizedLibraryOptions['assets'], workspaceRoot: string) {
62+
function createAssetMatchers(assets: NormalizedLibraryOptions['assets']) {
6763
return assets.map((asset) => {
68-
const absInput = path.resolve(workspaceRoot, asset.input);
69-
const posixInput = toPosixPath(absInput).replace(/\/+$/, '');
7064
const isMatch = picomatch(asset.glob, {
7165
dot: true,
7266
ignore: [...DEFAULT_ASSET_IGNORE, ...(asset.ignore ?? [])],
7367
});
7468

75-
return { asset, posixInputPrefix: `${posixInput}/`, isMatch };
69+
return { asset, isMatch };
7670
});
7771
}

‎packages/angular/build/src/builders/library/pipeline/build-action.ts‎

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import path from 'node:path';
1111
import type { ComponentStylesheetBundler } from '../../../tools/esbuild/angular/component-stylesheets';
1212
import { emitFilesToDisk } from '../../../utils/emit-files-to-disk';
1313
import { assertIsError } from '../../../utils/error';
14-
import { toPosixPath } from '../../../utils/path';
1514
import type { NormalizedLibraryOptions, SingleBuildState } from '../types';
1615
import { collectAssetsToEmit } from './assets';
1716
import { bundleEntryPoints } from './bundler';
@@ -49,7 +48,7 @@ export function createSingleBuildState(): SingleBuildState {
4948
* Context required to execute a single library build iteration.
5049
*/
5150
export interface BuildActionContext {
52-
/** Normalized options for the library build. */
51+
/** Normalized options for the library build with POSIX paths. */
5352
options: NormalizedLibraryOptions;
5453

5554
/** The Architect builder context. */
@@ -62,7 +61,7 @@ export interface BuildActionContext {
6261
isWatchMode: boolean;
6362

6463
/**
65-
* Set of file paths tracked for compilation and configuration
64+
* Set of POSIX file paths tracked for compilation and configuration
6665
* (including `tsconfig.json`, `package.json`, entry points, and referenced source,
6766
* template, and stylesheet files). Updated during compilation and used to determine
6867
* whether file changes require recompiling entry points, excluding asset files so
@@ -73,7 +72,7 @@ export interface BuildActionContext {
7372
/** State preserved across incremental builds in watch mode. */
7473
buildState: SingleBuildState;
7574

76-
/** Set of file paths modified since the last build iteration. */
75+
/** Set of POSIX file paths modified since the last build iteration. */
7776
modifiedFiles?: Set<string>;
7877

7978
/**
@@ -91,6 +90,7 @@ export interface BuildActionContext {
9190
* manifest generation, and asset copying.
9291
*
9392
* @param actionContext The build action state and configuration.
93+
* @returns A promise resolving to the build action result containing errors, warnings, and logs.
9494
*/
9595
export async function buildAction(actionContext: BuildActionContext): Promise<BuildActionResult> {
9696
const {
@@ -106,10 +106,10 @@ export async function buildAction(actionContext: BuildActionContext): Promise<Bu
106106
const warnings: string[] = [];
107107
const logs: string[] = [];
108108

109-
const posixPackageJsonPath = toPosixPath(options.packageJsonPath);
109+
const { packageJsonPath } = options;
110110
const { pendingChangedEsmFiles, pendingChangedDtsFiles } = buildState;
111111

112-
if (!modifiedFiles || modifiedFiles.has(posixPackageJsonPath)) {
112+
if (!modifiedFiles || modifiedFiles.has(packageJsonPath)) {
113113
buildState.hasEmittedManifests = false;
114114
}
115115

@@ -120,7 +120,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<Bu
120120
buildState.hasEntryPointsChanges ||
121121
pendingChangedEsmFiles.size > 0 ||
122122
pendingChangedDtsFiles.size > 0 ||
123-
hasModifiedWatchedFile(modifiedFiles, watchedCompilationFiles, posixPackageJsonPath);
123+
hasModifiedWatchedFile(modifiedFiles, watchedCompilationFiles, packageJsonPath);
124124

125125
try {
126126
if (!buildState.hasEmittedManifests) {
@@ -172,13 +172,12 @@ export async function buildAction(actionContext: BuildActionContext): Promise<Bu
172172
pendingChangedDtsFiles.add(file);
173173
}
174174

175-
const itemsToBundle = collectEntryPointsToBundle(options, buildState, logs);
176-
177175
let bundleOutput: Awaited<ReturnType<typeof bundleEntryPoints>> | undefined;
178176
let diagnostics: LibraryCompilationDiagnostics;
179177
if (hasStylesheetErrors) {
180178
diagnostics = await diagnosePromise;
181179
} else {
180+
const itemsToBundle = collectEntryPointsToBundle(options, buildState, logs);
182181
try {
183182
[bundleOutput, diagnostics] = await Promise.all([
184183
bundleEntryPoints(itemsToBundle, esmFiles, dtsFiles, {
@@ -243,7 +242,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise<Bu
243242
));
244243
filesToEmit.push(...resolvedAssetsToEmit);
245244

246-
await emitFilesToDisk(filesToEmit, (file) => path.join(options.outputPath, file.path));
245+
await emitFilesToDisk(filesToEmit, (file) => path.posix.join(options.outputPath, file.path));
247246

248247
buildState.hasEmittedManifests = true;
249248
buildState.hasEmittedTslib = hasTslibImport;
@@ -262,16 +261,16 @@ export async function buildAction(actionContext: BuildActionContext): Promise<Bu
262261
*
263262
* @param modifiedFiles Set of POSIX file paths modified since the last build.
264263
* @param watchedCompilationFiles Set of POSIX file paths tracked for compilation.
265-
* @param posixPackageJsonPath The normalized POSIX path to the library's `package.json`.
264+
* @param packageJsonPath The normalized POSIX path to the library's `package.json`.
266265
* @returns `true` if a watched compilation file was modified; otherwise `false`.
267266
*/
268267
export function hasModifiedWatchedFile(
269268
modifiedFiles: ReadonlySet<string>,
270269
watchedCompilationFiles: ReadonlySet<string>,
271-
posixPackageJsonPath: string,
270+
packageJsonPath: string,
272271
): boolean {
273272
for (const file of modifiedFiles) {
274-
if (file !== posixPackageJsonPath && watchedCompilationFiles.has(file)) {
273+
if (file !== packageJsonPath && watchedCompilationFiles.has(file)) {
275274
return true;
276275
}
277276
}

0 commit comments

Comments
 (0)