Repository navigation
Expand file tree
/
Copy pathRushVersionSelector.ts
More file actions
131 lines (113 loc) · 5.74 KB
/
Copy pathRushVersionSelector.ts
File metadata and controls
131 lines (113 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import * as path from 'node:path';
import * as semver from 'semver';
import { LockFile, Import } from '@rushstack/node-core-library';
import { REPORTER_PROTOCOL_VERSION, type ReporterPrivacyClassification } from '@rushstack/rush-reporter';
import { Utilities } from '@microsoft/rush-lib/lib/utilities/Utilities';
import { _FlagFile, _RushGlobalFolder } from '@microsoft/rush-lib';
import { RushCommandSelector } from './RushCommandSelector';
import type { IRushFrontendLaunchOptions } from './IRushFrontendLaunchOptions';
import type { MinimalRushConfiguration } from './MinimalRushConfiguration';
const MAX_INSTALL_ATTEMPTS: number = 3;
export class RushVersionSelector {
#rushGlobalFolder: _RushGlobalFolder;
#currentPackageVersion: string;
public constructor(currentPackageVersion: string) {
this.#rushGlobalFolder = new _RushGlobalFolder();
this.#currentPackageVersion = currentPackageVersion;
}
public async ensureRushVersionInstalledAsync(
version: string,
configuration: MinimalRushConfiguration | undefined,
executeOptions: IRushFrontendLaunchOptions
): Promise<void> {
const isLegacyRushVersion: boolean = semver.lt(version, '4.0.0');
const expectedRushPath: string = path.join(this.#rushGlobalFolder.nodeSpecificPath, `rush-${version}`);
const installMarker: _FlagFile = new _FlagFile(expectedRushPath, 'last-install', {
node: process.versions.node
});
let installIsValid: boolean = await installMarker.isValidAsync();
if (!installIsValid) {
// Need to install Rush
this.#reportStartupMessage(
executeOptions,
`Rush version ${version} is not currently installed. Installing...`
);
const resourceName: string = `rush-${version}`;
this.#reportStartupMessage(executeOptions, `Trying to acquire lock for ${resourceName}`);
const lock: LockFile = await LockFile.acquireAsync(expectedRushPath, resourceName);
installIsValid = await installMarker.isValidAsync();
if (installIsValid) {
this.#reportStartupMessage(executeOptions, 'Another process performed the installation.');
} else {
await Utilities.installPackageInDirectoryAsync({
directory: expectedRushPath,
packageName: isLegacyRushVersion ? '@microsoft/rush' : '@microsoft/rush-lib',
version: version,
tempPackageTitle: 'rush-local-install',
maxInstallAttempts: MAX_INSTALL_ATTEMPTS,
// This is using a local configuration to install a package in a shared global location.
// Generally that's a bad practice, but in this case if we can successfully install
// the package at all, we can reasonably assume it's good for all the repositories.
// In particular, we'll assume that two different NPM registries cannot have two
// different implementations of the same version of the same package.
// This was needed for: https://github.com/microsoft/rushstack/issues/691
commonRushConfigFolder: configuration ? configuration.commonRushConfigFolder : undefined,
suppressOutput: true,
// Filter out npm-incompatible properties (e.g. pnpm-specific settings) from .npmrc
// since this installation always uses npm regardless of the repo's package manager.
filterNpmIncompatibleProperties: true
});
this.#reportStartupMessage(
executeOptions,
`Successfully installed Rush version ${version} in ${expectedRushPath}.`,
'local-sensitive'
);
// If we've made it here without exception, write the flag file
await installMarker.createAsync();
lock.release();
}
}
if (semver.lt(version, '3.0.20')) {
// In old versions, requiring the entry point invoked the command-line parser immediately,
// so fail if "rushx" or "rush-pnpm" was used
RushCommandSelector.failIfNotInvokedAsRush(version);
require(path.join(expectedRushPath, 'node_modules', '@microsoft', 'rush', 'lib', 'rush'));
} else if (semver.lt(version, '4.0.0')) {
// In old versions, requiring the entry point invoked the command-line parser immediately,
// so fail if "rushx" or "rush-pnpm" was used
RushCommandSelector.failIfNotInvokedAsRush(version);
require(path.join(expectedRushPath, 'node_modules', '@microsoft', 'rush', 'lib', 'start'));
} else {
// Explicitly resolve the entry point for rush-lib, rather than using a simple require, because
// newer versions of rush-lib use the package.json `exports` field, which maps
// `lib/index` to `lib-commonjs/index.js`
const rushLibEntrypoint: string = await Import.resolveModuleAsync({
modulePath: '@microsoft/rush-lib/lib/index',
baseFolderPath: expectedRushPath
});
const rushCliEntrypoint: typeof import('@microsoft/rush-lib') = require(rushLibEntrypoint);
// For newer rush-lib, RushCommandSelector can test whether "rushx" is supported or not
RushCommandSelector.execute(this.#currentPackageVersion, rushCliEntrypoint, executeOptions);
}
}
#reportStartupMessage(
options: IRushFrontendLaunchOptions,
text: string,
privacy: ReporterPrivacyClassification = 'public'
): void {
if (options.reporterEnabled) {
options.reporter.eventSink.emit({
protocolVersion: REPORTER_PROTOCOL_VERSION,
sessionId: options.reporter.sessionId,
source: { packageName: '@microsoft/rush', packageVersion: this.#currentPackageVersion },
privacy,
type: 'activityChanged',
payload: { kind: 'version-selection', text }
});
} else {
console.log(text);
}
}
}