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
10 changes: 10 additions & 0 deletions common/changes/@microsoft/rush/fix-5602_2026-02-07-17-45.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Fix allPreferredVersions and allowedAlternativeVersions missing in subspace pnpmfileSettings.json",
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
3 changes: 1 addition & 2 deletions libraries/rush-lib/src/logic/pnpm/IPnpmfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ export interface IWorkspaceProjectInfo
* The `settings` parameter passed to {@link IPnpmfileShim.hooks.readPackage} and
* {@link IPnpmfileShim.hooks.afterAllResolved}.
*/
export interface ISubspacePnpmfileShimSettings {
semverPath: string;
export interface ISubspacePnpmfileShimSettings extends Omit<IPnpmfileShimSettings, 'workspaceVersions'> {
workspaceProjects: Record<string, IWorkspaceProjectInfo>;
subspaceProjects: Record<string, IWorkspaceProjectInfo>;
userPnpmfilePath?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@

import * as path from 'node:path';

import { FileSystem, Import, JsonFile, type IDependenciesMetaTable } from '@rushstack/node-core-library';
import * as semver from 'semver';

import {
FileSystem,
Import,
JsonFile,
MapExtensions,
type IDependenciesMetaTable
} from '@rushstack/node-core-library';

import { subspacePnpmfileShimFilename, scriptsFolderPath } from '../../utilities/PathConstants';
import type { ISubspacePnpmfileShimSettings, IWorkspaceProjectInfo } from './IPnpmfile';
import type { RushConfiguration } from '../../api/RushConfiguration';
import type { RushConfigurationProject } from '../../api/RushConfigurationProject';
import type { PnpmPackageManager } from '../../api/packageManager/PnpmPackageManager';
import type { CommonVersionsConfiguration } from '../../api/CommonVersionsConfiguration';
import { RushConstants } from '../RushConstants';
import type { Subspace } from '../../api/Subspace';
import type { PnpmOptionsConfiguration } from './PnpmOptionsConfiguration';
Expand Down Expand Up @@ -80,9 +89,35 @@ export class SubspacePnpmfileConfiguration {
(subspace.contains(project) ? subspaceProjects : workspaceProjects)[packageName] = workspaceProjectInfo;
}

let allPreferredVersions: { [dependencyName: string]: string } = {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is SubspacePnpmfileConfiguration distinct from https://github.com/microsoft/rushstack/blob/main/libraries/rush-lib/src/logic/pnpm/PnpmfileConfiguration.ts? Looks like a lot of this is already in there. Can these two be largely deduplicated?

let allowedAlternativeVersions: { [dependencyName: string]: readonly string[] } = {};

// Populate preferred versions from subspace's common-versions.json (same as non-subspace pnpmfile shim)
const pnpmOptions: PnpmOptionsConfiguration =
rushConfiguration.packageManagerOptions as PnpmOptionsConfiguration;
if (pnpmOptions?.useWorkspaces) {
const commonVersionsConfiguration: CommonVersionsConfiguration = subspace.getCommonVersions(variant);
const preferredVersions: Map<string, string> = new Map();
MapExtensions.mergeFromMap(
preferredVersions,
rushConfiguration.getImplicitlyPreferredVersions(subspace, variant)
);
for (const [name, version] of commonVersionsConfiguration.getAllPreferredVersions()) {
if (!preferredVersions.has(name) || semver.subset(version, preferredVersions.get(name)!)) {
preferredVersions.set(name, version);
}
}
allPreferredVersions = MapExtensions.toObject(preferredVersions);
allowedAlternativeVersions = MapExtensions.toObject(
commonVersionsConfiguration.allowedAlternativeVersions
);
}

const settings: ISubspacePnpmfileShimSettings = {
workspaceProjects,
subspaceProjects,
allPreferredVersions,
allowedAlternativeVersions,
semverPath: Import.resolveModule({ modulePath: 'semver', baseFolderPath: __dirname })
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import { RushConfiguration } from '../../../api/RushConfiguration';
import { SubspacePnpmfileConfiguration } from '../SubspacePnpmfileConfiguration';
import { JsonFile, type JsonObject } from '@rushstack/node-core-library';

describe(SubspacePnpmfileConfiguration.name, () => {
const repoPath: string = `${__dirname}/repo-with-subspace`;
const rushFilename: string = `${repoPath}/rush.json`;
const rushConfiguration: RushConfiguration = RushConfiguration.loadFromConfigurationFile(rushFilename);
const shimPath: string = `${rushConfiguration.defaultSubspace.getSubspaceTempFolderPath()}/pnpmfileSettings.json`;

beforeAll(async () => {
const subspace = rushConfiguration.defaultSubspace;
await SubspacePnpmfileConfiguration.writeCommonTempSubspaceGlobalPnpmfileAsync(
rushConfiguration,
subspace,
undefined
);
});

it('should use the smallest-available SemVer range (preferredVersions)', async () => {
const shimJson: JsonObject = await JsonFile.loadAsync(shimPath);
expect(shimJson.allPreferredVersions).toHaveProperty('@rushstack/terminal', '0.19.2');
});

it('should record allPreferredVersions in pnpmfileSettings.json', async () => {
const shimJson: JsonObject = await JsonFile.loadAsync(shimPath);
expect(shimJson.allPreferredVersions).toHaveProperty('@rushstack/terminal', '0.19.2');
});

it('should record allowedAlternativeVersions in pnpmfileSettings.json', async () => {
const shimJson: JsonObject = await JsonFile.loadAsync(shimPath);
const allowedAlternativeVersions = shimJson.allowedAlternativeVersions as
| Record<string, readonly string[]>
| undefined;
expect(allowedAlternativeVersions).toBeDefined();
expect(allowedAlternativeVersions).toHaveProperty('foo', ['1.0.0']);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "a",
"version": "1.0.0",
"description": "Test package a to test subspace pnpmfile shim with preferred versions",
"dependencies": {
"@rushstack/terminal": "~0.19.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"useWorkspaces": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* This configuration file manages the experimental "subspaces" feature for Rush,
* which allows multiple PNPM lockfiles to be used in a single Rush workspace.
* For full documentation, please see https://rushjs.io
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/subspaces.schema.json",
"subspacesEnabled": true,
"subspaceNames": ["default"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
registry=https://registry.npmjs.org/
always-auth=false
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';

/**
* When using the PNPM package manager, you can use pnpmfile.js to workaround
* dependencies that have mistakes in their package.json file. (This feature is
* functionally similar to Yarn's "resolutions".)
*
* For details, see the PNPM documentation:
* https://pnpm.io/pnpmfile#hooks
*
* IMPORTANT: SINCE THIS FILE CONTAINS EXECUTABLE CODE, MODIFYING IT IS LIKELY TO INVALIDATE
* ANY CACHED DEPENDENCY ANALYSIS. After any modification to pnpmfile.js, it's recommended to run
* "rush update --full" so that PNPM will recalculate all version selections.
*/
module.exports = {
hooks: {
readPackage
}
};

function readPackage(packageJson, _context) {
return packageJson;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* This configuration file specifies NPM dependency version selections that affect all projects
* in a Rush repo. More documentation is available on the Rush website: https://rushjs.io
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json",

/**
* A table that specifies a "preferred version" for a given NPM package. This feature is typically used
* to hold back an indirect dependency to a specific older version, or to reduce duplication of indirect dependencies.
*
* The "preferredVersions" value can be any SemVer range specifier (e.g. "~1.2.3"). Rush injects these values into
* the "dependencies" field of the top-level common/temp/package.json, which influences how the package manager
* will calculate versions. The specific effect depends on your package manager. Generally it will have no
* effect on an incompatible or already constrained SemVer range. If you are using PNPM, similar effects can be
* achieved using the pnpmfile.js hook. See the Rush documentation for more details.
*
* After modifying this field, it's recommended to run "rush update --full" so that the package manager
* will recalculate all version selections.
*/
"preferredVersions": {
"@rushstack/terminal": "0.19.2"
},
"ensureConsistentVersions": false,
"allowedAlternativeVersions": {
"foo": ["1.0.0"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* This is the main configuration file for Rush.
* For full documentation, please see https://rushjs.io
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json",
"rushVersion": "5.166.0",
"pnpmVersion": "10.27.0",
"nodeSupportedVersionRange": "*",
// "projectFolderMinDepth": 1,
// "projectFolderMaxDepth": 2,
"projects": [
{
"packageName": "a",
"projectFolder": "a"
}
]
}