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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "Add support for pnpm trustPolicy, trustPolicyExclude, and trustPolicyIgnoreAfter settings in pnpm-config.json",
"type": "minor",
"packageName": "@microsoft/rush"
}
],
"packageName": "@microsoft/rush",
"email": "fpapado@users.noreply.github.com"
}
9 changes: 9 additions & 0 deletions common/reviews/api/rush-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,9 @@ export interface _IPnpmOptionsJson extends IPackageManagerOptionsJsonBase {
preventManualShrinkwrapChanges?: boolean;
resolutionMode?: PnpmResolutionMode;
strictPeerDependencies?: boolean;
trustPolicy?: PnpmTrustPolicy;
trustPolicyExclude?: string[];
trustPolicyIgnoreAfter?: number;
unsupportedPackageJsonSettings?: unknown;
useWorkspaces?: boolean;
}
Expand Down Expand Up @@ -1194,6 +1197,9 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
readonly preventManualShrinkwrapChanges: boolean;
readonly resolutionMode: PnpmResolutionMode | undefined;
readonly strictPeerDependencies: boolean;
readonly trustPolicy: PnpmTrustPolicy | undefined;
readonly trustPolicyExclude: string[] | undefined;
readonly trustPolicyIgnoreAfter: number | undefined;
readonly unsupportedPackageJsonSettings: unknown | undefined;
updateGlobalOnlyBuiltDependencies(onlyBuiltDependencies: string[] | undefined): void;
updateGlobalPatchedDependencies(patchedDependencies: Record<string, string> | undefined): void;
Expand All @@ -1209,6 +1215,9 @@ export type PnpmStoreLocation = 'local' | 'global';
// @public @deprecated (undocumented)
export type PnpmStoreOptions = PnpmStoreLocation;

// @public
export type PnpmTrustPolicy = 'no-downgrade' | 'off';

// @beta (undocumented)
export class ProjectChangeAnalyzer {
constructor(rushConfiguration: RushConfiguration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,58 @@
*/
/*[LINE "HYPOTHETICAL"]*/ "minimumReleaseAgeExclude": ["@myorg/*"],

/**
* The trust policy controls whether pnpm should block installation of package versions where
* the trust level has decreased (e.g., a package previously published with provenance is now
* published without it). Setting this to `"no-downgrade"` enables the protection.
*
* (SUPPORTED ONLY IN PNPM 10.21.0 AND NEWER)
*
* PNPM documentation: https://pnpm.io/settings#trustpolicy
*
* Possible values are: `off` and `no-downgrade`.
* The default is `off`.
*/
/*[LINE "HYPOTHETICAL"]*/ "trustPolicy": "no-downgrade",

/**
* An array of package names or patterns to exclude from the trust policy check.
* These packages will be allowed to install even if their trust level has decreased.
* Patterns are supported using glob syntax (e.g., "@myorg/*" to exclude all packages
* from an organization).
*
* For example:
*
* "trustPolicyExclude": ["@babel/core@7.28.5", "chokidar@4.0.3", "@myorg/*"]
*
* (SUPPORTED ONLY IN PNPM 10.22.0 AND NEWER)
*
* PNPM documentation: https://pnpm.io/settings#trustpolicyexclude
*
* The default value is [].
*/
/*[LINE "HYPOTHETICAL"]*/ "trustPolicyExclude": ["@myorg/*"],

/**
* The number of minutes after which pnpm will ignore trust level downgrades. Packages
* published longer ago than this threshold will not be blocked even if their trust level
* has decreased. This is useful when enabling strict trust policies, as it allows older versions
* of packages (which may lack a process for publishing with signatures or provenance) to be
* installed without manual exclusion, assuming they are safe due to their age.
*
* For example, the following setting ignores trust level changes for packages published
* more than 14 days ago:
*
* "trustPolicyIgnoreAfter": 20160
*
* (SUPPORTED ONLY IN PNPM 10.27.0 AND NEWER)
*
* PNPM documentation: https://pnpm.io/settings#trustpolicyignoreafter
*
* The default value is undefined (no exclusion).
*/
/*[LINE "HYPOTHETICAL"]*/ "trustPolicyIgnoreAfter": 20160,

/**
* If true, then Rush will add the `--strict-peer-dependencies` command-line parameter when
* invoking PNPM. This causes `rush update` to fail if there are unsatisfied peer dependencies,
Expand Down
3 changes: 2 additions & 1 deletion libraries/rush-lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export {
type IPnpmPeerDependenciesMeta,
type PnpmStoreOptions,
PnpmOptionsConfiguration,
type PnpmResolutionMode
type PnpmResolutionMode,
type PnpmTrustPolicy
} from './logic/pnpm/PnpmOptionsConfiguration';

export { BuildCacheConfiguration } from './api/BuildCacheConfiguration';
Expand Down
57 changes: 57 additions & 0 deletions libraries/rush-lib/src/logic/installManager/InstallHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ interface ICommonPackageJson extends IPackageJson {
patchedDependencies?: typeof PnpmOptionsConfiguration.prototype.globalPatchedDependencies;
minimumReleaseAge?: typeof PnpmOptionsConfiguration.prototype.minimumReleaseAge;
minimumReleaseAgeExclude?: typeof PnpmOptionsConfiguration.prototype.minimumReleaseAgeExclude;
trustPolicy?: typeof PnpmOptionsConfiguration.prototype.trustPolicy;
trustPolicyExclude?: typeof PnpmOptionsConfiguration.prototype.trustPolicyExclude;
trustPolicyIgnoreAfter?: typeof PnpmOptionsConfiguration.prototype.trustPolicyIgnoreAfter;
};
}

Expand Down Expand Up @@ -145,6 +148,60 @@ export class InstallHelpers {
}
}

if (pnpmOptions.trustPolicy !== undefined) {
if (
rushConfiguration.rushConfigurationJson.pnpmVersion !== undefined &&
semver.lt(rushConfiguration.rushConfigurationJson.pnpmVersion, '10.21.0')
) {
terminal.writeWarningLine(
Colorize.yellow(
`Your version of pnpm (${rushConfiguration.rushConfigurationJson.pnpmVersion}) ` +
`doesn't support the "trustPolicy" field in ` +
`${rushConfiguration.commonRushConfigFolder}/${RushConstants.pnpmConfigFilename}. ` +
'Remove this field or upgrade to pnpm 10.21.0 or newer.'
)
);
}

commonPackageJson.pnpm.trustPolicy = pnpmOptions.trustPolicy;
}

if (pnpmOptions.trustPolicyExclude) {
if (
rushConfiguration.rushConfigurationJson.pnpmVersion !== undefined &&
semver.lt(rushConfiguration.rushConfigurationJson.pnpmVersion, '10.22.0')
) {
terminal.writeWarningLine(
Colorize.yellow(
`Your version of pnpm (${rushConfiguration.rushConfigurationJson.pnpmVersion}) ` +
`doesn't support the "trustPolicyExclude" field in ` +
`${rushConfiguration.commonRushConfigFolder}/${RushConstants.pnpmConfigFilename}. ` +
'Remove this field or upgrade to pnpm 10.22.0 or newer.'
)
);
}

commonPackageJson.pnpm.trustPolicyExclude = pnpmOptions.trustPolicyExclude;
}

if (pnpmOptions.trustPolicyIgnoreAfter !== undefined) {
if (
rushConfiguration.rushConfigurationJson.pnpmVersion !== undefined &&
semver.lt(rushConfiguration.rushConfigurationJson.pnpmVersion, '10.27.0')
) {
terminal.writeWarningLine(
Colorize.yellow(
`Your version of pnpm (${rushConfiguration.rushConfigurationJson.pnpmVersion}) ` +
`doesn't support the "trustPolicyIgnoreAfter" field in ` +
`${rushConfiguration.commonRushConfigFolder}/${RushConstants.pnpmConfigFilename}. ` +
'Remove this field or upgrade to pnpm 10.27.0 or newer.'
)
);
}

commonPackageJson.pnpm.trustPolicyIgnoreAfter = pnpmOptions.trustPolicyIgnoreAfter;
}

if (pnpmOptions.unsupportedPackageJsonSettings) {
merge(commonPackageJson, pnpmOptions.unsupportedPackageJsonSettings);
}
Expand Down
61 changes: 61 additions & 0 deletions libraries/rush-lib/src/logic/pnpm/PnpmOptionsConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ export type PnpmStoreOptions = PnpmStoreLocation;
*/
export type PnpmResolutionMode = 'highest' | 'time-based' | 'lowest-direct';

/**
* Possible values for the `trustPolicy` setting in Rush's pnpm-config.json file.
* @remarks
* These values correspond to PNPM's `trust-policy` setting, which is documented here:
* {@link https://pnpm.io/settings#trustpolicy}
*
* @public
*/
export type PnpmTrustPolicy = 'no-downgrade' | 'off';

/**
* Possible values for the `pnpmLockfilePolicies` setting in Rush's pnpm-config.json file.
* @public
Expand Down Expand Up @@ -150,6 +160,18 @@ export interface IPnpmOptionsJson extends IPackageManagerOptionsJsonBase {
* {@inheritDoc PnpmOptionsConfiguration.minimumReleaseAgeExclude}
*/
minimumReleaseAgeExclude?: string[];
/**
* {@inheritDoc PnpmOptionsConfiguration.trustPolicy}
*/
trustPolicy?: PnpmTrustPolicy;
/**
* {@inheritDoc PnpmOptionsConfiguration.trustPolicyExclude}
*/
trustPolicyExclude?: string[];
/**
* {@inheritDoc PnpmOptionsConfiguration.trustPolicyIgnoreAfter}
*/
trustPolicyIgnoreAfter?: number;
/**
* {@inheritDoc PnpmOptionsConfiguration.alwaysInjectDependenciesFromOtherSubspaces}
*/
Expand Down Expand Up @@ -301,6 +323,42 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
*/
public readonly minimumReleaseAgeExclude: string[] | undefined;

/**
* The trust policy controls whether pnpm should block installation of package versions where the
* trust level has decreased (e.g., a package previously published with provenance is now published
* without it). Setting this to `"no-downgrade"` enables the protection.
*
* @remarks
* (SUPPORTED ONLY IN PNPM 10.21.0 AND NEWER)
*
* PNPM documentation: https://pnpm.io/settings#trustpolicy
*/
public readonly trustPolicy: PnpmTrustPolicy | undefined;

/**
* List of package names or patterns that are excluded from the trust policy check.
* These packages will be allowed to install even if their trust level has decreased.
*
* @remarks
* (SUPPORTED ONLY IN PNPM 10.22.0 AND NEWER)
*
* PNPM documentation: https://pnpm.io/settings#trustpolicyexclude
*
* Example: ["webpack", "react", "\@myorg/*"]
*/
public readonly trustPolicyExclude: string[] | undefined;

/**
* The number of minutes after which pnpm will ignore trust level downgrades. Packages published
* longer ago than this threshold will not be blocked even if their trust level has decreased.
*
* @remarks
* (SUPPORTED ONLY IN PNPM 10.27.0 AND NEWER)
*
* PNPM documentation: https://pnpm.io/settings#trustpolicyignoreafter
*/
public readonly trustPolicyIgnoreAfter: number | undefined;

/**
* If true, then `rush update` add injected install options for all cross-subspace
* workspace dependencies, to avoid subspace doppelganger issue.
Expand Down Expand Up @@ -495,6 +553,9 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
this.autoInstallPeers = json.autoInstallPeers;
this.minimumReleaseAge = json.minimumReleaseAge;
this.minimumReleaseAgeExclude = json.minimumReleaseAgeExclude;
this.trustPolicy = json.trustPolicy;
this.trustPolicyExclude = json.trustPolicyExclude;
this.trustPolicyIgnoreAfter = json.trustPolicyIgnoreAfter;
this.alwaysInjectDependenciesFromOtherSubspaces = json.alwaysInjectDependenciesFromOtherSubspaces;
this.alwaysFullInstall = json.alwaysFullInstall;
this.pnpmLockfilePolicies = json.pnpmLockfilePolicies;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ describe(PnpmOptionsConfiguration.name, () => {
]);
});

it('loads trustPolicy', () => {
const pnpmConfiguration: PnpmOptionsConfiguration = PnpmOptionsConfiguration.loadFromJsonFileOrThrow(
`${__dirname}/jsonFiles/pnpm-config-trustPolicy.json`,
fakeCommonTempFolder
);

expect(pnpmConfiguration.trustPolicy).toEqual('no-downgrade');
expect(TestUtilities.stripAnnotations(pnpmConfiguration.trustPolicyExclude)).toEqual([
'@myorg/*',
'chokidar@4.0.3',
'webpack@4.47.0 || 5.102.1',
'@babel/core@7.28.5'
]);
expect(pnpmConfiguration.trustPolicyIgnoreAfter).toEqual(20160);
});

it('loads catalog and catalogs', () => {
const pnpmConfiguration: PnpmOptionsConfiguration = PnpmOptionsConfiguration.loadFromJsonFileOrThrow(
`${__dirname}/jsonFiles/pnpm-config-catalog.json`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"trustPolicy": "no-downgrade",
"trustPolicyExclude": ["@myorg/*", "chokidar@4.0.3", "webpack@4.47.0 || 5.102.1", "@babel/core@7.28.5"],
"trustPolicyIgnoreAfter": 20160
}
20 changes: 20 additions & 0 deletions libraries/rush-lib/src/schemas/pnpm-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,26 @@
}
},

"trustPolicy": {
"description": "The trust policy controls whether pnpm should block installation of package versions where the trust level has decreased (e.g., a package previously published with provenance is now published without it). Setting this to \"no-downgrade\" enables the protection.\n\n(SUPPORTED ONLY IN PNPM 10.21.0 AND NEWER)\n\nPNPM documentation: https://pnpm.io/settings#trustpolicy",
"type": "string",
"enum": ["no-downgrade", "off"]
},

"trustPolicyExclude": {
"description": "List of package names or patterns that are excluded from the trust policy check. These packages will be allowed to install even if their trust level has decreased. Supports glob patterns (e.g., \"@myorg/*\").\n\n(SUPPORTED ONLY IN PNPM 10.22.0 AND NEWER)\n\nPNPM documentation: https://pnpm.io/settings#trustpolicyexclude\n\nExample: [\"webpack\", \"react\", \"@myorg/*\"]",
"type": "array",
"items": {
"description": "Package name or pattern",
"type": "string"
}
},

"trustPolicyIgnoreAfter": {
"description": "The number of minutes after which pnpm will ignore trust level downgrades. Packages published longer ago than this threshold will not be blocked even if their trust level has decreased.\n\n(SUPPORTED ONLY IN PNPM 10.27.0 AND NEWER)\n\nPNPM documentation: https://pnpm.io/settings#trustpolicyignoreafter",
"type": "number"
},

"alwaysFullInstall": {
"description": "(EXPERIMENTAL) If 'true', then filtered installs ('rush install --to my-project') * will be disregarded, instead always performing a full installation of the lockfile.",
"type": "boolean"
Expand Down