Real-time progress reporting system with state management for build operations. The progress reporting system enables builders to communicate their current status, completion progress, and operational state to users and tooling interfaces.
Enumeration of possible progress states during builder execution.
/**
* Possible states for builder progress reporting
*/
enum BuilderProgressState {
/**
* Builder has stopped execution
*/
Stopped = "stopped",
/**
* Builder encountered an error
*/
Error = "error",
/**
* Builder is waiting to start or for dependencies
*/
Waiting = "waiting",
/**
* Builder is actively running
*/
Running = "running"
}Type-safe progress reporting with state-specific properties.
/**
* Type-safe progress reporting interface
*/
type TypedBuilderProgress =
| {
state: BuilderProgressState.Stopped
}
| {
state: BuilderProgressState.Error;
error: json.JsonValue
}
| {
state: BuilderProgressState.Waiting;
status?: string
}
| {
state: BuilderProgressState.Running;
status?: string;
current: number;
total?: number
};
/**
* Complete progress interface combining typed and raw progress
*/
type BuilderProgress = json.JsonObject & RealBuilderProgress & TypedBuilderProgress;Usage Examples:
import { BuilderProgressState } from "@angular-devkit/architect";
// Progress state examples
const stoppedProgress: TypedBuilderProgress = {
state: BuilderProgressState.Stopped
};
const errorProgress: TypedBuilderProgress = {
state: BuilderProgressState.Error,
error: "Compilation failed"
};
const waitingProgress: TypedBuilderProgress = {
state: BuilderProgressState.Waiting,
status: "Waiting for dependencies"
};
const runningProgress: TypedBuilderProgress = {
state: BuilderProgressState.Running,
status: "Compiling TypeScript",
current: 75,
total: 100
};Complete progress report including target and builder information.
/**
* Complete progress report with metadata
*/
interface BuilderProgressReport extends BuilderProgress {
/**
* Target information if available
*/
target?: Target;
/**
* Builder information and metadata
*/
builder: BuilderInfo;
/**
* Unique identifier for the build context
*/
id: number;
}Usage Examples:
// Monitor progress reports
const run = await architect.scheduleTarget({ project: "app", target: "build" });
run.progress.subscribe((progress: BuilderProgressReport) => {
console.log(`Builder: ${progress.builder.builderName}`);
console.log(`State: ${progress.state}`);
if (progress.target) {
console.log(`Target: ${progress.target.project}:${progress.target.target}`);
}
if (progress.state === BuilderProgressState.Running) {
console.log(`Progress: ${progress.current}/${progress.total || '?'}`);
if (progress.status) {
console.log(`Status: ${progress.status}`);
}
}
if (progress.state === BuilderProgressState.Error) {
console.error(`Error: ${progress.error}`);
}
});Methods available on BuilderContext for reporting progress.
interface BuilderContext {
/**
* Report that the builder is now running
*/
reportRunning(): void;
/**
* Update the status message displayed to users
* @param status - Status message to display
*/
reportStatus(status: string): void;
/**
* Report progress with current and total values
* @param current - Current progress value
* @param total - Total progress value (optional, uses previous if omitted)
* @param status - Status message (optional)
*/
reportProgress(current: number, total?: number, status?: string): void;
}Usage Examples:
import { createBuilder } from "@angular-devkit/architect";
export default createBuilder((options, context) => {
// Report initial state
context.reportRunning();
context.reportStatus("Initializing build process");
return new Promise((resolve) => {
const tasks = [
"Reading configuration",
"Analyzing dependencies",
"Compiling TypeScript",
"Bundling modules",
"Optimizing output",
"Writing files"
];
let completed = 0;
const processTask = (taskIndex: number) => {
const task = tasks[taskIndex];
// Update status and progress
context.reportStatus(task);
context.reportProgress(taskIndex, tasks.length, task);
// Simulate work
setTimeout(() => {
completed++;
context.logger.info(`Completed: ${task}`);
if (completed < tasks.length) {
processTask(completed);
} else {
// Final progress update
context.reportProgress(tasks.length, tasks.length, "Build complete");
resolve({ success: true });
}
}, 1000);
};
processTask(0);
});
});Complex progress reporting patterns for sophisticated builders.
// Multi-phase builder with progress tracking
export const multiPhaseBuilder = createBuilder(async (options, context) => {
const phases = [
{ name: "Analysis", weight: 10 },
{ name: "Compilation", weight: 60 },
{ name: "Optimization", weight: 20 },
{ name: "Output", weight: 10 }
];
const totalWeight = phases.reduce((sum, phase) => sum + phase.weight, 0);
let completedWeight = 0;
for (const phase of phases) {
context.reportStatus(`Starting ${phase.name}`);
// Simulate phase work with sub-progress
for (let step = 0; step <= 10; step++) {
const phaseProgress = step / 10;
const overallProgress = completedWeight + (phase.weight * phaseProgress);
const percentage = Math.round((overallProgress / totalWeight) * 100);
context.reportProgress(
percentage,
100,
`${phase.name}: ${step}/10 steps`
);
await new Promise(resolve => setTimeout(resolve, 100));
}
completedWeight += phase.weight;
context.logger.info(`Completed ${phase.name}`);
}
return { success: true };
});
// Builder with error handling and progress
export const robustProgressBuilder = createBuilder(async (options, context) => {
try {
context.reportRunning();
context.reportProgress(0, 100, "Starting");
// Risky operation
await performRiskyOperation();
context.reportProgress(50, 100, "Halfway complete");
// Another risky operation
await performAnotherRiskyOperation();
context.reportProgress(100, 100, "Complete");
return { success: true };
} catch (error) {
// Report error state through progress
context.reportStatus(`Error: ${error.message}`);
// Note: Error state is typically handled by the framework
// but you can provide additional context through status
return { success: false, error: error.message };
}
});Examples of monitoring and reacting to progress updates.
// Progress monitoring with detailed logging
async function monitorBuildProgress(architect: Architect, target: Target) {
const run = await architect.scheduleTarget(target);
// Track progress with timestamps
const startTime = Date.now();
let lastProgress = 0;
run.progress.subscribe(progress => {
const elapsed = Date.now() - startTime;
switch (progress.state) {
case BuilderProgressState.Waiting:
console.log(`[${elapsed}ms] Waiting: ${progress.status || 'Ready to start'}`);
break;
case BuilderProgressState.Running:
const current = progress.current || 0;
const total = progress.total || 100;
const percent = Math.round((current / total) * 100);
if (percent > lastProgress) {
console.log(`[${elapsed}ms] Progress: ${percent}% - ${progress.status || 'Running'}`);
lastProgress = percent;
}
break;
case BuilderProgressState.Error:
console.error(`[${elapsed}ms] Error: ${progress.error}`);
break;
case BuilderProgressState.Stopped:
console.log(`[${elapsed}ms] Stopped`);
break;
}
});
const result = await run.result;
const totalTime = Date.now() - startTime;
console.log(`Build completed in ${totalTime}ms: ${result.success ? 'SUCCESS' : 'FAILED'}`);
return result;
}
// Progress aggregation across multiple builders
async function runParallelBuilds(architect: Architect, targets: Target[]) {
const runs = await Promise.all(
targets.map(target => architect.scheduleTarget(target))
);
// Aggregate progress from all runs
const progressStreams = runs.map((run, index) =>
run.progress.pipe(
map(progress => ({ ...progress, runIndex: index }))
)
);
merge(...progressStreams).subscribe(progress => {
console.log(`Run ${progress.runIndex}: ${progress.state} - ${progress.status}`);
});
// Wait for all to complete
const results = await Promise.all(runs.map(run => run.result));
return {
success: results.every(r => r.success),
results
};
}JSON schema definitions for progress validation.
// Progress schema is defined in progress-schema.json
// Key properties:
// - state: enum ["stopped", "error", "waiting", "running"]
// - current: number (when state is "running")
// - total: number (optional, when state is "running")
// - status: string (optional status message)
// - error: any (when state is "error")