Skip to content
Draft
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
9 changes: 7 additions & 2 deletions common/reviews/api/rush-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1171,9 +1171,9 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
// (undocumented)
readonly jsonFilename: string | undefined;
// @internal (undocumented)
static loadFromJsonFileOrThrow(jsonFilePath: string, commonTempFolder: string): PnpmOptionsConfiguration;
static loadFromJsonFileOrThrow(jsonFilePath: string, commonTempFolder: string, rushUserConfiguration?: RushUserConfiguration): PnpmOptionsConfiguration;
// @internal (undocumented)
static loadFromJsonObject(json: _IPnpmOptionsJson, commonTempFolder: string): PnpmOptionsConfiguration;
static loadFromJsonObject(json: _IPnpmOptionsJson, commonTempFolder: string, rushUserConfiguration?: RushUserConfiguration): PnpmOptionsConfiguration;
readonly minimumReleaseAge: number | undefined;
readonly minimumReleaseAgeExclude: string[] | undefined;
readonly pnpmLockfilePolicies: IPnpmLockfilePolicies | undefined;
Expand Down Expand Up @@ -1338,6 +1338,8 @@ export class RushConfiguration {
//
// @internal (undocumented)
readonly _rushPluginsConfiguration: RushPluginsConfiguration;
// @internal (undocumented)
readonly _rushUserConfiguration: RushUserConfiguration;
readonly shrinkwrapFilename: string;
get shrinkwrapFilePhrase(): string;
// @beta
Expand Down Expand Up @@ -1558,7 +1560,10 @@ export class RushUserConfiguration {
// (undocumented)
static getRushUserFolderPath(): string;
// (undocumented)
static initialize(): RushUserConfiguration;
// (undocumented)
static initializeAsync(): Promise<RushUserConfiguration>;
readonly pnpmStorePath: string | undefined;
}

// @public
Expand Down
15 changes: 13 additions & 2 deletions libraries/rush-lib/src/api/RushConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import type { PackageManagerOptionsConfigurationBase } from '../logic/base/BaseP
import { CustomTipsConfiguration } from './CustomTipsConfiguration';
import { SubspacesConfiguration } from './SubspacesConfiguration';
import { Subspace } from './Subspace';
import { RushUserConfiguration } from './RushUserConfiguration';

const MINIMUM_SUPPORTED_RUSH_JSON_VERSION: string = '0.0.0';
const DEFAULT_BRANCH: string = 'main';
Expand Down Expand Up @@ -247,6 +248,11 @@ export class RushConfiguration {
private readonly _subspacesByName: Map<string, Subspace>;
private readonly _subspaces: Subspace[] = [];

/**
* @internal
*/
public readonly _rushUserConfiguration: RushUserConfiguration;

/**
* The name of the package manager being used to install dependencies
*/
Expand Down Expand Up @@ -677,12 +683,16 @@ export class RushConfiguration {
);
this._rushPluginsConfiguration = new RushPluginsConfiguration(rushPluginsConfigFilename);

// Load user configuration for per-user settings like pnpm store path
this._rushUserConfiguration = RushUserConfiguration.initialize();

this.npmOptions = new NpmOptionsConfiguration(rushConfigurationJson.npmOptions || {});
this.yarnOptions = new YarnOptionsConfiguration(rushConfigurationJson.yarnOptions || {});
try {
this.pnpmOptions = PnpmOptionsConfiguration.loadFromJsonFileOrThrow(
`${this.commonRushConfigFolder}/${RushConstants.pnpmConfigFilename}`,
this.commonTempFolder
this.commonTempFolder,
this._rushUserConfiguration
);
if (rushConfigurationJson.pnpmOptions) {
throw new Error(
Expand All @@ -694,7 +704,8 @@ export class RushConfiguration {
if (FileSystem.isNotExistError(error as Error)) {
this.pnpmOptions = PnpmOptionsConfiguration.loadFromJsonObject(
rushConfigurationJson.pnpmOptions || {},
this.commonTempFolder
this.commonTempFolder,
this._rushUserConfiguration
);
} else {
throw error;
Expand Down
28 changes: 28 additions & 0 deletions libraries/rush-lib/src/api/RushUserConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import schemaJson from '../schemas/rush-user-settings.schema.json';

interface IRushUserSettingsJson {
buildCacheFolder?: string;
pnpm?: {
storePath?: string;
};
}

/**
Expand All @@ -25,11 +28,18 @@ export class RushUserConfiguration {
*/
public readonly buildCacheFolder: string | undefined;

/**
* If provided, specifies the path for the PNPM store directory.
* Can be overridden by the RUSH_PNPM_STORE_PATH environment variable.
*/
public readonly pnpmStorePath: string | undefined;

private constructor(rushUserConfigurationJson: IRushUserSettingsJson | undefined) {
this.buildCacheFolder = rushUserConfigurationJson?.buildCacheFolder;
if (this.buildCacheFolder && !path.isAbsolute(this.buildCacheFolder)) {
throw new Error('buildCacheFolder must be an absolute path');
}
this.pnpmStorePath = rushUserConfigurationJson?.pnpm?.storePath;
}

public static async initializeAsync(): Promise<RushUserConfiguration> {
Expand All @@ -50,6 +60,24 @@ export class RushUserConfiguration {
return new RushUserConfiguration(rushUserSettingsJson);
}

public static initialize(): RushUserConfiguration {
const rushUserFolderPath: string = RushUserConfiguration.getRushUserFolderPath();
const rushUserSettingsFilePath: string = path.join(rushUserFolderPath, 'settings.json');
let rushUserSettingsJson: IRushUserSettingsJson | undefined;
try {
rushUserSettingsJson = JsonFile.loadAndValidate(
rushUserSettingsFilePath,
RushUserConfiguration._schema
);
} catch (e) {
if (!FileSystem.isNotExistError(e as Error)) {
throw e;
}
}

return new RushUserConfiguration(rushUserSettingsJson);
}

public static getRushUserFolderPath(): string {
const homeFolderPath: string = User.getHomeFolder();
return `${homeFolderPath}/${RushConstants.rushUserConfigurationFolderName}`;
Expand Down
3 changes: 2 additions & 1 deletion libraries/rush-lib/src/api/Subspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ export class Subspace {
try {
this._cachedPnpmOptions = PnpmOptionsConfiguration.loadFromJsonFileOrThrow(
this.getPnpmConfigFilePath(),
subspaceTempFolder
subspaceTempFolder,
this._rushConfiguration._rushUserConfiguration
);
this._cachedPnpmOptionsInitialized = true;
} catch (e) {
Expand Down
15 changes: 10 additions & 5 deletions libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
PackageManagerOptionsConfigurationBase
} from '../base/BasePackageManagerOptionsConfiguration';
import { EnvironmentConfiguration } from '../../api/EnvironmentConfiguration';
import type { RushUserConfiguration } from '../../api/RushUserConfiguration';
import schemaJson from '../../schemas/pnpm-config.schema.json';

/**
Expand Down Expand Up @@ -466,13 +467,15 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
return this._globalPatchedDependencies;
}

private constructor(json: IPnpmOptionsJson, commonTempFolder: string, jsonFilename?: string) {
private constructor(json: IPnpmOptionsJson, commonTempFolder: string, jsonFilename?: string, rushUserConfiguration?: RushUserConfiguration) {
super(json);
this._json = json;
this.jsonFilename = jsonFilename;
this.pnpmStore = json.pnpmStore || 'local';
if (EnvironmentConfiguration.pnpmStorePathOverride) {
this.pnpmStorePath = EnvironmentConfiguration.pnpmStorePathOverride;
} else if (rushUserConfiguration?.pnpmStorePath) {
this.pnpmStorePath = rushUserConfiguration.pnpmStorePath;
} else if (this.pnpmStore === 'global') {
this.pnpmStorePath = '';
} else {
Expand Down Expand Up @@ -504,7 +507,8 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
/** @internal */
public static loadFromJsonFileOrThrow(
jsonFilePath: string,
commonTempFolder: string
commonTempFolder: string,
rushUserConfiguration?: RushUserConfiguration
): PnpmOptionsConfiguration {
// TODO: plumb through the terminal
const terminal: Terminal = new Terminal(new ConsoleTerminalProvider());
Expand All @@ -518,15 +522,16 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
jsonFilePath
);
pnpmConfigJson.$schema = pnpmOptionsConfigFile.getSchemaPropertyOriginalValue(pnpmConfigJson);
return new PnpmOptionsConfiguration(pnpmConfigJson || {}, commonTempFolder, jsonFilePath);
return new PnpmOptionsConfiguration(pnpmConfigJson || {}, commonTempFolder, jsonFilePath, rushUserConfiguration);
}

/** @internal */
public static loadFromJsonObject(
json: IPnpmOptionsJson,
commonTempFolder: string
commonTempFolder: string,
rushUserConfiguration?: RushUserConfiguration
): PnpmOptionsConfiguration {
return new PnpmOptionsConfiguration(json, commonTempFolder);
return new PnpmOptionsConfiguration(json, commonTempFolder, undefined, rushUserConfiguration);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import * as path from 'node:path';
import { PnpmOptionsConfiguration } from '../PnpmOptionsConfiguration';
import { TestUtilities } from '@rushstack/heft-config-file';
import { EnvironmentConfiguration } from '../../../api/EnvironmentConfiguration';
import type { RushUserConfiguration } from '../../../api/RushUserConfiguration';

const fakeCommonTempFolder: string = path.join(__dirname, 'common', 'temp');

Expand Down Expand Up @@ -122,4 +124,69 @@ describe(PnpmOptionsConfiguration.name, () => {
}
});
});

it('uses default store path when no user configuration is provided', () => {
const pnpmConfiguration: PnpmOptionsConfiguration = PnpmOptionsConfiguration.loadFromJsonObject(
{},
fakeCommonTempFolder
);

expect(pnpmConfiguration.pnpmStorePath).toEqual(`${fakeCommonTempFolder}/pnpm-store`);
});

it('uses user configuration storePath when provided', () => {
const mockUserConfig: Partial<RushUserConfiguration> = {
buildCacheFolder: undefined,
pnpmStorePath: '/custom/pnpm/store'
};

const pnpmConfiguration: PnpmOptionsConfiguration = PnpmOptionsConfiguration.loadFromJsonObject(
{},
fakeCommonTempFolder,
mockUserConfig as RushUserConfiguration
);

expect(pnpmConfiguration.pnpmStorePath).toEqual('/custom/pnpm/store');
});

it('environment variable takes precedence over user configuration', () => {
// Save original value
const originalEnvValue = process.env.RUSH_PNPM_STORE_PATH;

try {
// Set environment variable with a path that will be normalized
// Use an absolute path like the temp folder
const envStorePath: string = path.join(fakeCommonTempFolder, 'env-pnpm-store');
process.env.RUSH_PNPM_STORE_PATH = envStorePath;

// Re-validate environment configuration to pick up the change
EnvironmentConfiguration.reset();
EnvironmentConfiguration.validate();

const mockUserConfig: Partial<RushUserConfiguration> = {
buildCacheFolder: undefined,
pnpmStorePath: '/custom/pnpm/store'
};

const pnpmConfiguration: PnpmOptionsConfiguration = PnpmOptionsConfiguration.loadFromJsonObject(
{},
fakeCommonTempFolder,
mockUserConfig as RushUserConfiguration
);

// The environment variable value should be used (after normalization)
expect(pnpmConfiguration.pnpmStorePath).toContain('env-pnpm-store');
} finally {
// Restore original value
if (originalEnvValue !== undefined) {
process.env.RUSH_PNPM_STORE_PATH = originalEnvValue;
} else {
delete process.env.RUSH_PNPM_STORE_PATH;
}

// Re-validate to restore original state
EnvironmentConfiguration.reset();
EnvironmentConfiguration.validate();
}
});
});
12 changes: 12 additions & 0 deletions libraries/rush-lib/src/schemas/rush-user-settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@
"buildCacheFolder": {
"type": "string",
"description": "If provided, store build cache in the specified folder. Must be an absolute path."
},

"pnpm": {
"type": "object",
"description": "PNPM-specific user configuration options.",
"properties": {
"storePath": {
"type": "string",
"description": "If provided, specifies the path for the PNPM store directory. Can be overridden by the RUSH_PNPM_STORE_PATH environment variable."
}
},
"additionalProperties": false
}
},
"additionalProperties": false
Expand Down