Skip to content
Merged
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/main_2025-10-03-18-53.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Fix an issue where the `$schema` property is dropped from `common/config/rush/pnpm-config.json` when running `rush-pnpm patch-commit ...`",
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/heft-config-file",
"comment": "Add the ability to get the original value of the `$schema` property.",
"type": "minor"
}
],
"packageName": "@rushstack/heft-config-file"
}
1 change: 1 addition & 0 deletions common/reviews/api/heft-config-file.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export abstract class ConfigurationFileBase<TConfigurationFile, TExtraOptions ex
static _formatPathForLogging: (path: string) => string;
getObjectSourceFilePath<TObject extends object>(obj: TObject): string | undefined;
getPropertyOriginalValue<TParentProperty extends object, TValue>(options: IOriginalValueOptions<TParentProperty>): TValue | undefined;
getSchemaPropertyOriginalValue<TObject extends object>(obj: TObject): string | undefined;
// (undocumented)
protected _loadConfigurationFileInnerWithCache(terminal: ITerminal, resolvedConfigurationFilePath: string, projectFolderPath: string | undefined, onConfigurationFileNotFound?: IOnConfigurationFileNotFoundCallback): TConfigurationFile;
// (undocumented)
Expand Down
4 changes: 3 additions & 1 deletion common/reviews/api/rush-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,8 @@ export interface IPnpmLockfilePolicies {

// @internal
export interface _IPnpmOptionsJson extends IPackageManagerOptionsJsonBase {
// (undocumented)
$schema?: string;
alwaysFullInstall?: boolean;
alwaysInjectDependenciesFromOtherSubspaces?: boolean;
autoInstallPeers?: boolean;
Expand Down Expand Up @@ -1183,7 +1185,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
// (undocumented)
readonly jsonFilename: string | undefined;
// @internal (undocumented)
static loadFromJsonFileOrThrow(jsonFilename: string, commonTempFolder: string): PnpmOptionsConfiguration;
static loadFromJsonFileOrThrow(jsonFilePath: string, commonTempFolder: string): PnpmOptionsConfiguration;
// @internal (undocumented)
static loadFromJsonObject(json: _IPnpmOptionsJson, commonTempFolder: string): PnpmOptionsConfiguration;
readonly pnpmLockfilePolicies: IPnpmLockfilePolicies | undefined;
Expand Down
60 changes: 42 additions & 18 deletions libraries/heft-config-file/src/ConfigurationFileBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,32 @@ export const CONFIGURATION_FILE_FIELD_ANNOTATION: unique symbol = Symbol(
'configuration-file-field-annotation'
);

export interface IAnnotatedField<TField> {
[CONFIGURATION_FILE_FIELD_ANNOTATION]: IConfigurationFileFieldAnnotation<TField>;
export interface IAnnotatedField<
TField,
TConfigurationFileFieldAnnotation extends
IConfigurationFileFieldAnnotation<TField> = IConfigurationFileFieldAnnotation<TField>
> {
[CONFIGURATION_FILE_FIELD_ANNOTATION]: TConfigurationFileFieldAnnotation;
}

type IAnnotatedObject<TParentProperty> = Partial<IAnnotatedField<TParentProperty>>;
type IRootAnnotatedObject<TParentProperty> = Partial<
IAnnotatedField<TParentProperty, IRootConfigurationFileFieldAnnotation<TParentProperty>>
>;

interface IConfigurationFileFieldAnnotation<TField> {
configurationFilePath: string | undefined;
originalValues: { [propertyName in keyof TField]: unknown };
}

interface IRootConfigurationFileFieldAnnotation<TField> extends IConfigurationFileFieldAnnotation<TField> {
schemaPropertyOriginalValue?: string;
}

interface IObjectWithSchema {
$schema?: string;
}

/**
* Options provided to the custom resolver specified in {@link ICustomJsonPathMetadata}.
*
Expand Down Expand Up @@ -453,15 +470,8 @@ export abstract class ConfigurationFileBase<TConfigurationFile, TExtraOptions ex
* loaded from.
*/
public getObjectSourceFilePath<TObject extends object>(obj: TObject): string | undefined {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const annotation: IConfigurationFileFieldAnnotation<TObject> | undefined = (obj as any)[
CONFIGURATION_FILE_FIELD_ANNOTATION
];
if (annotation) {
return annotation.configurationFilePath;
}

return undefined;
const { [CONFIGURATION_FILE_FIELD_ANNOTATION]: annotation }: IAnnotatedObject<TObject> = obj;
return annotation?.configurationFilePath;
}

/**
Expand All @@ -471,15 +481,22 @@ export abstract class ConfigurationFileBase<TConfigurationFile, TExtraOptions ex
public getPropertyOriginalValue<TParentProperty extends object, TValue>(
options: IOriginalValueOptions<TParentProperty>
): TValue | undefined {
const {
[CONFIGURATION_FILE_FIELD_ANNOTATION]: annotation
}: { [CONFIGURATION_FILE_FIELD_ANNOTATION]?: IConfigurationFileFieldAnnotation<TParentProperty> } =
options.parentObject;
if (annotation?.originalValues.hasOwnProperty(options.propertyName)) {
return annotation.originalValues[options.propertyName] as TValue;
const { parentObject, propertyName } = options;
const { [CONFIGURATION_FILE_FIELD_ANNOTATION]: annotation }: IAnnotatedObject<TParentProperty> =
parentObject;
if (annotation?.originalValues.hasOwnProperty(propertyName)) {
return annotation.originalValues[propertyName] as TValue;
}
}

/**
* Get the original value of the `$schema` property from the original configuration file, it one was present.
*/
public getSchemaPropertyOriginalValue<TObject extends object>(obj: TObject): string | undefined {
const { [CONFIGURATION_FILE_FIELD_ANNOTATION]: annotation }: IRootAnnotatedObject<TObject> = obj;
return annotation?.schemaPropertyOriginalValue;
}

protected _loadConfigurationFileInnerWithCache(
terminal: ITerminal,
resolvedConfigurationFilePath: string,
Expand Down Expand Up @@ -979,14 +996,21 @@ export abstract class ConfigurationFileBase<TConfigurationFile, TExtraOptions ex

// Need to do a dance with the casting here because while we know that JSON keys are always
// strings, TypeScript doesn't.
return this._mergeObjects(
const result: Partial<TConfigurationFile> = this._mergeObjects(
parentConfiguration as { [key: string]: unknown },
configurationJson as { [key: string]: unknown },
resolvedConfigurationFilePath,
this._defaultPropertyInheritance,
this._propertyInheritanceTypes as IPropertiesInheritance<{ [key: string]: unknown }>,
ignoreProperties
) as Partial<TConfigurationFile>;

const schemaPropertyOriginalValue: string | undefined = (configurationJson as IObjectWithSchema).$schema;
(result as unknown as Required<IRootAnnotatedObject<{}>>)[
CONFIGURATION_FILE_FIELD_ANNOTATION
].schemaPropertyOriginalValue = schemaPropertyOriginalValue;

return result;
}

private _mergeObjects<TField extends { [key: string]: unknown }>(
Expand Down
Loading
Loading