diff --git a/goldens/public-api/angular/build/index.api.md b/goldens/public-api/angular/build/index.api.md index d09f6bf40a6a..9e35cbe6b158 100644 --- a/goldens/public-api/angular/build/index.api.md +++ b/goldens/public-api/angular/build/index.api.md @@ -149,6 +149,9 @@ export function executeDevServerBuilder(options: DevServerBuilderOptions, contex // @public export function executeExtractI18nBuilder(options: ExtractI18nBuilderOptions, context: BuilderContext, extensions?: ApplicationBuilderExtensions): Promise; +// @public +export function executeNgPackagrBuilder(options: NgPackagrBuilderOptions, context: BuilderContext): AsyncIterableIterator; + // @public export interface ExtractI18nBuilderOptions { buildTarget?: string; @@ -158,6 +161,14 @@ export interface ExtractI18nBuilderOptions { progress?: boolean; } +// @public +export interface NgPackagrBuilderOptions { + poll?: number; + project: string; + tsConfig?: string; + watch?: boolean; +} + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/angular/build/BUILD.bazel b/packages/angular/build/BUILD.bazel index 91868fb61cfd..ba11626610e7 100644 --- a/packages/angular/build/BUILD.bazel +++ b/packages/angular/build/BUILD.bazel @@ -23,6 +23,11 @@ ts_json_schema( src = "src/builders/extract-i18n/schema.json", ) +ts_json_schema( + name = "ng_packagr_schema", + src = "src/builders/ng-packagr/schema.json", +) + ts_project( name = "build", srcs = glob( @@ -40,6 +45,7 @@ ts_project( "//packages/angular/build:src/builders/application/schema.ts", "//packages/angular/build:src/builders/dev-server/schema.ts", "//packages/angular/build:src/builders/extract-i18n/schema.ts", + "//packages/angular/build:src/builders/ng-packagr/schema.ts", ], data = glob( include = [ @@ -90,6 +96,7 @@ ts_project( "//:root_modules/lmdb", "//:root_modules/magic-string", "//:root_modules/mrmime", + "//:root_modules/ng-packagr", "//:root_modules/parse5-html-rewriting-stream", "//:root_modules/picomatch", "//:root_modules/piscina", @@ -186,6 +193,7 @@ ts_project( "//:root_modules/@angular/platform-browser", "//:root_modules/@angular/platform-browser-dynamic", "//:root_modules/@angular/router", + "//:root_modules/ng-packagr", "//:root_modules/rxjs", "//:root_modules/tslib", "//:root_modules/typescript", diff --git a/packages/angular/build/builders.json b/packages/angular/build/builders.json index b0174fc3fee9..ef98f535c16c 100644 --- a/packages/angular/build/builders.json +++ b/packages/angular/build/builders.json @@ -14,6 +14,11 @@ "implementation": "./src/builders/extract-i18n/index", "schema": "./src/builders/extract-i18n/schema.json", "description": "Extract i18n messages from an application." + }, + "ng-packagr": { + "implementation": "./src/builders/ng-packagr/index", + "schema": "./src/builders/ng-packagr/schema.json", + "description": "Build a library with ng-packagr." } } } diff --git a/packages/angular/build/package.json b/packages/angular/build/package.json index 8b0b6a950849..9f0b998a476f 100644 --- a/packages/angular/build/package.json +++ b/packages/angular/build/package.json @@ -55,6 +55,7 @@ "@angular/service-worker": "^19.0.0 || ^19.1.0-next.0", "@angular/ssr": "^0.0.0-PLACEHOLDER", "less": "^4.2.0", + "ng-packagr": "^19.0.0 || ^19.1.0-next.0", "postcss": "^8.4.0", "tailwindcss": "^2.0.0 || ^3.0.0", "typescript": ">=5.5 <5.8" @@ -75,6 +76,9 @@ "less": { "optional": true }, + "ng-packagr": { + "optional": true + }, "postcss": { "optional": true }, diff --git a/packages/angular/build/src/builders/ng-packagr/builder.ts b/packages/angular/build/src/builders/ng-packagr/builder.ts new file mode 100644 index 000000000000..f2dc60ebf30c --- /dev/null +++ b/packages/angular/build/src/builders/ng-packagr/builder.ts @@ -0,0 +1,86 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect'; +import type { NgPackagrOptions } from 'ng-packagr'; +import { join, resolve } from 'node:path'; +import { assertIsError } from '../../utils/error'; +import { normalizeCacheOptions } from '../../utils/normalize-cache'; +import { purgeStaleBuildCache } from '../../utils/purge-cache'; +import type { Schema as NgPackagrBuilderOptions } from './schema'; + +/** + * A Builder that executes the `ng-packagr` tool to build an Angular library. + * + * @param options The builder options as defined by the JSON schema. + * @param context A BuilderContext instance. + * @returns A BuilderOutput object. + * + * @experimental Direct usage of this function is considered experimental. + */ +export async function* execute( + options: NgPackagrBuilderOptions, + context: BuilderContext, +): AsyncIterableIterator { + // Purge old build disk cache. + await purgeStaleBuildCache(context); + + const root = context.workspaceRoot; + let packager; + try { + packager = (await import('ng-packagr')).ngPackagr(); + } catch (error) { + assertIsError(error); + if (error.code === 'MODULE_NOT_FOUND') { + return { + success: false, + error: + 'The "ng-packagr" package was not found. To correct this error, ensure this package is installed in the project.', + }; + } + + throw error; + } + + packager.forProject(resolve(root, options.project)); + + if (options.tsConfig) { + packager.withTsConfig(resolve(root, options.tsConfig)); + } + + const projectName = context.target?.project; + if (!projectName) { + throw new Error('The builder requires a target.'); + } + + const metadata = await context.getProjectMetadata(projectName); + const { enabled: cacheEnabled, path: cacheDirectory } = normalizeCacheOptions( + metadata, + context.workspaceRoot, + ); + + const ngPackagrOptions: NgPackagrOptions = { + cacheEnabled, + poll: options.poll, + cacheDirectory: join(cacheDirectory, 'ng-packagr'), + }; + + try { + if (options.watch) { + await packager.watch(ngPackagrOptions).toPromise(); + } else { + await packager.build(ngPackagrOptions); + } + + yield { success: true }; + } catch (error) { + assertIsError(error); + + yield { success: false, error: error.message }; + } +} diff --git a/packages/angular/build/src/builders/ng-packagr/index.ts b/packages/angular/build/src/builders/ng-packagr/index.ts new file mode 100644 index 000000000000..df32d691aa43 --- /dev/null +++ b/packages/angular/build/src/builders/ng-packagr/index.ts @@ -0,0 +1,14 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { createBuilder } from '@angular-devkit/architect'; +import { execute } from './builder'; +import type { Schema as NgPackagrBuilderOptions } from './schema'; + +export { type NgPackagrBuilderOptions, execute }; +export default createBuilder(execute); diff --git a/packages/angular/build/src/builders/ng-packagr/schema.json b/packages/angular/build/src/builders/ng-packagr/schema.json new file mode 100644 index 000000000000..da76255f092a --- /dev/null +++ b/packages/angular/build/src/builders/ng-packagr/schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "ng-packagr Target", + "description": "ng-packagr target options for Build Architect. Use to build library projects.", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The file path for the ng-packagr configuration file, relative to the current workspace." + }, + "tsConfig": { + "type": "string", + "description": "The full path for the TypeScript configuration file, relative to the current workspace." + }, + "watch": { + "type": "boolean", + "description": "Run build when files change.", + "default": false + }, + "poll": { + "type": "number", + "description": "Enable and define the file watching poll time period in milliseconds." + } + }, + "additionalProperties": false, + "required": ["project"] +} diff --git a/packages/angular/build/src/index.ts b/packages/angular/build/src/index.ts index 64c5fc2f3b32..a27e1f5ae9d2 100644 --- a/packages/angular/build/src/index.ts +++ b/packages/angular/build/src/index.ts @@ -21,3 +21,8 @@ export { execute as executeExtractI18nBuilder, type ExtractI18nBuilderOptions, } from './builders/extract-i18n'; + +export { + execute as executeNgPackagrBuilder, + type NgPackagrBuilderOptions, +} from './builders/ng-packagr'; diff --git a/packages/angular/cli/BUILD.bazel b/packages/angular/cli/BUILD.bazel index 15cf4e64008c..abee3d62b711 100644 --- a/packages/angular/cli/BUILD.bazel +++ b/packages/angular/cli/BUILD.bazel @@ -78,6 +78,7 @@ CLI_SCHEMA_DATA = [ "//packages/angular/build:src/builders/application/schema.json", "//packages/angular/build:src/builders/dev-server/schema.json", "//packages/angular/build:src/builders/extract-i18n/schema.json", + "//packages/angular/build:src/builders/ng-packagr/schema.json", "//packages/angular_devkit/build_angular:src/builders/app-shell/schema.json", "//packages/angular_devkit/build_angular:src/builders/browser/schema.json", "//packages/angular_devkit/build_angular:src/builders/browser-esbuild/schema.json", diff --git a/packages/angular/cli/lib/config/workspace-schema.json b/packages/angular/cli/lib/config/workspace-schema.json index 402ad662cf09..edffabf285ef 100644 --- a/packages/angular/cli/lib/config/workspace-schema.json +++ b/packages/angular/cli/lib/config/workspace-schema.json @@ -407,6 +407,7 @@ "@angular/build:application", "@angular/build:dev-server", "@angular/build:extract-i18n", + "@angular/build:ng-packagr", "@angular-devkit/build-angular:application", "@angular-devkit/build-angular:app-shell", "@angular-devkit/build-angular:browser", @@ -792,6 +793,28 @@ } } } + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "builder": { + "const": "@angular/build:ng-packagr" + }, + "defaultConfiguration": { + "type": "string", + "description": "A default named configuration to use when a target configuration is not provided." + }, + "options": { + "$ref": "../../../../angular/build/src/builders/ng-packagr/schema.json" + }, + "configurations": { + "type": "object", + "additionalProperties": { + "$ref": "../../../../angular/build/src/builders/ng-packagr/schema.json" + } + } + } } ] }