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" + } + } + } } ] } diff --git a/packages/schematics/angular/library/index.ts b/packages/schematics/angular/library/index.ts index ab471db6367a..715a84361bf9 100644 --- a/packages/schematics/angular/library/index.ts +++ b/packages/schematics/angular/library/index.ts @@ -53,8 +53,8 @@ function addDependenciesToPackageJson() { }, { type: NodeDependencyType.Dev, - name: '@angular-devkit/build-angular', - version: latestVersions.DevkitBuildAngular, + name: '@angular/build', + version: latestVersions.AngularBuild, }, { type: NodeDependencyType.Dev, @@ -91,7 +91,7 @@ function addLibToWorkspaceFile( prefix: options.prefix, targets: { build: { - builder: Builders.NgPackagr, + builder: Builders.BuildNgPackagr, defaultConfiguration: 'production', options: { project: `${projectRoot}/ng-package.json`, diff --git a/packages/schematics/angular/library/index_spec.ts b/packages/schematics/angular/library/index_spec.ts index d08b44d605d5..800839ef2b90 100644 --- a/packages/schematics/angular/library/index_spec.ts +++ b/packages/schematics/angular/library/index_spec.ts @@ -388,9 +388,7 @@ describe('Library Schematic', () => { const tree = await schematicRunner.runSchematic('library', defaultOptions, workspaceTree); const workspace = JSON.parse(tree.readContent('/angular.json')); - expect(workspace.projects.foo.architect.build.builder).toBe( - '@angular-devkit/build-angular:ng-packagr', - ); + expect(workspace.projects.foo.architect.build.builder).toBe('@angular/build:ng-packagr'); }); describe('standalone=false', () => { diff --git a/packages/schematics/angular/migrations/use-application-builder/migration.ts b/packages/schematics/angular/migrations/use-application-builder/migration.ts index aed15549fbbe..40f2740e9463 100644 --- a/packages/schematics/angular/migrations/use-application-builder/migration.ts +++ b/packages/schematics/angular/migrations/use-application-builder/migration.ts @@ -215,6 +215,7 @@ function updateProjects(tree: Tree, context: SchematicContext) { case Builders.Application: case Builders.DevServer: case Builders.ExtractI18n: + case Builders.NgPackagr: // Ignore application, dev server, and i18n extraction for devkit usage check. // Both will be replaced if no other usage is found. continue; @@ -239,6 +240,9 @@ function updateProjects(tree: Tree, context: SchematicContext) { case Builders.ExtractI18n: target.builder = '@angular/build:extract-i18n'; break; + case Builders.NgPackagr: + target.builder = '@angular/build:ng-packagr'; + break; } } diff --git a/packages/schematics/angular/utility/latest-versions.ts b/packages/schematics/angular/utility/latest-versions.ts index a95ebc11e9c7..38a7cb2cbb13 100644 --- a/packages/schematics/angular/utility/latest-versions.ts +++ b/packages/schematics/angular/utility/latest-versions.ts @@ -13,6 +13,7 @@ const dependencies = require('./latest-versions/package.json')['dependencies']; export const latestVersions: Record & { Angular: string; DevkitBuildAngular: string; + AngularBuild: string; AngularSSR: string; } = { ...dependencies, @@ -21,5 +22,6 @@ export const latestVersions: Record & { Angular: dependencies['@angular/core'], DevkitBuildAngular: '^0.0.0-PLACEHOLDER', + AngularBuild: '^0.0.0-PLACEHOLDER', AngularSSR: '^0.0.0-PLACEHOLDER', }; diff --git a/packages/schematics/angular/utility/workspace-models.ts b/packages/schematics/angular/utility/workspace-models.ts index 41c41345f736..fd7eaf4d60f4 100644 --- a/packages/schematics/angular/utility/workspace-models.ts +++ b/packages/schematics/angular/utility/workspace-models.ts @@ -28,6 +28,7 @@ export enum Builders { Karma = '@angular-devkit/build-angular:karma', TsLint = '@angular-devkit/build-angular:tslint', NgPackagr = '@angular-devkit/build-angular:ng-packagr', + BuildNgPackagr = '@angular/build:ng-packagr', DevServer = '@angular-devkit/build-angular:dev-server', ExtractI18n = '@angular-devkit/build-angular:extract-i18n', Protractor = '@angular-devkit/build-angular:private-protractor',