Node.js-specific implementations and utilities for integrating with the file system and Node.js module resolution. This module provides concrete implementations of the ArchitectHost interface that work with real Angular workspaces and Node.js environments.
Production host implementation that integrates with real Angular workspaces and Node.js module resolution.
/**
* Node.js-specific builder information extending base BuilderInfo
*/
interface NodeModulesBuilderInfo extends BuilderInfo {
/**
* Import path for loading the builder module
*/
import: string;
}
/**
* Production host implementation for Node.js environments
*/
class NodeModulesArchitectHost implements ArchitectHost<NodeModulesBuilderInfo> {
/**
* Create Node.js architect host
* @param workspaceRoot - Absolute path to workspace root directory
* @param currentDirectory - Current working directory path
*/
constructor(workspaceRoot: string, currentDirectory?: string);
/**
* Get builder name for a target from workspace configuration
* @param target - Target to resolve builder name for
* @returns Promise resolving to builder name or null if not found
*/
getBuilderNameForTarget(target: Target): Promise<string | null>;
/**
* Resolve builder information from Node.js modules
* @param builderName - Builder name in format "package:builder"
* @returns Promise resolving to builder info with import path
*/
resolveBuilder(builderName: string): Promise<NodeModulesBuilderInfo | null>;
/**
* Load builder from resolved builder info
* @param info - Builder info with import path
* @returns Promise resolving to loaded builder instance
*/
loadBuilder(info: NodeModulesBuilderInfo): Promise<Builder | null>;
/**
* Get current working directory
* @returns Promise resolving to current directory path
*/
getCurrentDirectory(): Promise<string>;
/**
* Get workspace root directory
* @returns Promise resolving to workspace root path
*/
getWorkspaceRoot(): Promise<string>;
/**
* Get options for target from workspace configuration
* @param target - Target to get options for
* @returns Promise resolving to options object or null
*/
getOptionsForTarget(target: Target): Promise<json.JsonObject | null>;
/**
* Get project metadata from workspace
* @param projectName - Name of project
* @returns Promise resolving to project metadata or null
*/
getProjectMetadata(projectName: string): Promise<json.JsonObject | null>;
/**
* Get project metadata from target
* @param target - Target containing project information
* @returns Promise resolving to project metadata or null
*/
getProjectMetadata(target: Target): Promise<json.JsonObject | null>;
}Usage Examples:
import { Architect } from "@angular-devkit/architect";
import { NodeModulesArchitectHost } from "@angular-devkit/architect/node";
import { workspaces, logging } from "@angular-devkit/core";
// Create host for current working directory
const host = new NodeModulesArchitectHost(process.cwd());
const architect = new Architect(host);
// Schedule target from angular.json workspace configuration
const run = await architect.scheduleTarget({
project: "my-app",
target: "build",
configuration: "production"
});
const result = await run.result;
console.log(`Build ${result.success ? 'succeeded' : 'failed'}`);
// Schedule builder directly by name
const lintRun = await architect.scheduleBuilder(
"@angular-eslint/builder:lint",
{ lintFilePatterns: ["src/**/*.ts"] }
);
const lintResult = await lintRun.result;
if (!lintResult.success) {
console.error("Linting failed");
}Interface for workspace-specific operations used by NodeModulesArchitectHost.
/**
* Interface for workspace operations and configuration access
*/
interface WorkspaceHost {
/**
* Get builder name for a project target
* @param project - Project name
* @param target - Target name
* @returns Promise resolving to builder name
*/
getBuilderName(project: string, target: string): Promise<string>;
/**
* Get project metadata
* @param project - Project name
* @returns Promise resolving to project metadata
*/
getMetadata(project: string): Promise<json.JsonObject>;
/**
* Get options for a target
* @param project - Project name
* @param target - Target name
* @param configuration - Optional configuration name
* @returns Promise resolving to target options
*/
getOptions(project: string, target: string, configuration?: string): Promise<json.JsonObject>;
/**
* Check if target exists
* @param project - Project name
* @param target - Target name
* @returns Promise resolving to boolean indicating existence
*/
hasTarget(project: string, target: string): Promise<boolean>;
/**
* Get default configuration name for target
* @param project - Project name
* @param target - Target name
* @returns Promise resolving to default configuration name or undefined
*/
getDefaultConfigurationName(project: string, target: string): Promise<string | undefined>;
}Node.js-specific job registry for loading builders from the file system.
/**
* Node.js job registry that loads builders from disk
*/
namespace jobs {
/**
* Node.js-specific job registry implementation
*/
class NodeJobRegistry implements Registry {
/**
* Create registry for Node.js environment
* @param workspaceRoot - Workspace root directory
*/
constructor(workspaceRoot?: string);
/**
* Get job handler by name, loading from Node.js modules
* @param name - Job name to load
* @returns Observable of job handler or null
*/
get<A extends JsonValue, I extends JsonValue, O extends JsonValue>(
name: JobName
): Observable<JobHandler<A, I, O> | null>;
}
}Usage Examples:
import { jobs } from "@angular-devkit/architect/node";
import { SimpleScheduler } from "@angular-devkit/architect";
// Create Node.js job registry
const registry = new jobs.NodeJobRegistry("/path/to/workspace");
// Create scheduler with Node.js registry
const scheduler = new SimpleScheduler(registry);
// Schedule job that will be loaded from Node.js modules
const job = scheduler.schedule("@angular-devkit/build-angular:browser", {
outputPath: "dist/my-app"
});
job.output.subscribe(output => {
console.log("Build output:", output);
});Integration with Angular workspace configuration files.
// Working with angular.json workspace configuration
import { NodeModulesArchitectHost } from "@angular-devkit/architect/node";
import { workspaces } from "@angular-devkit/core";
async function buildFromWorkspace(workspacePath: string) {
// Load workspace configuration
const workspaceHost = workspaces.createWorkspaceHost(new NodeJsSyncHost());
const { workspace } = await workspaces.readWorkspace(workspacePath, workspaceHost);
// Create architect host
const architectHost = new NodeModulesArchitectHost(workspacePath);
const architect = new Architect(architectHost);
// Build all applications in workspace
for (const [projectName, project] of workspace.projects) {
if (project.extensions.projectType === 'application') {
const buildTarget = project.targets.get('build');
if (buildTarget) {
console.log(`Building ${projectName}...`);
const run = await architect.scheduleTarget({
project: projectName,
target: 'build'
});
const result = await run.result;
console.log(`${projectName}: ${result.success ? 'SUCCESS' : 'FAILED'}`);
}
}
}
}
// Example: Build all projects
buildFromWorkspace('./angular.json')
.then(() => console.log('All builds completed'))
.catch(error => console.error('Build failed:', error));How Node.js module resolution works for builders.
// Builder resolution follows these patterns:
// 1. Package builders (most common)
// "package-name:builder-name" -> loads from node_modules/package-name
const browserBuilder = "@angular-devkit/build-angular:browser";
const devServerBuilder = "@angular-devkit/build-angular:dev-server";
// 2. Local builders
// "./path/to/builder" -> loads from relative path
const customBuilder = "./tools/builders/custom-builder";
// 3. Absolute paths
// "/absolute/path/to/builder" -> loads from absolute path
// Example of loading different builder types
async function demonstrateBuilderLoading() {
const host = new NodeModulesArchitectHost(process.cwd());
// Load package builder
const packageBuilderInfo = await host.resolveBuilder("@angular-devkit/build-angular:browser");
console.log("Package builder import:", packageBuilderInfo?.import);
// Load local builder
const localBuilderInfo = await host.resolveBuilder("./tools/my-builder");
console.log("Local builder import:", localBuilderInfo?.import);
// Load and execute builder
if (packageBuilderInfo) {
const builder = await host.loadBuilder(packageBuilderInfo);
if (builder) {
console.log("Builder loaded successfully");
}
}
}Common error scenarios and handling patterns.
import { NodeModulesArchitectHost } from "@angular-devkit/architect/node";
async function robustBuilderExecution() {
const host = new NodeModulesArchitectHost(process.cwd());
const architect = new Architect(host);
try {
// Attempt to schedule target
const run = await architect.scheduleTarget({
project: "my-app",
target: "build"
});
// Handle progress and results
run.progress.subscribe(
progress => console.log(`Progress: ${progress.state}`),
error => console.error("Progress error:", error)
);
const result = await run.result;
if (result.success) {
console.log("Build completed successfully");
} else {
console.error("Build failed:", result.error);
}
} catch (error) {
if (error.message.includes("Project") && error.message.includes("does not exist")) {
console.error("Project not found in workspace");
} else if (error.message.includes("Target") && error.message.includes("does not exist")) {
console.error("Target not configured for project");
} else if (error.message.includes("Cannot resolve builder")) {
console.error("Builder package not installed or not found");
} else {
console.error("Unexpected error:", error.message);
}
}
}
// Handle workspace file issues
async function validateWorkspace(workspacePath: string) {
try {
const host = new NodeModulesArchitectHost(workspacePath);
const architect = new Architect(host);
// Test basic functionality
const hasTest = await architect.has("non-existent-job");
console.log("Workspace validation passed");
} catch (error) {
if (error.code === 'ENOENT') {
console.error("Workspace directory does not exist");
} else if (error.message.includes("angular.json")) {
console.error("Invalid or missing angular.json file");
} else {
console.error("Workspace validation failed:", error.message);
}
}
}Integration patterns for command-line tools.
#!/usr/bin/env node
import { Architect } from "@angular-devkit/architect";
import { NodeModulesArchitectHost } from "@angular-devkit/architect/node";
import { logging } from "@angular-devkit/core";
async function main() {
const args = process.argv.slice(2);
const [project, target, configuration] = args;
if (!project || !target) {
console.error("Usage: build-tool <project> <target> [configuration]");
process.exit(1);
}
// Setup logging
const logger = new logging.Logger("build-tool");
logger.subscribe(entry => {
const message = `${entry.level}: ${entry.message}`;
console.log(message);
});
// Create architect
const host = new NodeModulesArchitectHost(process.cwd());
const architect = new Architect(host);
try {
// Schedule target
const run = await architect.scheduleTarget(
{ project, target, configuration },
{},
{ logger }
);
// Show progress
run.progress.subscribe(progress => {
if (progress.state === BuilderProgressState.Running) {
console.log(`[${progress.builder.builderName}] ${progress.status || 'Running...'}`);
}
});
// Wait for completion
const result = await run.result;
if (result.success) {
console.log("✓ Build completed successfully");
process.exit(0);
} else {
console.error("✗ Build failed:", result.error);
process.exit(1);
}
} catch (error) {
console.error("Error:", error.message);
process.exit(1);
}
}
// Handle process signals
process.on('SIGINT', () => {
console.log("Build interrupted");
process.exit(130);
});
process.on('SIGTERM', () => {
console.log("Build terminated");
process.exit(143);
});
main().catch(error => {
console.error("Unexpected error:", error);
process.exit(1);
});