Skip to content

Commit

Permalink
Merge pull request #3383 from dmichon-msft/install-option
Browse files Browse the repository at this point in the history
[rush-lib] Allow phased commands to install prior to execution if requested
  • Loading branch information
iclanton committed May 4, 2022
2 parents 7ffca33 + 736f13e commit c228179
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 8 deletions.
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Provide ability for phased script commands to internally invoke \"rush install\" prior to execution.",
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
25 changes: 19 additions & 6 deletions libraries/rush-lib/src/api/CommandLineConfiguration.ts
Expand Up @@ -74,7 +74,7 @@ export interface ICommandWithParameters {

export interface IPhasedCommandConfig extends IPhasedCommandWithoutPhasesJson, ICommandWithParameters {
/**
* If set to "true," then this phased command was generated from a bulk command, and
* If set to `true`, then this phased command was generated from a bulk command, and
* was not explicitly defined in the command-line.json file.
*/
isSynthetic: boolean;
Expand All @@ -83,13 +83,19 @@ export interface IPhasedCommandConfig extends IPhasedCommandWithoutPhasesJson, I
phases: Set<IPhase>;

/**
* If set to "true," this phased command will alwasy run in watch mode, regardless of CLI flags.
* If set to `true`, this phased command will always run in watch mode, regardless of CLI flags.
*/
alwaysWatch: boolean;
/**
* The set of phases to execute when running this phased command in watch mode.
*/
watchPhases: Set<IPhase>;
/**
* If set to `true`, then this phased command will always perform an install before executing, regardless of CLI flags.
* If set to `false`, then Rush will define a built-in "--install" CLI flag for this command.
* If undefined, then Rush does not define a built-in "--install" CLI flag for this command and no installation is performed.
*/
alwaysInstall: boolean | undefined;
}

export interface IGlobalCommandConfig extends IGlobalCommandJson, ICommandWithParameters {}
Expand Down Expand Up @@ -287,7 +293,8 @@ export class CommandLineConfiguration {
associatedParameters: new Set<IParameterJson>(),
phases: commandPhases,
watchPhases,
alwaysWatch: false
alwaysWatch: false,
alwaysInstall: undefined
};

for (const phaseName of command.phases) {
Expand Down Expand Up @@ -315,7 +322,7 @@ export class CommandLineConfiguration {
}
}

const { watchOptions } = command;
const { watchOptions, installOptions } = command;

if (watchOptions) {
normalizedCommand.alwaysWatch = watchOptions.alwaysWatch;
Expand All @@ -334,6 +341,10 @@ export class CommandLineConfiguration {
}
}

if (installOptions) {
normalizedCommand.alwaysInstall = installOptions.alwaysInstall;
}

break;
}

Expand Down Expand Up @@ -401,7 +412,8 @@ export class CommandLineConfiguration {
disableBuildCache: DEFAULT_REBUILD_COMMAND_JSON.disableBuildCache,
associatedParameters: buildCommand.associatedParameters, // rebuild should share build's parameters in this case,
watchPhases: new Set(),
alwaysWatch: false
alwaysWatch: false,
alwaysInstall: undefined
};
this.commands.set(rebuildCommand.name, rebuildCommand);
}
Expand Down Expand Up @@ -656,7 +668,8 @@ export class CommandLineConfiguration {
phases,
// Bulk commands used the same phases for watch as for regular execution. Preserve behavior.
watchPhases: command.watchForChanges ? phases : new Set(),
alwaysWatch: !!command.watchForChanges
alwaysWatch: !!command.watchForChanges,
alwaysInstall: undefined
};

return translatedCommand;
Expand Down
3 changes: 3 additions & 0 deletions libraries/rush-lib/src/api/CommandLineJson.ts
Expand Up @@ -49,6 +49,9 @@ export interface IPhasedCommandJson extends IPhasedCommandWithoutPhasesJson {
alwaysWatch: boolean;
watchPhases: string[];
};
installOptions?: {
alwaysInstall: boolean;
};
}

/**
Expand Down
3 changes: 2 additions & 1 deletion libraries/rush-lib/src/cli/RushCommandLineParser.ts
Expand Up @@ -396,7 +396,8 @@ export class RushCommandLineParser extends CommandLineParser {
watchPhases: command.watchPhases,
phases: commandLineConfiguration.phases,

alwaysWatch: command.alwaysWatch
alwaysWatch: command.alwaysWatch,
alwaysInstall: command.alwaysInstall
})
);
}
Expand Down
25 changes: 25 additions & 0 deletions libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts
Expand Up @@ -48,6 +48,7 @@ export interface IPhasedScriptActionOptions extends IBaseScriptActionOptions<IPh
phases: Map<string, IPhase>;

alwaysWatch: boolean;
alwaysInstall: boolean | undefined;
}

interface IRunPhasesOptions {
Expand Down Expand Up @@ -84,6 +85,7 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> {
private readonly _initialPhases: ReadonlySet<IPhase>;
private readonly _watchPhases: ReadonlySet<IPhase>;
private readonly _alwaysWatch: boolean;
private readonly _alwaysInstall: boolean | undefined;
private readonly _knownPhases: ReadonlyMap<string, IPhase>;

private _changedProjectsOnly!: CommandLineFlagParameter;
Expand All @@ -93,6 +95,7 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> {
private _ignoreHooksParameter!: CommandLineFlagParameter;
private _watchParameter: CommandLineFlagParameter | undefined;
private _timelineParameter: CommandLineFlagParameter | undefined;
private _installParameter: CommandLineFlagParameter | undefined;

public constructor(options: IPhasedScriptActionOptions) {
super(options);
Expand All @@ -102,6 +105,7 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> {
this._initialPhases = options.initialPhases;
this._watchPhases = options.watchPhases;
this._alwaysWatch = options.alwaysWatch;
this._alwaysInstall = options.alwaysInstall;
this._knownPhases = options.phases;

this.hooks = new PhasedCommandHooks();
Expand All @@ -113,6 +117,16 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> {
}

public async runAsync(): Promise<void> {
if (this._alwaysInstall || this._installParameter?.value) {
const { doBasicInstallAsync } = await import('../../logic/installManager/doBasicInstallAsync');

await doBasicInstallAsync({
rushConfiguration: this.rushConfiguration,
rushGlobalFolder: this.rushGlobalFolder,
isDebug: this.parser.isDebug
});
}

// TODO: Replace with last-install.flag when "rush link" and "rush unlink" are deprecated
const lastLinkFlag: LastLinkFlag = LastLinkFlagFactory.getCommonTempFlag(this.rushConfiguration);
if (!lastLinkFlag.isValid()) {
Expand Down Expand Up @@ -397,6 +411,17 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> {
});
}

// If `this._alwaysInstall === undefined`, Rush does not define the parameter
// but a repository may still define a custom parameter with the same name.
if (this._alwaysInstall === false) {
this._installParameter = this.defineFlagParameter({
parameterLongName: '--install',
description:
'Normally a phased command expects "rush install" to have been manually run first. If this flag is specified, ' +
'Rush will automatically perform an install before processing the current command.'
});
}

this.defineScriptParameters();

for (const [{ associatedPhases }, tsCommandLineParameter] of this.customParameters) {
Expand Down
50 changes: 50 additions & 0 deletions libraries/rush-lib/src/logic/installManager/doBasicInstallAsync.ts
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import type { RushConfiguration } from '../../api/RushConfiguration';
import type { RushGlobalFolder } from '../../api/RushGlobalFolder';
import type { BaseInstallManager } from '../base/BaseInstallManager';
import { InstallManagerFactory } from '../InstallManagerFactory';
import { SetupChecks } from '../SetupChecks';
import { PurgeManager } from '../PurgeManager';
import { VersionMismatchFinder } from '../versionMismatch/VersionMismatchFinder';

export interface IRunInstallOptions {
rushConfiguration: RushConfiguration;
rushGlobalFolder: RushGlobalFolder;
isDebug: boolean;
}

export async function doBasicInstallAsync(options: IRunInstallOptions): Promise<void> {
const { rushConfiguration, rushGlobalFolder, isDebug } = options;

VersionMismatchFinder.ensureConsistentVersions(rushConfiguration);
SetupChecks.validate(rushConfiguration);

const purgeManager: typeof PurgeManager.prototype = new PurgeManager(rushConfiguration, rushGlobalFolder);

const installManager: BaseInstallManager = InstallManagerFactory.getInstallManager(
rushConfiguration,
rushGlobalFolder,
purgeManager,
{
debug: isDebug,
allowShrinkwrapUpdates: false,
checkOnly: false,
bypassPolicy: false,
noLink: false,
fullUpgrade: false,
recheckShrinkwrap: false,
collectLogFile: false,
pnpmFilterArguments: [],
maxInstallAttempts: 1,
networkConcurrency: undefined
}
);

try {
await installManager.doInstallAsync();
} finally {
purgeManager.deleteAll();
}
}
17 changes: 16 additions & 1 deletion libraries/rush-lib/src/schemas/command-line.schema.json
Expand Up @@ -209,6 +209,20 @@
}
}
}
},
"installOptions": {
"title": "Install Options",
"description": "Controls behavior related to performing installation as part of executing this command.",
"type": "object",
"additionalProperties": false,
"required": ["alwaysInstall"],
"properties": {
"alwaysInstall": {
"title": "Always Install",
"description": "Indicates that this command will always perform a standard \"rush install\" before executing, as if the \"--install\" CLI flag was passed.",
"type": "boolean"
}
}
}
}
},
Expand All @@ -225,7 +239,8 @@
"enableParallelism": { "$ref": "#/definitions/anything" },
"incremental": { "$ref": "#/definitions/anything" },
"phases": { "$ref": "#/definitions/anything" },
"watchOptions": { "$ref": "#/definitions/anything" }
"watchOptions": { "$ref": "#/definitions/anything" },
"installOptions": { "$ref": "#/definitions/anything" }
}
}
]
Expand Down

0 comments on commit c228179

Please sign in to comment.