diff --git a/packages/angular/build/src/builders/library/builder.ts b/packages/angular/build/src/builders/library/builder.ts index 84b933e7ceb6..c3c2149adf5b 100644 --- a/packages/angular/build/src/builders/library/builder.ts +++ b/packages/angular/build/src/builders/library/builder.ts @@ -111,13 +111,10 @@ export async function* executeLibraryBuilder( ); // Track all referenced compilation files for watch mode - const watchedCompilationFiles = new Set([ - toPosixPath(tsConfigPath), - toPosixPath(packageJsonPath), - ]); + const watchedCompilationFiles = new Set([tsConfigPath, packageJsonPath]); for (const entryPoint of normalizedOptions.entryPoints.values()) { - watchedCompilationFiles.add(toPosixPath(entryPoint.entryFilePath)); + watchedCompilationFiles.add(entryPoint.entryFilePath); } if (isWatchMode) { @@ -237,6 +234,17 @@ async function executeBuild( /** * Runs the watch loop, rebuilding the library as watched files are modified. + * + * @param watcher The file watcher instance. + * @param options The normalized library builder options with POSIX paths. + * @param stylesheetBundler The component stylesheet bundler instance. + * @param watchedCompilationFiles Set of POSIX file paths tracked for compilation. + * @param context The Architect builder context. + * @param withProgress Progress wrapper function (`withSpinner` or `withNoProgress`). + * @param buildState The state preserved across incremental builds. + * @param buildAction The library build action function to execute. + * @param hasModifiedWatchedFile Helper to check whether watched compilation files changed. + * @param signal Optional abort signal to terminate the watch loop. */ async function* runWatchLoop( watcher: BuildWatcher, @@ -253,7 +261,6 @@ async function* runWatchLoop( const { collectAssetsToEmit } = await import('./pipeline/assets'); const { workspaceRoot, packageJsonPath, assets, clearScreen } = options; - const posixPackageJsonPath = toPosixPath(packageJsonPath); for await (const changes of watcher) { if (signal?.aborted) { @@ -293,13 +300,13 @@ async function* runWatchLoop( // Check if package.json was modified let hasPackageJsonChanges = false; - if (changedFiles.has(posixPackageJsonPath)) { + if (changedFiles.has(packageJsonPath)) { try { const packageJson = await loadPackageJson(packageJsonPath); - options.packageJson = packageJson; if (!packageJson.name) { throw new Error(`The package.json at '${packageJsonPath}' must contain a 'name'.`); } + options.packageJson = packageJson; options.packageName = packageJson.name; updateWatchedEntryPoints( @@ -328,7 +335,7 @@ async function* runWatchLoop( !buildState.singleProgramCache || buildState.hasCompilationError || buildState.hasEntryPointsChanges || - hasModifiedWatchedFile(changedFiles, watchedCompilationFiles, posixPackageJsonPath); + hasModifiedWatchedFile(changedFiles, watchedCompilationFiles, packageJsonPath); const assetsToEmit = await collectAssetsToEmit( assets, @@ -388,7 +395,7 @@ function logMessages( * @param logger The Architect logger API. * @param startTime High-resolution timestamp recorded when the build iteration started. * @param success Whether the build iteration succeeded without errors. - * @param outputPath Optional output directory path to log on successful builds. + * @param outputPath Optional POSIX output directory path to log on successful builds. */ function logBuildResult( logger: logging.LoggerApi, diff --git a/packages/angular/build/src/builders/library/options.ts b/packages/angular/build/src/builders/library/options.ts index b7747fb6db27..14bc4514027d 100644 --- a/packages/angular/build/src/builders/library/options.ts +++ b/packages/angular/build/src/builders/library/options.ts @@ -13,6 +13,7 @@ import { normalizeAssetPatterns } from '../../utils'; import { supportColor } from '../../utils/color'; import { assertIsError } from '../../utils/error'; import { normalizeCacheOptions } from '../../utils/normalize-cache'; +import { toPosixPath } from '../../utils/path'; import { generateSearchDirectories, getTailwindConfig, @@ -24,14 +25,25 @@ import { loadPackageJson } from './pipeline/utils'; import type { Schema as LibraryBuilderOptions } from './schema'; import type { NormalizedLibraryOptions, PackageJsonData } from './types'; +/** + * Normalizes raw library builder options into {@link NormalizedLibraryOptions} with all + * file and directory paths normalized to POSIX format. + * + * @param context The Architect builder context. + * @param projectName The name of the library project. + * @param options The raw library builder schema options. + * @returns A promise resolving to the normalized library options. + */ export async function normalizeLibraryOptions( context: BuilderContext, projectName: string, options: LibraryBuilderOptions, ): Promise { - const { workspaceRoot } = context; + const workspaceRoot = toPosixPath(context.workspaceRoot); const projectMetadata = await context.getProjectMetadata(projectName); - const { projectRoot, projectSourceRoot } = getProjectRootPaths(workspaceRoot, projectMetadata); + const projectRootPaths = getProjectRootPaths(workspaceRoot, projectMetadata); + const projectRoot = toPosixPath(projectRootPaths.projectRoot); + const projectSourceRoot = toPosixPath(projectRootPaths.projectSourceRoot); const { tsConfig, @@ -50,8 +62,8 @@ export async function normalizeLibraryOptions( clearScreen, } = options; - const resolvedTsConfigPath = path.resolve(workspaceRoot, tsConfig); - const packageJsonPath = path.join(projectRoot, 'package.json'); + const resolvedTsConfigPath = toPosixPath(path.resolve(workspaceRoot, tsConfig)); + const packageJsonPath = path.posix.join(projectRoot, 'package.json'); let packageJson: PackageJsonData; try { @@ -99,7 +111,7 @@ export async function normalizeLibraryOptions( } defaultAssets.push({ glob: 'README.md', - input: path.dirname(entryPoint.entryFilePath), + input: path.posix.dirname(entryPoint.entryFilePath), output: entryPoint.name, }); } @@ -109,7 +121,11 @@ export async function normalizeLibraryOptions( workspaceRoot, projectRoot, projectSourceRoot, - ); + ).map((asset) => ({ + ...asset, + input: toPosixPath(path.resolve(workspaceRoot, asset.input)), + output: toPosixPath(asset.output), + })); const cacheOptions = normalizeCacheOptions( projectMetadata, @@ -119,7 +135,7 @@ export async function normalizeLibraryOptions( ); const styleIncludePaths = (stylePreprocessorOptions?.includePaths ?? []).map((p: string) => - path.resolve(workspaceRoot, p), + toPosixPath(path.resolve(workspaceRoot, p)), ); const searchDirectories = await generateSearchDirectories([projectRoot, workspaceRoot]); @@ -133,7 +149,9 @@ export async function normalizeLibraryOptions( projectRoot, packageName, packageJson, - outputPath: path.resolve(workspaceRoot, options.outputPath ?? path.join('dist', projectName)), + outputPath: toPosixPath( + path.resolve(workspaceRoot, options.outputPath ?? path.posix.join('dist', projectName)), + ), deleteOutputPath, packageJsonPath, tsConfigPath: resolvedTsConfigPath, diff --git a/packages/angular/build/src/builders/library/pipeline/assets.ts b/packages/angular/build/src/builders/library/pipeline/assets.ts index e1e46f4b9f08..7b8329a7865a 100644 --- a/packages/angular/build/src/builders/library/pipeline/assets.ts +++ b/packages/angular/build/src/builders/library/pipeline/assets.ts @@ -9,7 +9,6 @@ import { statSync } from 'node:fs'; import path from 'node:path'; import picomatch from 'picomatch'; -import { toPosixPath } from '../../../utils/path'; import { DEFAULT_ASSET_IGNORE, resolveAssets } from '../../../utils/resolve-assets'; import type { NormalizedLibraryOptions } from '../types'; import { type DiskOutputFile, createDiskOutputFile } from './utils'; @@ -17,9 +16,9 @@ import { type DiskOutputFile, createDiskOutputFile } from './utils'; /** * Resolves and collects configured library assets to be emitted to disk. * - * @param assets The normalized asset patterns. - * @param workspaceRoot The workspace root directory path. - * @param modifiedFiles Optional set of modified file paths for incremental copying in watch mode. + * @param assets The normalized asset patterns with POSIX paths. + * @param workspaceRoot The normalized POSIX workspace root directory path. + * @param modifiedFiles Optional set of modified POSIX file paths for incremental copying in watch mode. * @returns An array of disk file emission descriptors. */ export async function collectAssetsToEmit( @@ -32,25 +31,22 @@ export async function collectAssetsToEmit( } if (modifiedFiles) { - const matchers = createAssetMatchers(assets, workspaceRoot); + const matchers = createAssetMatchers(assets); const filesToEmit: DiskOutputFile[] = []; for (const file of modifiedFiles) { - const resolvedFile = path.isAbsolute(file) ? file : path.resolve(workspaceRoot, file); - const posixFile = toPosixPath(resolvedFile); - - for (const { asset, posixInputPrefix, isMatch } of matchers) { - if (!posixFile.startsWith(posixInputPrefix)) { + for (const { asset, prefix, isMatch } of matchers) { + if (!file.startsWith(prefix)) { continue; } - const relative = posixFile.slice(posixInputPrefix.length); + const relative = file.slice(prefix.length); if (!isMatch(relative)) { continue; } - if (statSync(resolvedFile, { throwIfNoEntry: false })?.isFile()) { - filesToEmit.push(createDiskOutputFile(resolvedFile, path.join(asset.output, relative))); + if (statSync(file, { throwIfNoEntry: false })?.isFile()) { + filesToEmit.push(createDiskOutputFile(file, path.posix.join(asset.output, relative))); } } } @@ -63,15 +59,14 @@ export async function collectAssetsToEmit( return resolvedAssets.map(({ source, destination }) => createDiskOutputFile(source, destination)); } -function createAssetMatchers(assets: NormalizedLibraryOptions['assets'], workspaceRoot: string) { +function createAssetMatchers(assets: NormalizedLibraryOptions['assets']) { return assets.map((asset) => { - const absInput = path.resolve(workspaceRoot, asset.input); - const posixInput = toPosixPath(absInput).replace(/\/+$/, ''); const isMatch = picomatch(asset.glob, { dot: true, ignore: [...DEFAULT_ASSET_IGNORE, ...(asset.ignore ?? [])], }); + const prefix = asset.input.endsWith('/') ? asset.input : `${asset.input}/`; - return { asset, posixInputPrefix: `${posixInput}/`, isMatch }; + return { asset, prefix, isMatch }; }); } diff --git a/packages/angular/build/src/builders/library/pipeline/build-action.ts b/packages/angular/build/src/builders/library/pipeline/build-action.ts index 6d40f5fe4383..887ede84a74a 100644 --- a/packages/angular/build/src/builders/library/pipeline/build-action.ts +++ b/packages/angular/build/src/builders/library/pipeline/build-action.ts @@ -11,7 +11,6 @@ import path from 'node:path'; import type { ComponentStylesheetBundler } from '../../../tools/esbuild/angular/component-stylesheets'; import { emitFilesToDisk } from '../../../utils/emit-files-to-disk'; import { assertIsError } from '../../../utils/error'; -import { toPosixPath } from '../../../utils/path'; import type { NormalizedLibraryOptions, SingleBuildState } from '../types'; import { collectAssetsToEmit } from './assets'; import { bundleEntryPoints } from './bundler'; @@ -49,7 +48,7 @@ export function createSingleBuildState(): SingleBuildState { * Context required to execute a single library build iteration. */ export interface BuildActionContext { - /** Normalized options for the library build. */ + /** Normalized options for the library build with POSIX paths. */ options: NormalizedLibraryOptions; /** The Architect builder context. */ @@ -62,7 +61,7 @@ export interface BuildActionContext { isWatchMode: boolean; /** - * Set of file paths tracked for compilation and configuration + * Set of POSIX file paths tracked for compilation and configuration * (including `tsconfig.json`, `package.json`, entry points, and referenced source, * template, and stylesheet files). Updated during compilation and used to determine * whether file changes require recompiling entry points, excluding asset files so @@ -73,7 +72,7 @@ export interface BuildActionContext { /** State preserved across incremental builds in watch mode. */ buildState: SingleBuildState; - /** Set of file paths modified since the last build iteration. */ + /** Set of POSIX file paths modified since the last build iteration. */ modifiedFiles?: Set; /** @@ -91,6 +90,7 @@ export interface BuildActionContext { * manifest generation, and asset copying. * * @param actionContext The build action state and configuration. + * @returns A promise resolving to the build action result containing errors, warnings, and logs. */ export async function buildAction(actionContext: BuildActionContext): Promise { const { @@ -106,10 +106,10 @@ export async function buildAction(actionContext: BuildActionContext): Promise 0 || pendingChangedDtsFiles.size > 0 || - hasModifiedWatchedFile(modifiedFiles, watchedCompilationFiles, posixPackageJsonPath); + hasModifiedWatchedFile(modifiedFiles, watchedCompilationFiles, packageJsonPath); try { if (!buildState.hasEmittedManifests) { @@ -172,13 +172,12 @@ export async function buildAction(actionContext: BuildActionContext): Promise> | undefined; let diagnostics: LibraryCompilationDiagnostics; if (hasStylesheetErrors) { diagnostics = await diagnosePromise; } else { + const itemsToBundle = collectEntryPointsToBundle(options, buildState, logs); try { [bundleOutput, diagnostics] = await Promise.all([ bundleEntryPoints(itemsToBundle, esmFiles, dtsFiles, { @@ -243,7 +242,7 @@ export async function buildAction(actionContext: BuildActionContext): Promise path.join(options.outputPath, file.path)); + await emitFilesToDisk(filesToEmit, (file) => path.posix.join(options.outputPath, file.path)); buildState.hasEmittedManifests = true; buildState.hasEmittedTslib = hasTslibImport; @@ -262,16 +261,16 @@ export async function buildAction(actionContext: BuildActionContext): Promise, watchedCompilationFiles: ReadonlySet, - posixPackageJsonPath: string, + packageJsonPath: string, ): boolean { for (const file of modifiedFiles) { - if (file !== posixPackageJsonPath && watchedCompilationFiles.has(file)) { + if (file !== packageJsonPath && watchedCompilationFiles.has(file)) { return true; } } diff --git a/packages/angular/build/src/builders/library/pipeline/bundler.ts b/packages/angular/build/src/builders/library/pipeline/bundler.ts index 5cd89a4b110d..c48942885083 100644 --- a/packages/angular/build/src/builders/library/pipeline/bundler.ts +++ b/packages/angular/build/src/builders/library/pipeline/bundler.ts @@ -60,7 +60,7 @@ interface MultiBundleOutput { /** In-memory files generated by the bundle invocation. */ filesToEmit: MemoryOutputFile[]; - /** Map of bundle entry names to the set of virtual module IDs included in the bundle. */ + /** Map of bundle entry names to the set of virtual POSIX module IDs included in the bundle. */ moduleIdsByBundle: Map>; /** Set of bundle entry names that import 'tslib'. */ @@ -75,9 +75,9 @@ interface MultiBundleOutput { * using at most 2 Rolldown instances total (1 for all .mjs bundles, 1 for all .d.ts bundles). * * @param items The collection of entry point inputs with change statuses. - * @param esmFiles Map of virtual ESM output file paths to their content. - * @param dtsFiles Map of virtual TypeScript declaration file paths to their content. - * @param options The normalized library builder options. + * @param esmFiles Map of virtual POSIX ESM output file paths to their content. + * @param dtsFiles Map of virtual POSIX TypeScript declaration file paths to their content. + * @param options The normalized library builder options with POSIX paths. * @returns A promise resolving to the bundled output files and bundle cache results. */ export async function bundleEntryPoints( @@ -135,7 +135,7 @@ export async function bundleEntryPoints( * * @param entryPoints The entry points to include in the bundle input map. * @param dtsMode Whether the input map is for declaration files (.d.ts) rather than ESM files (.js). - * @returns A record mapping bundle names to virtual entry file paths. + * @returns A record mapping bundle names to virtual POSIX entry file paths. */ function resolveEntryInputMap( entryPoints: readonly NormalizedEntryPoint[], @@ -143,20 +143,18 @@ function resolveEntryInputMap( ): Record { const input: Record = {}; for (const { bundleName, entryFilePath } of entryPoints) { - const posixPath = toPosixPath(entryFilePath); input[bundleName] = dtsMode - ? posixPath.replace(/\.([cm]?ts)$/, '.d.$1') - : posixPath.replace(/\.([cm]?)ts$/, '.$1js'); + ? entryFilePath.replace(/\.([cm]?ts)$/, '.d.$1') + : entryFilePath.replace(/\.([cm]?)ts$/, '.$1js'); } return input; } /** - * Creates a Rolldown plugin that loads modules from an in-memory file map - * and prevents illegal cross-entry-point sibling imports. + * Creates a Rolldown plugin that resolves and loads modules from an in-memory file map. * - * @param files Map of virtual file paths to their content. + * @param files Map of virtual POSIX file paths to their content. * @param extensions Candidate extensions to probe when resolving extensionless module specifiers. * @param includeMap Whether source map files should also be loaded from the memory map. * @returns A Rolldown plugin instance. @@ -183,12 +181,7 @@ function createMemoryFileLoaderPlugin( return { id, external: true }; } - const posixId = toPosixPath(id); - const importerPosix = toPosixPath(importer); - const resolved = - posixId[0] === '.' - ? path.posix.join(path.posix.dirname(importerPosix), posixId) - : posixId; + const resolved = id[0] === '.' ? path.posix.join(path.posix.dirname(importer), id) : id; let resolvedCandidate: string | undefined; if (files.has(resolved)) { @@ -230,7 +223,7 @@ function createMemoryFileLoaderPlugin( * the transitive set of module IDs belonging to each entry point bundle. * * @param output The array of output chunks and assets from Rolldown. - * @param dir The destination output directory prefix. + * @param dir The destination POSIX output directory prefix. * @returns The processed multi-bundle output. */ function processRolldownOutput( @@ -312,10 +305,10 @@ const IGNORED_ROLLDOWN_WARNING_CODES: ReadonlySet = new Set([ /** * Executes a Rolldown bundle build across all provided entry inputs. * - * @param input Map of bundle names to virtual entry file paths. + * @param input Map of bundle names to virtual POSIX entry file paths. * @param plugins Rolldown plugins to use during bundling. * @param extension Output file extension ('mjs' or 'd.ts'). - * @param options The normalized library options. + * @param options The normalized library options with POSIX paths. * @returns A promise resolving to the multi-bundle output. */ async function executeMultiBundle( @@ -367,7 +360,7 @@ async function executeMultiBundle( try { const { output } = await bundle.generate({ format: 'es', - dir: path.join(outputPath, dir), + dir: path.posix.join(outputPath, dir), entryFileNames: `[name].${extension}`, chunkFileNames: `[name]-[hash].${extension}`, sourcemap, @@ -388,8 +381,8 @@ async function executeMultiBundle( * Bundles JavaScript ESM output files for all provided entry points. * * @param entryPoints The entry points to bundle. - * @param esmFiles Map of virtual ESM output file paths to contents. - * @param options The normalized library options. + * @param esmFiles Map of virtual POSIX ESM output file paths to contents. + * @param options The normalized library options with POSIX paths. * @returns A promise resolving to the multi-bundle output. */ async function bundleAllEsm( @@ -418,8 +411,8 @@ async function bundleAllEsm( * Bundles TypeScript declaration (.d.ts) output files for all provided entry points. * * @param entryPoints The entry points to bundle. - * @param dtsFiles Map of virtual declaration output file paths to contents. - * @param options The normalized library options. + * @param dtsFiles Map of virtual POSIX declaration output file paths to contents. + * @param options The normalized library options with POSIX paths. * @returns A promise resolving to the multi-bundle output. */ async function bundleAllDts( diff --git a/packages/angular/build/src/builders/library/pipeline/compilation.ts b/packages/angular/build/src/builders/library/pipeline/compilation.ts index afc5c319854e..5a795b463bae 100644 --- a/packages/angular/build/src/builders/library/pipeline/compilation.ts +++ b/packages/angular/build/src/builders/library/pipeline/compilation.ts @@ -35,19 +35,19 @@ export interface LibraryCompilationDiagnostics { * Output of the unified library compilation step. */ export interface LibraryCompilationOutput { - /** Read-only map of emitted JavaScript files. */ + /** Read-only map of emitted JavaScript files keyed by POSIX path. */ readonly esmFiles: ReadonlyMap; - /** Read-only map of emitted TypeScript declaration files. */ + /** Read-only map of emitted TypeScript declaration files keyed by POSIX path. */ readonly dtsFiles: ReadonlyMap; - /** Set of JavaScript file paths that were modified or newly emitted in this iteration. */ + /** Set of POSIX JavaScript file paths that were modified or newly emitted in this iteration. */ readonly changedEsmFiles: ReadonlySet; - /** Set of declaration file paths that were modified or newly emitted in this iteration. */ + /** Set of POSIX declaration file paths that were modified or newly emitted in this iteration. */ readonly changedDtsFiles: ReadonlySet; - /** Set of all source and dependency file paths referenced during compilation. */ + /** Set of all POSIX source and dependency file paths referenced during compilation. */ readonly referencedFiles: ReadonlySet; /** Cached compilation state for subsequent incremental builds. */ @@ -70,10 +70,10 @@ export interface LibraryCompilationOutput { * Compiles all library entry points in a single TypeScript and Angular compilation pass. * * @param entryPoints The collection of normalized library entry points to compile. - * @param options The normalized library builder options. + * @param options The normalized library builder options with POSIX paths. * @param stylesheetBundler The component stylesheet bundler instance. * @param cached Optional cached compilation state from a previous build iteration. - * @param modifiedFiles Optional set of modified file paths for incremental compilation. + * @param modifiedFiles Optional set of modified POSIX file paths for incremental compilation. * @returns A promise resolving to the compilation output containing emitted files and diagnostics. */ export async function compileLibrary( @@ -179,13 +179,12 @@ export async function compileLibrary( if (effectiveModifiedFiles) { for (const modifiedFile of effectiveModifiedFiles) { - const posixModified = toPosixPath(modifiedFile); - if (allReferencedFiles.has(posixModified)) { + if (allReferencedFiles.has(modifiedFile)) { continue; } - const basePathWithoutExt = posixModified.replace(/(?:\.d\.[cm]?ts|\.[cm]?[jt]sx?)$/i, ''); - if (basePathWithoutExt === posixModified || existsSync(posixModified)) { + const basePathWithoutExt = modifiedFile.replace(/(?:\.d\.[cm]?ts|\.[cm]?[jt]sx?)$/i, ''); + if (basePathWithoutExt === modifiedFile || existsSync(modifiedFile)) { continue; } diff --git a/packages/angular/build/src/builders/library/pipeline/entry-points.ts b/packages/angular/build/src/builders/library/pipeline/entry-points.ts index 7797f069fade..5cff4ca82136 100644 --- a/packages/angular/build/src/builders/library/pipeline/entry-points.ts +++ b/packages/angular/build/src/builders/library/pipeline/entry-points.ts @@ -38,9 +38,9 @@ export function getEntryPointBundleName(packageName: string, entryPointName = '. * @param posixKey Normalized POSIX key without trailing slashes. * @param isPrimary Whether this is the primary entry point. * @param targetPath The relative file path string from exports. - * @param projectRoot The library project root directory. + * @param projectRoot The library project root POSIX directory path. * @param packageName The root package name (e.g. `@my/lib`). - * @returns The normalized entry point descriptor. + * @returns The normalized entry point descriptor with POSIX paths. */ function normalizeEntryPoint( key: string, @@ -65,7 +65,7 @@ function normalizeEntryPoint( const subpath = isPrimary ? '.' : `./${name}`; const displayName = (isPrimary ? packageName : `${packageName}/${name}`).toLowerCase(); const bundleName = getEntryPointBundleName(packageName, name); - const entryFilePath = path.resolve(projectRoot, targetPath); + const entryFilePath = toPosixPath(path.resolve(projectRoot, targetPath)); if (!/(?, @@ -234,6 +238,12 @@ export function haveEntryPointsChanged( /** * Updates watched files, entry points in options, and cached bundle results when package.json entry points change. + * + * @param packageJson The reloaded `package.json` data. + * @param options The normalized library builder options. + * @param buildState The state preserved across incremental builds. + * @param watchedCompilationFiles Set of POSIX file paths tracked for compilation. + * @param packageJsonPath Normalized POSIX path to `package.json`. */ export function updateWatchedEntryPoints( packageJson: PackageJsonData, @@ -251,7 +261,7 @@ export function updateWatchedEntryPoints( if (haveEntryPointsChanged(options.entryPoints, newEntryPoints)) { for (const entryPoint of newEntryPoints.values()) { - watchedCompilationFiles.add(toPosixPath(entryPoint.entryFilePath)); + watchedCompilationFiles.add(entryPoint.entryFilePath); } for (const name of buildState.previousBundleResults.keys()) { @@ -315,8 +325,8 @@ export function collectEntryPointsToBundle( /** * Checks whether any file in `changedFiles` belongs to the given entry point's `moduleIds`. * - * @param moduleIds Set of module IDs referenced by the entry point's previous bundle. - * @param changedFiles Set of file paths that changed during compilation. + * @param moduleIds Set of virtual POSIX module IDs referenced by the entry point's previous bundle. + * @param changedFiles Set of POSIX file paths that changed during compilation. * @returns `true` if at least one changed file is part of `moduleIds`; otherwise `false`. */ function hasEntryPointChanges( diff --git a/packages/angular/build/src/builders/library/pipeline/package-manifests.ts b/packages/angular/build/src/builders/library/pipeline/package-manifests.ts index 90e5d5bbd94a..98ed88a506e6 100644 --- a/packages/angular/build/src/builders/library/pipeline/package-manifests.ts +++ b/packages/angular/build/src/builders/library/pipeline/package-manifests.ts @@ -188,7 +188,7 @@ let cachedTslibRange: string | undefined; /** * Get the tslib range used by the Angular compiler. - * @param workspaceRoot path to the workspace root + * @param workspaceRoot path to the workspace root. * @returns tslib range */ async function getAngularTslibRange(workspaceRoot: string): Promise { diff --git a/packages/angular/build/src/builders/library/pipeline/stylesheet-bundler.ts b/packages/angular/build/src/builders/library/pipeline/stylesheet-bundler.ts index 74c44f3a1491..971741d7a2e2 100644 --- a/packages/angular/build/src/builders/library/pipeline/stylesheet-bundler.ts +++ b/packages/angular/build/src/builders/library/pipeline/stylesheet-bundler.ts @@ -10,6 +10,9 @@ import { ComponentStylesheetBundler } from '../../../tools/esbuild/angular/compo import type { BundleStylesheetOptions } from '../../../tools/esbuild/stylesheets/bundle-options'; import type { NormalizedLibraryOptions } from '../types'; +/** + * Subset of normalized library options required to configure the component stylesheet bundler. + */ export type LibraryStylesheetBundlerOptions = Pick< NormalizedLibraryOptions, | 'workspaceRoot' @@ -25,7 +28,7 @@ export type LibraryStylesheetBundlerOptions = Pick< /** * Creates a stylesheet bundler instance configured for library compilation. * - * @param options The normalized library builder options. + * @param options The normalized library builder options with POSIX paths. * @param incremental Whether incremental watch mode is enabled. * @param target The esbuild target environments derived from browserslist. * @returns A new ComponentStylesheetBundler instance. diff --git a/packages/angular/build/src/builders/library/pipeline/utils.ts b/packages/angular/build/src/builders/library/pipeline/utils.ts index 9092a73005ef..8ef8cf7fe9f3 100644 --- a/packages/angular/build/src/builders/library/pipeline/utils.ts +++ b/packages/angular/build/src/builders/library/pipeline/utils.ts @@ -7,6 +7,7 @@ */ import fs from 'node:fs/promises'; +import { toPosixPath } from '../../../utils/path'; import type { PackageJsonData } from '../types'; const IS_DTS_FILE_REGEXP = /\.d\.[cm]?ts$/i; @@ -28,7 +29,7 @@ export const TYPES_OUTPUT_DIR = 'types'; export interface MemoryOutputFile { origin: 'memory'; - /** The destination path where the file should be written. */ + /** The relative POSIX destination path where the file should be written. */ path: string; /** The contents of the file as either a string or byte array. */ @@ -41,10 +42,10 @@ export interface MemoryOutputFile { export interface DiskOutputFile { origin: 'disk'; - /** The path to the source file on disk. */ + /** The POSIX path to the source file on disk. */ source: string; - /** The destination path where the file should be copied. */ + /** The relative POSIX destination path where the file should be copied. */ path: string; } @@ -54,26 +55,26 @@ export interface DiskOutputFile { export type OutputFile = MemoryOutputFile | DiskOutputFile; /** - * Creates an output file descriptor for an existing file on disk. + * Creates an output file descriptor for an existing file on disk with POSIX paths. * * @param source The path to the source file on disk. - * @param path The destination path where the file should be copied. - * @returns A {@link DiskOutputFile} descriptor. + * @param path The relative destination path where the file should be copied. + * @returns A {@link DiskOutputFile} descriptor with POSIX `source` and `path`. */ export function createDiskOutputFile(source: string, path: string): DiskOutputFile { return { origin: 'disk', - source, - path, + source: toPosixPath(source), + path: toPosixPath(path), }; } /** - * Creates an output file descriptor for an in-memory file. + * Creates an output file descriptor for an in-memory file with a POSIX destination path. * - * @param path The destination path where the file should be written. + * @param path The relative destination path where the file should be written. * @param contents The contents of the file as either a string, byte array, or JSON object. - * @returns A {@link MemoryOutputFile} descriptor. + * @returns A {@link MemoryOutputFile} descriptor with a POSIX `path`. */ export function createMemoryOutputFile( path: string, @@ -81,7 +82,7 @@ export function createMemoryOutputFile( ): MemoryOutputFile { return { origin: 'memory', - path, + path: toPosixPath(path), contents: typeof contents === 'string' || contents instanceof Uint8Array ? contents diff --git a/packages/angular/build/src/builders/library/types.ts b/packages/angular/build/src/builders/library/types.ts index d360cfe2c868..2deeddf589e5 100644 --- a/packages/angular/build/src/builders/library/types.ts +++ b/packages/angular/build/src/builders/library/types.ts @@ -28,7 +28,7 @@ export interface NormalizedEntryPoint { /** Base name of the output bundle (e.g. 'my-lib' or 'my-lib-testing'). */ bundleName: string; - /** Absolute path to entry file. */ + /** Absolute POSIX path to the entry file. */ entryFilePath: string; /** Is this the primary entry point ('.')? */ @@ -59,21 +59,40 @@ export interface PackageJsonData { /** * Normalized options for the library builder. + * All file and directory paths are normalized to POSIX format. */ export interface NormalizedLibraryOptions { + /** Absolute POSIX path to the workspace root directory. */ workspaceRoot: string; + + /** Absolute POSIX path to the library project root directory. */ projectRoot: string; + packageName: string; packageJson: PackageJsonData; + + /** Absolute POSIX path to the build output directory. */ outputPath: string; + deleteOutputPath: boolean; + + /** Absolute POSIX path to the library's `package.json`. */ packageJsonPath: string; + + /** Absolute POSIX path to the TypeScript configuration file. */ tsConfigPath: string; + entryPoints: Map; inlineStyleLanguage: 'css' | 'less' | 'sass' | 'scss'; + + /** Absolute POSIX paths to style preprocessor include directories. */ styleIncludePaths: string[]; + sass?: StylesheetPluginsass; + + /** Normalized asset patterns with POSIX `input` and `output` paths. */ assets: ReturnType; + compilationMode?: 'partial' | 'full'; declarationMap?: boolean; allowedNonPeerDependencies: RegExp[]; @@ -93,10 +112,10 @@ export interface NormalizedLibraryOptions { * Cached module ID sets for a bundled entry point. */ export interface BundleResult { - /** Exact set of virtual ESM module IDs bundled into this entry point. */ + /** Exact set of virtual POSIX ESM module IDs bundled into this entry point. */ esmModuleIds: ReadonlySet; - /** Exact set of virtual DTS module IDs bundled into this entry point. */ + /** Exact set of virtual POSIX DTS module IDs bundled into this entry point. */ dtsModuleIds: ReadonlySet; /** Whether this entry point bundle imports 'tslib'. */ @@ -110,13 +129,13 @@ export interface SingleProgramCache { /** The active Angular compilation instance. */ readonly compilationInstance: AngularCompilation; - /** In-memory map of emitted JavaScript files keyed by relative output path. */ + /** In-memory map of emitted JavaScript files keyed by POSIX path. */ readonly esmFiles: Map; - /** In-memory map of emitted TypeScript declaration files keyed by relative output path. */ + /** In-memory map of emitted TypeScript declaration files keyed by POSIX path. */ readonly dtsFiles: Map; - /** Set of file paths that failed during stylesheet bundling or compilation. */ + /** Set of POSIX file paths that failed during stylesheet bundling or compilation. */ readonly failedFiles?: ReadonlySet; } @@ -133,10 +152,10 @@ export interface SingleBuildState { /** Map of entry point names to their previous bundle results. */ previousBundleResults: Map; - /** Set of changed ESM file paths pending bundling. */ + /** Set of changed POSIX ESM file paths pending bundling. */ pendingChangedEsmFiles: Set; - /** Set of changed TypeScript declaration file paths pending bundling. */ + /** Set of changed POSIX TypeScript declaration file paths pending bundling. */ pendingChangedDtsFiles: Set; /** Whether the previous build iteration encountered a compilation or diagnostic error. */ diff --git a/packages/angular/build/src/utils/delete-output-dir.ts b/packages/angular/build/src/utils/delete-output-dir.ts index 45084760793d..1f676e77b759 100644 --- a/packages/angular/build/src/utils/delete-output-dir.ts +++ b/packages/angular/build/src/utils/delete-output-dir.ts @@ -17,8 +17,9 @@ export async function deleteOutputDir( outputPath: string, emptyOnlyDirectories?: string[], ): Promise { - const resolvedOutputPath = resolve(root, outputPath); - if (resolvedOutputPath === root) { + const resolvedRoot = resolve(root); + const resolvedOutputPath = resolve(resolvedRoot, outputPath); + if (resolvedOutputPath === resolvedRoot) { throw new Error('Output path MUST not be project root directory!'); } diff --git a/packages/angular/build/src/utils/normalize-asset-patterns.ts b/packages/angular/build/src/utils/normalize-asset-patterns.ts index d97b6e605fa4..66365e10df22 100644 --- a/packages/angular/build/src/utils/normalize-asset-patterns.ts +++ b/packages/angular/build/src/utils/normalize-asset-patterns.ts @@ -24,14 +24,15 @@ export function normalizeAssetPatterns( // When sourceRoot is not available, we default to ${projectRoot}/src. const sourceRoot = projectSourceRoot || path.join(projectRoot, 'src'); - const resolvedSourceRoot = path.resolve(workspaceRoot, sourceRoot); - const resolvedProjectRoot = path.resolve(workspaceRoot, projectRoot); + const resolvedWorkspaceRoot = path.resolve(workspaceRoot); + const resolvedSourceRoot = path.resolve(resolvedWorkspaceRoot, sourceRoot); + const resolvedProjectRoot = path.resolve(resolvedWorkspaceRoot, projectRoot); return assetPatterns.map((assetPattern) => { // Normalize string asset patterns to objects. if (typeof assetPattern === 'string') { const assetPath = path.normalize(assetPattern); - const resolvedAssetPath = path.resolve(workspaceRoot, assetPath); + const resolvedAssetPath = path.resolve(resolvedWorkspaceRoot, assetPath); let root: string; // Check if the string asset is within sourceRoot. @@ -39,8 +40,8 @@ export function normalizeAssetPatterns( root = resolvedSourceRoot; } else if (resolvedAssetPath.startsWith(resolvedProjectRoot)) { root = resolvedProjectRoot; - } else if (resolvedAssetPath.startsWith(workspaceRoot)) { - root = workspaceRoot; + } else if (resolvedAssetPath.startsWith(resolvedWorkspaceRoot)) { + root = resolvedWorkspaceRoot; } else { throw new Error(`The ${assetPattern} asset path must be within the workspace root.`); } @@ -67,11 +68,11 @@ export function normalizeAssetPatterns( } // Output directory for both is the relative path from the root to input. - const output = path.relative(root, path.resolve(workspaceRoot, input)); + const output = path.relative(root, path.resolve(resolvedWorkspaceRoot, input)); assetPattern = { glob, input, output }; } else { - if (!isSubDirectory(workspaceRoot, assetPattern.input)) { + if (!isSubDirectory(resolvedWorkspaceRoot, assetPattern.input)) { throw new Error(`The ${assetPattern.input} asset path must be within the workspace root.`); }