Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@rushstack/rush-daemon",
"comment": "Add an opt-in all-project engine component factory with explicit phase/plugin shape, retained invalidation reconciliation, and a deterministic engine shutdown contract.",
"type": "minor"
}
],
"packageName": "@rushstack/rush-daemon",
"email": "mojazayeri@users.noreply.github.com"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@rushstack/rush-daemon",
"comment": "Add a reusable warm workspace session with stable Rush configuration metadata, retained headless invalidations, and deterministic host lifecycle integration.",
"type": "minor"
}
],
"packageName": "@rushstack/rush-daemon",
"email": "mojazayeri@users.noreply.github.com"
}
3 changes: 3 additions & 0 deletions common/config/subspaces/default/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

206 changes: 206 additions & 0 deletions common/reviews/api/rush-daemon.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,49 @@

/// <reference types="node" />

import type { GetInputsSnapshotAsyncFn } from '@microsoft/rush-lib';
import type { IDaemonPaths } from '@rushstack/rush-daemon-transport';
import type { IInputsSnapshot } from '@microsoft/rush-lib';
import type { IOperationGraph } from '@microsoft/rush-lib';
import type { Operation } from '@microsoft/rush-lib';
import { RushConfiguration } from '@microsoft/rush-lib';
import type { RushConfigurationProject } from '@microsoft/rush-lib';
import type { RushSession } from '@microsoft/rush-lib';

// @beta
export type CreateWorkspaceEngineComponentsAsync = (options: ICreateWorkspaceEngineComponentsOptions) => Promise<IWorkspaceEngineComponents>;

// @beta
export type CreateWorkspaceSessionComponentsAsync = (options: ICreateWorkspaceSessionComponentsOptions) => Promise<IWorkspaceSessionComponents>;

// @beta
export interface ICreateWorkspaceEngineComponentsOptions extends IWorkspaceEngineShape {
readonly projectSelection: ReadonlySet<RushConfigurationProject>;
// (undocumented)
readonly rushConfiguration: RushConfiguration;
}

// @beta
export interface ICreateWorkspaceSessionComponentsOptions {
// (undocumented)
readonly invalidations: WorkspaceInvalidationTracker;
// (undocumented)
readonly onError?: (error: Error) => void;
// (undocumented)
readonly rushConfiguration: RushConfiguration;
}

// @beta
export interface IMapWorkspaceInvalidationsOptions {
// (undocumented)
readonly changedPaths: ReadonlyArray<string>;
// (undocumented)
readonly currentInputsSnapshot: IInputsSnapshot;
// (undocumented)
readonly nextInputsSnapshot: IInputsSnapshot;
// (undocumented)
readonly operationGraph: IOperationGraph;
}

// @public
export interface IRequestLease {
Expand All @@ -27,6 +69,7 @@ export interface IRequestSchedulerAcquireOptions {

// @beta
export interface IRushDaemonHostOptions {
readonly createWorkspaceSessionAsync?: WorkspaceSessionFactory;
readonly daemonVersion: string;
readonly onError?: (error: Error) => void;
readonly repoRoot: string;
Expand All @@ -40,6 +83,127 @@ export interface IRushDaemonServeOptions extends IRushDaemonHostOptions {
readonly shutdownSignal?: AbortSignal;
}

// @beta
export interface IWorkspaceEngineComponentFactoryOptions {
// (undocumented)
readonly createEngineComponentsAsync: CreateWorkspaceEngineComponentsAsync;
// (undocumented)
readonly mapInvalidationsToOperationsAsync: MapWorkspaceInvalidationsToOperationsAsync;
// (undocumented)
readonly shape: IWorkspaceEngineShape;
}

// @beta
export interface IWorkspaceEngineComponents extends AsyncDisposable {
[Symbol.asyncDispose](): Promise<void>;
// (undocumented)
readonly getInputsSnapshotAsync: GetInputsSnapshotAsyncFn;
// (undocumented)
readonly inputsSnapshot: IInputsSnapshot;
// (undocumented)
readonly operationGraph: IOperationGraph;
// (undocumented)
readonly rushSession: RushSession;
}

// @beta
export interface IWorkspaceEngineShape {
// (undocumented)
readonly phaseNames: ReadonlyArray<string>;
// (undocumented)
readonly pluginNames: ReadonlyArray<string>;
}

// @beta
export interface IWorkspaceInvalidationReconciliation {
// (undocumented)
readonly inputsSnapshot: IInputsSnapshot;
// (undocumented)
readonly invalidatedOperationCount: number;
// (undocumented)
readonly isFullInvalidation: boolean;
// (undocumented)
readonly sequence: number;
}

// @beta
export interface IWorkspaceInvalidationSnapshot {
readonly changedPaths: ReadonlyArray<string>;
readonly hasUnknownChanges: boolean;
readonly isWatcherHealthy: boolean;
readonly sequence: number;
}

// @beta
export interface IWorkspaceInvalidationWatcher extends AsyncDisposable {
// (undocumented)
startAsync(onInvalidation: (changedPath?: string) => void): Promise<void>;
}

// @beta
export interface IWorkspaceSession extends AsyncDisposable {
// (undocumented)
readonly engineShape: IWorkspaceEngineShape | undefined;
// (undocumented)
readonly inputsSnapshot: IInputsSnapshot | undefined;
// (undocumented)
readonly invalidations: WorkspaceInvalidationTracker;
// (undocumented)
readonly metadata: IWorkspaceSessionMetadata;
// (undocumented)
readonly operationGraph: IOperationGraph | undefined;
// (undocumented)
reconcileInvalidationsAsync(): Promise<IWorkspaceInvalidationReconciliation | undefined>;
// (undocumented)
readonly rushConfiguration: RushConfiguration;
// (undocumented)
readonly rushSession: RushSession | undefined;
}

// @beta
export interface IWorkspaceSessionComponents extends AsyncDisposable {
// (undocumented)
readonly engineShape?: IWorkspaceEngineShape;
// (undocumented)
readonly inputsSnapshot?: IInputsSnapshot;
// (undocumented)
readonly operationGraph?: IOperationGraph;
readonly projectWatcher?: IWorkspaceInvalidationWatcher;
// (undocumented)
readonly reconcileInvalidationsAsync?: () => Promise<IWorkspaceInvalidationReconciliation>;
// (undocumented)
readonly rushSession?: RushSession;
}

// @beta
export interface IWorkspaceSessionMetadata {
// (undocumented)
readonly projectCount: number;
// (undocumented)
readonly projectNames: ReadonlyArray<string>;
// (undocumented)
readonly repoRoot: string;
// (undocumented)
readonly rushJsonFile: string;
// (undocumented)
readonly rushVersion: string;
}

// @beta
export interface IWorkspaceSessionOptions {
// (undocumented)
readonly createComponentsAsync?: CreateWorkspaceSessionComponentsAsync;
// (undocumented)
readonly onError?: (error: Error) => void;
// (undocumented)
readonly repoRoot: string;
// (undocumented)
readonly rushVersion: string;
}

// @beta
export type MapWorkspaceInvalidationsToOperationsAsync = (options: IMapWorkspaceInvalidationsOptions) => Promise<Iterable<Operation>>;

// @public
export enum RequestExclusivityClass {
// (undocumented)
Expand Down Expand Up @@ -77,6 +241,7 @@ export enum RequestSchedulerErrorCode {
// @beta
export class RushDaemonHost {
closeAsync(): Promise<void>;
getWorkspaceSessionAsync(): Promise<IWorkspaceSession>;
// (undocumented)
readonly paths: IDaemonPaths;
static startAsync(options: IRushDaemonHostOptions): Promise<RushDaemonHost>;
Expand All @@ -85,6 +250,47 @@ export class RushDaemonHost {
// @beta
export function serveRushDaemonAsync(options: IRushDaemonServeOptions): Promise<void>;

// @beta
export class WorkspaceEngineComponentFactory {
constructor(options: IWorkspaceEngineComponentFactoryOptions);
// (undocumented)
readonly createAsync: CreateWorkspaceSessionComponentsAsync;
// (undocumented)
readonly shape: IWorkspaceEngineShape;
}

// @beta
export class WorkspaceInvalidationTracker {
acknowledgeThrough(sequence: number): void;
getSnapshot(): IWorkspaceInvalidationSnapshot;
invalidate(changedPath?: string): void;
markWatcherUnhealthy(): void;
}

// @beta
export class WorkspaceSession implements IWorkspaceSession {
[Symbol.asyncDispose](): Promise<void>;
static createAsync(options: IWorkspaceSessionOptions): Promise<WorkspaceSession>;
// (undocumented)
get engineShape(): IWorkspaceEngineShape | undefined;
// (undocumented)
get inputsSnapshot(): IInputsSnapshot | undefined;
// (undocumented)
readonly invalidations: WorkspaceInvalidationTracker;
// (undocumented)
readonly metadata: IWorkspaceSessionMetadata;
// (undocumented)
readonly operationGraph: IOperationGraph | undefined;
reconcileInvalidationsAsync(): Promise<IWorkspaceInvalidationReconciliation | undefined>;
// (undocumented)
readonly rushConfiguration: RushConfiguration;
// (undocumented)
readonly rushSession: RushSession | undefined;
}

// @beta
export type WorkspaceSessionFactory = (options: IWorkspaceSessionOptions) => Promise<IWorkspaceSession>;

// (No @packageDocumentation comment for this package)

```
18 changes: 17 additions & 1 deletion libraries/rush-daemon/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
# @rushstack/rush-daemon

The long-lived Rush workspace daemon host, including workspace-keyed listener bootstrap,
protocol handshake and liveness control, and explicit serve/shutdown lifecycle APIs.
protocol handshake and liveness control, a warm `WorkspaceSession`, and explicit
serve/shutdown lifecycle APIs.

The package provides an opt-in `rushd` executable. Run it from a Rush workspace to start the host
for the nearest `rush.json`; it does not change the default behavior of `rush`, `rushx`, or
`rush-pnpm`.

The host loads `RushConfiguration` once before signaling readiness and keeps a headless file watcher
active for the daemon lifetime. Its invalidation tracker retains changes while no clients are
connected so a later request can reconcile them. The tracker starts with a conservative unknown
invalidation covering session startup, and excessive distinct paths are compacted into the same
full-workspace signal.

`WorkspaceEngineComponentFactory` provides the opt-in seam for a command integration to supply a real
all-project operation graph, its `RushSession`, and a refreshable inputs snapshot. The integration must
declare the complete phase and plugin shape because Rush plugins can currently vary that shape by command.
The factory validates graph ownership, serializes retained invalidation reconciliation, and maps path-specific
changes through the integration. The engine owner must supply one deterministic async disposer because
`IOperationGraph` does not yet expose an operation that both stops the lifetime and awaits runner cleanup.
The default daemon executable does not construct or route this graph while the command-independent plugin shape and per-iteration runner
lifetime tracked by [rushstack#5895](https://github.com/microsoft/rushstack/issues/5895) remain incomplete.
1 change: 1 addition & 0 deletions libraries/rush-daemon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"_phase:test": "heft run --only test -- --clean"
},
"dependencies": {
"@microsoft/rush-lib": "workspace:*",
"@rushstack/node-core-library": "workspace:*",
"@rushstack/rush-daemon-protocol": "workspace:*",
"@rushstack/rush-daemon-transport": "workspace:*"
Expand Down
Loading