Skip to content
Open
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ for large scale TypeScript monorepos.
- [API Documenter](https://api-extractor.com/pages/setup/generating_docs/) - use TSDoc comments to publish an API documentation website
- [Lockfile Explorer](https://lfx.rushstack.io/) - investigate and solve version conflicts for PNPM lockfiles
- [TSDoc](https://tsdoc.org/) - the standard for doc comments in TypeScript code
- [Dogfooding the Rush daemon](./docs/rush/dogfooding-rush-daemon.md) - contributor guide for building this repo
with the opt-in `rush-client` daemon, built from source

## Related Repos

Expand Down
9 changes: 7 additions & 2 deletions apps/rush-cli-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
Separate `rush-client` and `rushx-client` binaries, opt-in until cutover. Existing
`rush`, `rushx`, and their reporter entrypoints are unchanged.

To try the daemon on the rushstack repository itself before a release contains it, follow the
[contributor dogfooding guide](../../docs/rush/dogfooding-rush-daemon.md).

## Native frontend dependency

The dependency on `@microsoft/rush` is intentional: it is the version-selecting
Expand Down Expand Up @@ -116,9 +119,11 @@ changed configuration or command shape replaces the session and graph in the sam
process. Environment, installed dependencies, implementation content, or selected
Rush version changes require a process restart rather than patching the existing
engine. Direct, inherited, and rig-based project configuration uses private native
loaders and is rechecked before execution. External plugins, `.env`, phased
loaders and is rechecked before execution. External plugins that participate in the
requested command (unassociated plugins, plugins associated with it, or plugin command-line
files that define it, its phases or parameters for either), `.env`, phased
watch/install options, and unsupported event-hook scripts still use typed
pre-execution fallback; this does not exclude the built-in `install` and `update`
pre-execution fallback; plugins scoped only to other commands are permitted. This does not exclude the built-in `install` and `update`
commands described above. The native Rush lock is held for preparation and each
coalesced iteration, not while idle; native commands and `--no-daemon` can run
after a completed request without stopping the daemon.
Expand Down
11 changes: 11 additions & 0 deletions apps/rush-cli-client/src/bundledRushVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import { JsonFile } from '@rushstack/node-core-library';

/**
* The version of the bundled Rush engine, which is `Rush.version`, read without loading the engine.
*/
export const BUNDLED_RUSH_VERSION: string = JsonFile.load(
require.resolve('@microsoft/rush-lib/package.json')
).version;
18 changes: 11 additions & 7 deletions apps/rush-cli-client/src/daemonConnectionOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@

import * as fs from 'node:fs';

import { Rush } from '@microsoft/rush-lib';
import { JsonFile } from '@rushstack/node-core-library';
import type { IConnectOrStartDaemonOptions } from '@rushstack/rush-client-core';
import { computeDaemonWorkspaceKey, resolveDaemonPathsFromProcess } from '@rushstack/rush-daemon-transport';
import { readDaemonInstallationMetadata } from '@rushstack/rush-daemon/lib/DaemonInstallation';
import {
DaemonLauncherUnavailableError,
getSelectedDaemonStartCommand,
selectDaemonLauncherAsync,
type IVersionSelectedDaemonLaunch
} from '@rushstack/rush-daemon/lib/VersionSelectedDaemonLauncher';
getSelectedDaemonStartCommand
} from '@rushstack/rush-daemon/lib/DaemonLaunchCommand';
import type { IVersionSelectedDaemonLaunch } from '@rushstack/rush-daemon/lib/VersionSelectedDaemonLauncher';

import { BUNDLED_RUSH_VERSION } from './bundledRushVersion';

export function getDaemonConnectionOptions(
repoRoot: string,
Expand Down Expand Up @@ -57,9 +57,13 @@ export async function getDaemonConnectionOptionsAsync(
false
);
if (!autoStart) return { paths: options.paths };
// The bundled runtime is already loaded here; its bootstrap re-attests before binding.
if (rushVersion === Rush.version)
// The bundled runtime needs no selection; its bootstrap re-attests before binding.
if (rushVersion === BUNDLED_RUSH_VERSION)
return getDaemonConnectionOptions(repoRoot, rushVersion, environment, true);
// Selecting another installation uses the engine's installer APIs, so it is loaded only when needed.
const { selectDaemonLauncherAsync } = await import(
'@rushstack/rush-daemon/lib/VersionSelectedDaemonLauncher'
);
const launch: IVersionSelectedDaemonLaunch = await selectDaemonLauncherAsync({
repoRoot: fs.realpathSync.native(repoRoot),
rushVersion,
Expand Down
22 changes: 13 additions & 9 deletions apps/rush-cli-client/src/launchClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
import * as path from 'node:path';

import {
Rush,
RushConfiguration,
daemonEnvironmentVariables,
resolveDaemonConfiguration,
type IDaemonConfigurationJson
} from '@microsoft/rush-lib';
} from '@microsoft/rush-lib/lib/api/DaemonConfiguration';
import { tryFindRushJsonLocation } from '@microsoft/rush-lib/lib/api/RushJsonLocation';
import { JsonFile } from '@rushstack/node-core-library';
import {
DaemonClientError,
Expand All @@ -22,9 +21,9 @@ import {
} from '@rushstack/rush-client-core';
import type { DaemonVerbosity, IDaemonRequestEnvelope } from '@rushstack/rush-daemon-protocol';
import { ConsoleTerminalProvider } from '@rushstack/terminal';
import { MinimalRushConfiguration } from '@microsoft/rush/lib/MinimalRushConfiguration';
import { DaemonLauncherUnavailableError } from '@rushstack/rush-daemon/lib/VersionSelectedDaemonLauncher';
import { DaemonLauncherUnavailableError } from '@rushstack/rush-daemon/lib/DaemonLaunchCommand';

import { BUNDLED_RUSH_VERSION } from './bundledRushVersion';
import { executeDaemonCommandAsync } from './daemonCommands';
import { ClientOperationRenderer } from './ClientOperationRenderer';
import { getDaemonConnectionOptionsAsync } from './daemonConnectionOptions';
Expand All @@ -39,7 +38,7 @@ interface IWorkspaceJson {
export async function launchClientAsync(rushx: boolean): Promise<void> {
const cwd: string = process.cwd();
const environment: Readonly<NodeJS.ProcessEnv> = Object.freeze({ ...process.env });
const rushJsonPath: string | undefined = RushConfiguration.tryFindRushJsonLocation({ startingFolder: cwd });
const rushJsonPath: string | undefined = tryFindRushJsonLocation({ startingFolder: cwd });
const workspace: IWorkspaceJson | undefined = rushJsonPath ? JsonFile.load(rushJsonPath) : undefined;
const config: Readonly<Required<IDaemonConfigurationJson>> = resolveDaemonConfiguration(
workspace?.daemon,
Expand All @@ -52,7 +51,8 @@ export async function launchClientAsync(rushx: boolean): Promise<void> {
rushx,
hasTerminal: !!(process.stdin.isTTY || process.stdout.isTTY || process.stderr.isTTY)
});
const selectedVersion: string = environment.RUSH_PREVIEW_VERSION ?? workspace?.rushVersion ?? Rush.version;
const selectedVersion: string =
environment.RUSH_PREVIEW_VERSION ?? workspace?.rushVersion ?? BUNDLED_RUSH_VERSION;
if (!rushx && route.commandName === 'daemon') {
if ((route.argv[1] === 'start' || route.argv[1] === 'restart') && process.argv.includes('--no-daemon')) {
throw new Error(`--no-daemon cannot be combined with daemon ${route.argv[1]}.`);
Expand Down Expand Up @@ -145,7 +145,11 @@ export async function launchClientAsync(rushx: boolean): Promise<void> {
}
};
try {
if (rushx) MinimalRushConfiguration.loadFromDefaultLocation((line) => discoveryLines.push(line));
if (rushx) {
// Rushx discovery output needs the frontend's configuration loader, which loads the engine.
const { MinimalRushConfiguration } = await import('@microsoft/rush/lib/MinimalRushConfiguration');
MinimalRushConfiguration.loadFromDefaultLocation((line) => discoveryLines.push(line));
}
await renderer.initializeAsync();
outcome = await executeWithDaemonRestartAsync(client, connection, {
request,
Expand Down Expand Up @@ -207,7 +211,7 @@ function launchInProcess(argv: ReadonlyArray<string>, rushx: boolean, selectedVe
const executable: string = rushx ? 'rushx' : 'rush';
const rushFolder: string = path.dirname(require.resolve('@microsoft/rush/package.json'));
process.argv = [process.execPath, path.join(rushFolder, 'bin', executable), ...argv];
if (selectedVersion !== Rush.version) {
if (selectedVersion !== BUNDLED_RUSH_VERSION) {
// Old Rush releases reject new RUSH_* names. Only strip this launcher's own inputs;
// the request snapshot was captured earlier and is never mutated.
for (const name of [...Object.values(daemonEnvironmentVariables), 'RUSH_DAEMON_EXPERIMENTAL']) {
Expand Down
7 changes: 5 additions & 2 deletions apps/rush-cli-client/src/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// See LICENSE in the project root for license information.

import type { IDaemonRequestAdmissionOptions } from '@rushstack/rush-daemon-protocol';
import { RushXCommand, type IRushXCommandLineArguments } from '@microsoft/rush-lib';
import {
parseRushXCommandLineArguments,
type IRushXCommandLineArguments
} from '@microsoft/rush-lib/lib/cli/RushXCommandLineArguments';

import { parseClientAdmissionControls, type IClientAdmissionControls } from './ClientAdmissionControls';

Expand Down Expand Up @@ -53,7 +56,7 @@ export function selectClientRoute(options: IClientRouteOptions): IClientRoute {
...(separator < 0 ? [] : controls.argv.slice(separator))
];
const rushxArguments: IRushXCommandLineArguments | undefined = options.rushx
? RushXCommand.parseArguments(argv, options.environment)
? parseRushXCommandLineArguments(argv, options.environment)
: undefined;
const commandName: string | undefined = rushxArguments ? rushxArguments.commandName || undefined : argv[0];
const reporterControls: boolean =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Speed up daemon requests: include the bundled implementation in the runtime fingerprint without resolving every file's real path on each request, and resolve rig profiles asynchronously when the daemon reloads project configuration.",
"type": "patch"
}
],
"packageName": "@microsoft/rush"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Allow daemon build and rebuild requests in repositories whose external Rush plugins are associated only with other commands, and reload the daemon generation when a configured plugin's autoinstaller package.json, cached manifest, or command-line.json changes.",
"type": "patch"
}
],
"packageName": "@microsoft/rush"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/rush-cli-client",
"comment": "Start rush-client without loading the Rush engine, which removes about 1.5 seconds from every invocation on Windows.",
"type": "patch"
}
],
"packageName": "@rushstack/rush-cli-client"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/rush-cli-client",
"comment": "Document the plugin narrowing and link the rushstack contributor guide for dogfooding the daemon from source.",
"type": "none"
}
],
"packageName": "@rushstack/rush-cli-client"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/rush-client-core",
"comment": "Keep waiting for a slow detached daemon start after the requesting client gives up, so later clients can use the daemon instead of finding an abandoned startup reservation.",
"type": "patch"
}
],
"packageName": "@rushstack/rush-client-core"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/rush-daemon",
"comment": "Move the daemon start command and launcher error into a module that does not load the Rush engine.",
"type": "none"
}
],
"packageName": "@rushstack/rush-daemon"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/rush-daemon",
"comment": "Document that external Rush plugins scoped only to other commands no longer prevent daemon builds.",
"type": "none"
}
],
"packageName": "@rushstack/rush-daemon"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/rush-daemon",
"comment": "On Windows, keep tools started by the detached daemon (Git, tar and operation shells) from opening visible console windows.",
"type": "patch"
}
],
"packageName": "@rushstack/rush-daemon"
}
15 changes: 15 additions & 0 deletions common/config/rush/deploy-rush-daemon-dogfood.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* This configuration file defines a deployment scenario for use with the "rush deploy" command.
* For full documentation, please see https://rushjs.io
*
* The "rush-daemon-dogfood" scenario extracts a stable, self-contained copy of the source-built
* "rush-client" and "rushx-client" binaries, including the daemon and the Rush engine they bundle.
* Contributors use it to try the opt-in Rush daemon on this repository before a release contains it:
* a daemon started from the copy keeps working while the workspace rebuilds its own Rush projects.
* See docs/rush/dogfooding-rush-daemon.md.
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/deploy-scenario.schema.json",

"deploymentProjectNames": ["@rushstack/rush-cli-client"]
}
Loading
Loading