Skip to content

Commit

Permalink
Merge pull request #4453 from g-chao/chao/support-injected-settings
Browse files Browse the repository at this point in the history
[rush-lib] Fix an issue where rush update can not handle dependenciesMeta in pnpm
  • Loading branch information
octogonz authored Jan 23, 2024
2 parents 966d679 + 45c2120 commit 4f84967
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Fix an issue where Rush does not detect changes to the `dependenciesMeta` field in project's `package.json` files, so may incorrectly skip updating/installation.",
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/node-core-library",
"comment": "Add the `dependenciesMeta` property to the `INodePackageJson` interface.",
"type": "minor"
}
],
"packageName": "@rushstack/node-core-library"
}
9 changes: 9 additions & 0 deletions common/reviews/api/node-core-library.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,14 @@ export interface IConsoleTerminalProviderOptions {
verboseEnabled: boolean;
}

// @public
export interface IDependenciesMetaTable {
// (undocumented)
[dependencyName: string]: {
injected?: boolean;
};
}

// @beta
export interface IDynamicPrefixProxyTerminalProviderOptions extends IPrefixProxyTerminalProviderOptionsBase {
getPrefix: () => string;
Expand Down Expand Up @@ -552,6 +560,7 @@ export class Import {
export interface INodePackageJson {
bin?: string;
dependencies?: IPackageJsonDependencyTable;
dependenciesMeta?: IDependenciesMetaTable;
description?: string;
devDependencies?: IPackageJsonDependencyTable;
homepage?: string;
Expand Down
10 changes: 10 additions & 0 deletions common/reviews/api/rush-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -903,13 +903,23 @@ export class PackageJsonDependency {
get version(): string;
}

// @public (undocumented)
export class PackageJsonDependencyMeta {
constructor(name: string, injected: boolean, onChange: () => void);
// (undocumented)
get injected(): boolean;
// (undocumented)
readonly name: string;
}

// @public (undocumented)
export class PackageJsonEditor {
// @internal
protected constructor(filepath: string, data: IPackageJson);
// (undocumented)
addOrUpdateDependency(packageName: string, newVersion: string, dependencyType: DependencyType): void;
get dependencyList(): ReadonlyArray<PackageJsonDependency>;
get dependencyMetaList(): ReadonlyArray<PackageJsonDependencyMeta>;
get devDependencyList(): ReadonlyArray<PackageJsonDependency>;
// (undocumented)
readonly filePath: string;
Expand Down
17 changes: 17 additions & 0 deletions libraries/node-core-library/src/IPackageJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ export interface IPeerDependenciesMetaTable {
};
}

/**
* This interface is part of the {@link IPackageJson} file format. It is used for the
* "dependenciesMeta" field.
* @public
*/
export interface IDependenciesMetaTable {
[dependencyName: string]: {
injected?: boolean;
};
}

/**
* An interface for accessing common fields from a package.json file whose version field may be missing.
*
Expand Down Expand Up @@ -166,6 +177,12 @@ export interface INodePackageJson {
*/
peerDependencies?: IPackageJsonDependencyTable;

/**
* An array of metadata for dependencies declared inside dependencies, optionalDependencies, and devDependencies.
* https://pnpm.io/package_json#dependenciesmeta
*/
dependenciesMeta?: IDependenciesMetaTable;

/**
* An array of metadata about peer dependencies.
*/
Expand Down
3 changes: 2 additions & 1 deletion libraries/node-core-library/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export {
IPackageJsonDependencyTable,
IPackageJsonScriptTable,
IPackageJsonRepository,
IPeerDependenciesMetaTable
IPeerDependenciesMetaTable,
IDependenciesMetaTable
} from './IPackageJson';
export {
Import,
Expand Down
39 changes: 39 additions & 0 deletions libraries/rush-lib/src/api/PackageJsonEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@ export class PackageJsonDependency {
}
}

/**
* @public
*/
export class PackageJsonDependencyMeta {
private _injected: boolean;
private _onChange: () => void;

public readonly name: string;

public constructor(name: string, injected: boolean, onChange: () => void) {
this.name = name;
this._injected = injected;
this._onChange = onChange;
}

public get injected(): boolean {
return this._injected;
}
}

/**
* @public
*/
Expand All @@ -57,6 +77,8 @@ export class PackageJsonEditor {
// and "peerDependencies" are mutually exclusive, but "devDependencies" is not.
private readonly _devDependencies: Map<string, PackageJsonDependency>;

private readonly _dependenciesMeta: Map<string, PackageJsonDependencyMeta>;

// NOTE: The "resolutions" field is a yarn specific feature that controls package
// resolution override within yarn.
private readonly _resolutions: Map<string, PackageJsonDependency>;
Expand All @@ -76,6 +98,7 @@ export class PackageJsonEditor {
this._dependencies = new Map<string, PackageJsonDependency>();
this._devDependencies = new Map<string, PackageJsonDependency>();
this._resolutions = new Map<string, PackageJsonDependency>();
this._dependenciesMeta = new Map<string, PackageJsonDependencyMeta>();

const dependencies: { [key: string]: string } = data.dependencies || {};
const optionalDependencies: { [key: string]: string } = data.optionalDependencies || {};
Expand All @@ -84,6 +107,8 @@ export class PackageJsonEditor {
const devDependencies: { [key: string]: string } = data.devDependencies || {};
const resolutions: { [key: string]: string } = data.resolutions || {};

const dependenciesMeta: { [key: string]: { [key: string]: boolean } } = data.dependenciesMeta || {};

const _onChange: () => void = this._onChange.bind(this);

try {
Expand Down Expand Up @@ -155,6 +180,13 @@ export class PackageJsonEditor {
);
});

Object.keys(dependenciesMeta || {}).forEach((packageName: string) => {
this._dependenciesMeta.set(
packageName,
new PackageJsonDependencyMeta(packageName, dependenciesMeta[packageName].injected, _onChange)
);
});

// (Do not sort this._resolutions because order may be significant; the RFC is unclear about that.)
Sort.sortMapKeys(this._dependencies);
Sort.sortMapKeys(this._devDependencies);
Expand Down Expand Up @@ -193,6 +225,13 @@ export class PackageJsonEditor {
return [...this._devDependencies.values()];
}

/**
* The list of dependenciesMeta in package.json.
*/
public get dependencyMetaList(): ReadonlyArray<PackageJsonDependencyMeta> {
return [...this._dependenciesMeta.values()];
}

/**
* This field is a Yarn-specific feature that allows overriding of package resolution.
*
Expand Down
7 changes: 6 additions & 1 deletion libraries/rush-lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ export { ApprovedPackagesItem, ApprovedPackagesConfiguration } from './api/Appro

export { CommonVersionsConfiguration } from './api/CommonVersionsConfiguration';

export { PackageJsonEditor, PackageJsonDependency, DependencyType } from './api/PackageJsonEditor';
export {
PackageJsonEditor,
PackageJsonDependency,
DependencyType,
PackageJsonDependencyMeta
} from './api/PackageJsonEditor';

export { RepoStateFile } from './logic/RepoStateFile';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@
import colors from 'colors/safe';
import * as path from 'path';
import * as semver from 'semver';
import { FileSystem, FileConstants, AlreadyReportedError, Async } from '@rushstack/node-core-library';
import {
FileSystem,
FileConstants,
AlreadyReportedError,
Async,
type IDependenciesMetaTable
} from '@rushstack/node-core-library';

import { BaseInstallManager } from '../base/BaseInstallManager';
import type { IInstallManagerOptions } from '../base/BaseInstallManagerTypes';
import type { BaseShrinkwrapFile } from '../../logic/base/BaseShrinkwrapFile';
import { DependencySpecifier, DependencySpecifierType } from '../DependencySpecifier';
import { type PackageJsonEditor, DependencyType } from '../../api/PackageJsonEditor';
import {
type PackageJsonEditor,
DependencyType,
type PackageJsonDependencyMeta
} from '../../api/PackageJsonEditor';
import { PnpmWorkspaceFile } from '../pnpm/PnpmWorkspaceFile';
import type { RushConfigurationProject } from '../../api/RushConfigurationProject';
import { RushConstants } from '../../logic/RushConstants';
Expand All @@ -23,6 +33,8 @@ import { EnvironmentConfiguration } from '../../api/EnvironmentConfiguration';
import { ShrinkwrapFileFactory } from '../ShrinkwrapFileFactory';
import { BaseProjectShrinkwrapFile } from '../base/BaseProjectShrinkwrapFile';
import { type CustomTipId, type ICustomTipInfo, PNPM_CUSTOM_TIPS } from '../../api/CustomTipsConfiguration';
import type { PnpmShrinkwrapFile } from '../pnpm/PnpmShrinkwrapFile';
import { objectsAreDeepEqual } from '../../utilities/objectUtilities';

/**
* This class implements common logic between "rush install" and "rush update".
Expand Down Expand Up @@ -56,7 +68,7 @@ export class WorkspaceInstallManager extends BaseInstallManager {
* @override
*/
protected async prepareCommonTempAsync(
shrinkwrapFile: BaseShrinkwrapFile | undefined
shrinkwrapFile: (PnpmShrinkwrapFile & BaseShrinkwrapFile) | undefined
): Promise<{ shrinkwrapIsUpToDate: boolean; shrinkwrapWarnings: string[] }> {
// Block use of the RUSH_TEMP_FOLDER environment variable
if (EnvironmentConfiguration.rushTempFolderOverride !== undefined) {
Expand Down Expand Up @@ -235,6 +247,56 @@ export class WorkspaceInstallManager extends BaseInstallManager {
}
}

// For pnpm pacakge manager, we need to handle dependenciesMeta changes in package.json. See more: https://pnpm.io/package_json#dependenciesmeta
// If dependenciesMeta settings is different between package.json and pnpm-lock.yaml, then shrinkwrapIsUpToDate return false.
if (this.rushConfiguration.packageManager === 'pnpm') {
// First, build a object for dependenciesMeta settings in package.json
// key is the package path, value is the dependenciesMeta info for that package
const packagePathToDependenciesMetaInPackageJson: { [key: string]: IDependenciesMetaTable } = {};
const commonTempFolder: string = this.rushConfiguration.commonTempFolder;
const rushJsonFolder: string = this.rushConfiguration.rushJsonFolder;

// get the relative path from common temp folder to repo root folder
const relativeFromTempFolderToRootFolder: string = path.relative(commonTempFolder, rushJsonFolder);
for (const rushProject of this.rushConfiguration.projects) {
const packageJson: PackageJsonEditor = rushProject.packageJsonEditor;
const projectRelativeFolder: string = rushProject.projectRelativeFolder;

// get the relative path from common temp folder to package folder, to align with the value in pnpm-lock.yaml
const relativePathFromTempFolderToPackageFolder: string =
relativeFromTempFolderToRootFolder + '/' + projectRelativeFolder;
const dependencyMetaList: ReadonlyArray<PackageJsonDependencyMeta> = packageJson.dependencyMetaList;

if (dependencyMetaList.length !== 0) {
const dependenciesMeta: IDependenciesMetaTable = {};
for (const dependencyMeta of dependencyMetaList) {
dependenciesMeta[dependencyMeta.name] = {
injected: dependencyMeta.injected
};
}
packagePathToDependenciesMetaInPackageJson[relativePathFromTempFolderToPackageFolder] =
dependenciesMeta;
}
}

// Second, build a object for dependenciesMeta settings in pnpm-lock.yaml
// key is the package path, value is the dependenciesMeta info for that package
const packagePathToDependenciesMetaInShrinkwrapFile: { [key: string]: IDependenciesMetaTable } = {};
if (shrinkwrapFile?.importers !== undefined) {
for (const [key, value] of shrinkwrapFile?.importers) {
if (value.dependenciesMeta !== undefined) {
packagePathToDependenciesMetaInShrinkwrapFile[key] = value.dependenciesMeta;
}
}
}

// Now, we compare these two objects to see if they are equal or not
shrinkwrapIsUpToDate = objectsAreDeepEqual(
packagePathToDependenciesMetaInPackageJson,
packagePathToDependenciesMetaInShrinkwrapFile
);
}

// Write the common package.json
InstallHelpers.generateCommonPackageJson(this.rushConfiguration);

Expand Down
5 changes: 5 additions & 0 deletions libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const yamlModule: typeof import('js-yaml') = Import.lazy('js-yaml', require);
export interface IPeerDependenciesMetaYaml {
optional?: boolean;
}
export interface IDependenciesMetaYaml {
injected?: boolean;
}

export type IPnpmV7VersionSpecifier = string;
export interface IPnpmV8VersionSpecifier {
Expand Down Expand Up @@ -69,6 +72,8 @@ export interface IPnpmShrinkwrapImporterYaml {
devDependencies?: Record<string, IPnpmVersionSpecifier>;
/** The list of resolved version numbers for optional dependencies */
optionalDependencies?: Record<string, IPnpmVersionSpecifier>;
/** The list of metadata for dependencies declared inside dependencies, optionalDependencies, and devDependencies. */
dependenciesMeta?: Record<string, IDependenciesMetaYaml>;
/**
* The list of specifiers used to resolve dependency versions
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Loaded @microsoft/rush-lib from process.env._RUSH_LIB_PATH
'Operation',
'OperationStatus',
'PackageJsonDependency',
'PackageJsonDependencyMeta',
'PackageJsonEditor',
'PackageManager',
'PackageManagerOptionsConfigurationBase',
Expand Down

0 comments on commit 4f84967

Please sign in to comment.