Skip to content

[rush] Add support for an "extends" property in pnpm-config.json files. #5023

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Dec 3, 2024
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
21 changes: 11 additions & 10 deletions apps/heft/src/utilities/CoreConfigFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import * as path from 'path';
import {
ConfigurationFile,
ProjectConfigurationFile,
InheritanceType,
PathResolutionMethod,
type IJsonPathMetadataResolverOptions
Expand Down Expand Up @@ -59,9 +59,9 @@ export interface IHeftConfigurationJson {
}

export class CoreConfigFiles {
private static _heftConfigFileLoader: ConfigurationFile<IHeftConfigurationJson> | undefined;
private static _heftConfigFileLoader: ProjectConfigurationFile<IHeftConfigurationJson> | undefined;
private static _nodeServiceConfigurationLoader:
| ConfigurationFile<INodeServicePluginConfiguration>
| ProjectConfigurationFile<INodeServicePluginConfiguration>
| undefined;

public static heftConfigurationProjectRelativeFilePath: string = `${Constants.projectConfigFolderName}/${Constants.heftConfigurationFilename}`;
Expand Down Expand Up @@ -110,7 +110,7 @@ export class CoreConfigFiles {
};

const schemaObject: object = await import('../schemas/heft.schema.json');
CoreConfigFiles._heftConfigFileLoader = new ConfigurationFile<IHeftConfigurationJson>({
CoreConfigFiles._heftConfigFileLoader = new ProjectConfigurationFile<IHeftConfigurationJson>({
projectRelativeFilePath: CoreConfigFiles.heftConfigurationProjectRelativeFilePath,
jsonSchemaObject: schemaObject,
propertyInheritanceDefaults: {
Expand All @@ -134,7 +134,7 @@ export class CoreConfigFiles {
});
}

const heftConfigFileLoader: ConfigurationFile<IHeftConfigurationJson> =
const heftConfigFileLoader: ProjectConfigurationFile<IHeftConfigurationJson> =
CoreConfigFiles._heftConfigFileLoader;

let configurationFile: IHeftConfigurationJson;
Expand All @@ -158,10 +158,11 @@ export class CoreConfigFiles {
// want to see if it parses. We will use the ConfigurationFile class to load it to ensure
// that we follow the "extends" chain for the entire config file.
const legacySchemaObject: object = await import('../schemas/heft-legacy.schema.json');
const legacyConfigFileLoader: ConfigurationFile<unknown> = new ConfigurationFile<unknown>({
projectRelativeFilePath: CoreConfigFiles.heftConfigurationProjectRelativeFilePath,
jsonSchemaObject: legacySchemaObject
});
const legacyConfigFileLoader: ProjectConfigurationFile<unknown> =
new ProjectConfigurationFile<unknown>({
projectRelativeFilePath: CoreConfigFiles.heftConfigurationProjectRelativeFilePath,
jsonSchemaObject: legacySchemaObject
});
await legacyConfigFileLoader.loadConfigurationFileForProjectAsync(terminal, projectPath, rigConfig);
} catch (e2) {
// It doesn't match the legacy schema either. Throw the original error.
Expand Down Expand Up @@ -232,7 +233,7 @@ export class CoreConfigFiles {
if (!CoreConfigFiles._nodeServiceConfigurationLoader) {
const schemaObject: object = await import('../schemas/node-service.schema.json');
CoreConfigFiles._nodeServiceConfigurationLoader =
new ConfigurationFile<INodeServicePluginConfiguration>({
new ProjectConfigurationFile<INodeServicePluginConfiguration>({
projectRelativeFilePath: CoreConfigFiles.nodeServiceConfigurationProjectRelativeFilePath,
jsonSchemaObject: schemaObject
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Add support for an `\"extends\"` property in the `common/config/rush/pnpm-config.json` and `common/config/subspace/*/pnpm-config.json` files.",
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/heft-api-extractor-plugin",
"comment": "",
"type": "none"
}
],
"packageName": "@rushstack/heft-api-extractor-plugin"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/heft-config-file",
"comment": "Add a new `NonProjectConfigurationFile` class that is designed to load absolute-pathed configuration files without rig support.",
"type": "minor"
}
],
"packageName": "@rushstack/heft-config-file"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/heft-config-file",
"comment": "Rename `ConfigurationFile` to `ProjectConfigurationFile` and mark `ConfigurationFile` as `@deprecated`.",
"type": "minor"
}
],
"packageName": "@rushstack/heft-config-file"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/heft-jest-plugin",
"comment": "",
"type": "none"
}
],
"packageName": "@rushstack/heft-jest-plugin"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/heft-sass-plugin",
"comment": "",
"type": "none"
}
],
"packageName": "@rushstack/heft-sass-plugin"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/heft-typescript-plugin",
"comment": "",
"type": "none"
}
],
"packageName": "@rushstack/heft-typescript-plugin"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/heft",
"comment": "",
"type": "none"
}
],
"packageName": "@rushstack/heft"
}
78 changes: 62 additions & 16 deletions common/reviews/api/heft-config-file.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,44 +7,50 @@
import type { IRigConfig } from '@rushstack/rig-package';
import type { ITerminal } from '@rushstack/terminal';

// @beta @deprecated (undocumented)
export const ConfigurationFile: typeof ProjectConfigurationFile;

// @beta @deprecated (undocumented)
export type ConfigurationFile<TConfigurationFile> = ProjectConfigurationFile<TConfigurationFile>;

// @beta (undocumented)
export class ConfigurationFile<TConfigurationFile> {
constructor(options: IConfigurationFileOptions<TConfigurationFile>);
export abstract class ConfigurationFileBase<TConfigurationFile, TExtraOptions extends {}> {
constructor(options: IConfigurationFileOptions<TConfigurationFile, TExtraOptions>);
// @internal (undocumented)
static _formatPathForLogging: (path: string) => string;
getObjectSourceFilePath<TObject extends object>(obj: TObject): string | undefined;
getPropertyOriginalValue<TParentProperty extends object, TValue>(options: IOriginalValueOptions<TParentProperty>): TValue | undefined;
loadConfigurationFileForProject(terminal: ITerminal, projectPath: string, rigConfig?: IRigConfig): TConfigurationFile;
loadConfigurationFileForProjectAsync(terminal: ITerminal, projectPath: string, rigConfig?: IRigConfig): Promise<TConfigurationFile>;
readonly projectRelativeFilePath: string;
tryLoadConfigurationFileForProject(terminal: ITerminal, projectPath: string, rigConfig?: IRigConfig): TConfigurationFile | undefined;
tryLoadConfigurationFileForProjectAsync(terminal: ITerminal, projectPath: string, rigConfig?: IRigConfig): Promise<TConfigurationFile | undefined>;
// (undocumented)
protected _loadConfigurationFileInnerWithCache(terminal: ITerminal, resolvedConfigurationFilePath: string, visitedConfigurationFilePaths: Set<string>, rigConfig: IRigConfig | undefined): TConfigurationFile;
// (undocumented)
protected _loadConfigurationFileInnerWithCacheAsync(terminal: ITerminal, resolvedConfigurationFilePath: string, visitedConfigurationFilePaths: Set<string>, rigConfig: IRigConfig | undefined): Promise<TConfigurationFile>;
// (undocumented)
protected abstract _tryLoadConfigurationFileInRig(terminal: ITerminal, rigConfig: IRigConfig, visitedConfigurationFilePaths: Set<string>): TConfigurationFile | undefined;
// (undocumented)
protected abstract _tryLoadConfigurationFileInRigAsync(terminal: ITerminal, rigConfig: IRigConfig, visitedConfigurationFilePaths: Set<string>): Promise<TConfigurationFile | undefined>;
}

// @beta (undocumented)
export type IConfigurationFileOptions<TConfigurationFile> = IConfigurationFileOptionsWithJsonSchemaFilePath<TConfigurationFile> | IConfigurationFileOptionsWithJsonSchemaObject<TConfigurationFile>;
export type IConfigurationFileOptions<TConfigurationFile, TExtraOptions extends object> = IConfigurationFileOptionsWithJsonSchemaFilePath<TConfigurationFile, TExtraOptions> | IConfigurationFileOptionsWithJsonSchemaObject<TConfigurationFile, TExtraOptions>;

// @beta (undocumented)
export interface IConfigurationFileOptionsBase<TConfigurationFile> {
jsonPathMetadata?: IJsonPathsMetadata<TConfigurationFile>;
projectRelativeFilePath: string;
propertyInheritance?: IPropertiesInheritance<TConfigurationFile>;
propertyInheritanceDefaults?: IPropertyInheritanceDefaults;
}

// @beta (undocumented)
export interface IConfigurationFileOptionsWithJsonSchemaFilePath<TConfigurationFile> extends IConfigurationFileOptionsBase<TConfigurationFile> {
// (undocumented)
jsonSchemaObject?: never;
export type IConfigurationFileOptionsWithJsonSchemaFilePath<TConfigurationFile, TExtraOptions extends {}> = IConfigurationFileOptionsBase<TConfigurationFile> & TExtraOptions & {
jsonSchemaPath: string;
}
jsonSchemaObject?: never;
};

// @beta (undocumented)
export interface IConfigurationFileOptionsWithJsonSchemaObject<TConfigurationFile> extends IConfigurationFileOptionsBase<TConfigurationFile> {
export type IConfigurationFileOptionsWithJsonSchemaObject<TConfigurationFile, TExtraOptions extends {}> = IConfigurationFileOptionsBase<TConfigurationFile> & TExtraOptions & {
jsonSchemaObject: object;
// (undocumented)
jsonSchemaPath?: never;
}
};

// @beta
export interface ICustomJsonPathMetadata<TConfigurationFile> {
Expand Down Expand Up @@ -95,6 +101,11 @@ export interface IOriginalValueOptions<TParentProperty> {
propertyName: keyof TParentProperty;
}

// @beta (undocumented)
export interface IProjectConfigurationFileOptions {
projectRelativeFilePath: string;
}

// @beta (undocumented)
export type IPropertiesInheritance<TConfigurationFile> = {
[propertyName in keyof TConfigurationFile]?: IPropertyInheritance<InheritanceType.append | InheritanceType.merge | InheritanceType.replace> | ICustomPropertyInheritance<TConfigurationFile[propertyName]>;
Expand All @@ -114,6 +125,18 @@ export interface IPropertyInheritanceDefaults {
object?: IPropertyInheritance<InheritanceType.merge | InheritanceType.replace>;
}

// @beta (undocumented)
export class NonProjectConfigurationFile<TConfigurationFile> extends ConfigurationFileBase<TConfigurationFile, {}> {
loadConfigurationFile(terminal: ITerminal, filePath: string): TConfigurationFile;
loadConfigurationFileAsync(terminal: ITerminal, filePath: string): Promise<TConfigurationFile>;
tryLoadConfigurationFile(terminal: ITerminal, filePath: string): TConfigurationFile | undefined;
tryLoadConfigurationFileAsync(terminal: ITerminal, filePath: string): Promise<TConfigurationFile | undefined>;
// (undocumented)
protected _tryLoadConfigurationFileInRig(terminal: ITerminal, rigConfig: IRigConfig, visitedConfigurationFilePaths: Set<string>): TConfigurationFile | undefined;
// (undocumented)
protected _tryLoadConfigurationFileInRigAsync(terminal: ITerminal, rigConfig: IRigConfig, visitedConfigurationFilePaths: Set<string>): Promise<TConfigurationFile | undefined>;
}

// @beta (undocumented)
export enum PathResolutionMethod {
custom = "custom",
Expand All @@ -124,7 +147,30 @@ export enum PathResolutionMethod {
resolvePathRelativeToProjectRoot = "resolvePathRelativeToProjectRoot"
}

// @beta (undocumented)
export class ProjectConfigurationFile<TConfigurationFile> extends ConfigurationFileBase<TConfigurationFile, IProjectConfigurationFileOptions> {
constructor(options: IConfigurationFileOptions<TConfigurationFile, IProjectConfigurationFileOptions>);
loadConfigurationFileForProject(terminal: ITerminal, projectPath: string, rigConfig?: IRigConfig): TConfigurationFile;
loadConfigurationFileForProjectAsync(terminal: ITerminal, projectPath: string, rigConfig?: IRigConfig): Promise<TConfigurationFile>;
readonly projectRelativeFilePath: string;
tryLoadConfigurationFileForProject(terminal: ITerminal, projectPath: string, rigConfig?: IRigConfig): TConfigurationFile | undefined;
tryLoadConfigurationFileForProjectAsync(terminal: ITerminal, projectPath: string, rigConfig?: IRigConfig): Promise<TConfigurationFile | undefined>;
// (undocumented)
protected _tryLoadConfigurationFileInRig(terminal: ITerminal, rigConfig: IRigConfig, visitedConfigurationFilePaths: Set<string>): TConfigurationFile | undefined;
// (undocumented)
protected _tryLoadConfigurationFileInRigAsync(terminal: ITerminal, rigConfig: IRigConfig, visitedConfigurationFilePaths: Set<string>): Promise<TConfigurationFile | undefined>;
}

// @beta (undocumented)
export type PropertyInheritanceCustomFunction<TObject> = (currentObject: TObject, parentObject: TObject) => TObject;

// @beta
function stripAnnotations<TObject>(obj: TObject): TObject;

declare namespace TestUtilities {
export {
stripAnnotations
}
}

```
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
HeftConfiguration,
IHeftTaskRunIncrementalHookOptions
} from '@rushstack/heft';
import { ConfigurationFile } from '@rushstack/heft-config-file';
import { ProjectConfigurationFile } from '@rushstack/heft-config-file';

import { ApiExtractorRunner } from './ApiExtractorRunner';
import apiExtractorConfigSchema from './schemas/api-extractor-task.schema.json';
Expand Down Expand Up @@ -51,7 +51,7 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin {
private _apiExtractor: typeof TApiExtractor | undefined;
private _apiExtractorConfigurationFilePath: string | undefined | typeof UNINITIALIZED = UNINITIALIZED;
private _apiExtractorTaskConfigurationFileLoader:
| ConfigurationFile<IApiExtractorTaskConfiguration>
| ProjectConfigurationFile<IApiExtractorTaskConfiguration>
| undefined;
private _printedWatchWarning: boolean = false;

Expand Down Expand Up @@ -156,10 +156,11 @@ export default class ApiExtractorPlugin implements IHeftTaskPlugin {
heftConfiguration: HeftConfiguration
): Promise<IApiExtractorTaskConfiguration | undefined> {
if (!this._apiExtractorTaskConfigurationFileLoader) {
this._apiExtractorTaskConfigurationFileLoader = new ConfigurationFile<IApiExtractorTaskConfiguration>({
projectRelativeFilePath: TASK_CONFIG_RELATIVE_PATH,
jsonSchemaObject: apiExtractorConfigSchema
});
this._apiExtractorTaskConfigurationFileLoader =
new ProjectConfigurationFile<IApiExtractorTaskConfiguration>({
projectRelativeFilePath: TASK_CONFIG_RELATIVE_PATH,
jsonSchemaObject: apiExtractorConfigSchema
});
}

return await this._apiExtractorTaskConfigurationFileLoader.tryLoadConfigurationFileForProjectAsync(
Expand Down
8 changes: 4 additions & 4 deletions heft-plugins/heft-jest-plugin/src/JestPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type {
CommandLineStringListParameter
} from '@rushstack/heft';
import {
ConfigurationFile,
ProjectConfigurationFile,
type ICustomJsonPathMetadata,
type IJsonPathMetadataResolverOptions,
InheritanceType,
Expand Down Expand Up @@ -139,7 +139,7 @@ interface IPendingTestRun {
* @internal
*/
export default class JestPlugin implements IHeftTaskPlugin<IJestPluginOptions> {
private static _jestConfigurationFileLoader: ConfigurationFile<IHeftJestConfiguration> | undefined;
private static _jestConfigurationFileLoader: ProjectConfigurationFile<IHeftJestConfiguration> | undefined;

private _jestPromise: Promise<unknown> | undefined;
private _pendingTestRuns: Set<IPendingTestRun> = new Set();
Expand Down Expand Up @@ -677,7 +677,7 @@ export default class JestPlugin implements IHeftTaskPlugin<IJestPluginOptions> {
public static _getJestConfigurationLoader(
buildFolder: string,
projectRelativeFilePath: string
): ConfigurationFile<IHeftJestConfiguration> {
): ProjectConfigurationFile<IHeftJestConfiguration> {
if (!JestPlugin._jestConfigurationFileLoader) {
// By default, ConfigurationFile will replace all objects, so we need to provide merge functions for these
const shallowObjectInheritanceFunc: <T extends Record<string, unknown> | undefined>(
Expand Down Expand Up @@ -722,7 +722,7 @@ export default class JestPlugin implements IHeftTaskPlugin<IJestPluginOptions> {
resolveAsModule: true
});

JestPlugin._jestConfigurationFileLoader = new ConfigurationFile<IHeftJestConfiguration>({
JestPlugin._jestConfigurationFileLoader = new ProjectConfigurationFile<IHeftJestConfiguration>({
projectRelativeFilePath: projectRelativeFilePath,
// Bypass Jest configuration validation
jsonSchemaObject: anythingSchema,
Expand Down
6 changes: 3 additions & 3 deletions heft-plugins/heft-jest-plugin/src/test/JestPlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import * as path from 'path';
import type { Config } from '@jest/types';
import type { IHeftTaskSession, HeftConfiguration, CommandLineParameter } from '@rushstack/heft';
import type { ConfigurationFile } from '@rushstack/heft-config-file';
import type { ProjectConfigurationFile } from '@rushstack/heft-config-file';
import { Import, JsonFile } from '@rushstack/node-core-library';
import { StringBufferTerminalProvider, Terminal } from '@rushstack/terminal';

Expand Down Expand Up @@ -74,7 +74,7 @@ describe('JestConfigLoader', () => {
// Because we require the built modules, we need to set our rootDir to be in the 'lib' folder, since transpilation
// means that we don't run on the built test assets directly
const rootDir: string = path.resolve(__dirname, '..', '..', 'lib', 'test', 'project1');
const loader: ConfigurationFile<IHeftJestConfiguration> = JestPlugin._getJestConfigurationLoader(
const loader: ProjectConfigurationFile<IHeftJestConfiguration> = JestPlugin._getJestConfigurationLoader(
rootDir,
'config/jest.config.json'
);
Expand Down Expand Up @@ -161,7 +161,7 @@ describe('JestConfigLoader', () => {
// Because we require the built modules, we need to set our rootDir to be in the 'lib' folder, since transpilation
// means that we don't run on the built test assets directly
const rootDir: string = path.resolve(__dirname, '..', '..', 'lib', 'test', 'project2');
const loader: ConfigurationFile<IHeftJestConfiguration> = JestPlugin._getJestConfigurationLoader(
const loader: ProjectConfigurationFile<IHeftJestConfiguration> = JestPlugin._getJestConfigurationLoader(
rootDir,
'config/jest.config.json'
);
Expand Down
Loading
Loading