Skip to content

Commit

Permalink
[rush] Add support for an "extends" property in pnpm-config.json
Browse files Browse the repository at this point in the history
…files. (#5023)

* Create a non-project version of ConfigurationFile.

* fixup! Create a non-project version of ConfigurationFile.

* fixup! Create a non-project version of ConfigurationFile.

* fixup! Create a non-project version of ConfigurationFile.

* Add support for extends in pnpm-config.json

* fixup! Add support for extends in pnpm-config.json

* fixup! Add support for extends in pnpm-config.json

* Rename ConfigurationFile to ProjectConfigurationFile.

* fixup! Add support for extends in pnpm-config.json
  • Loading branch information
iclanton authored Dec 3, 2024
1 parent 8f373ef commit b71f856
Show file tree
Hide file tree
Showing 28 changed files with 827 additions and 405 deletions.
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
}
}

```
13 changes: 7 additions & 6 deletions heft-plugins/heft-api-extractor-plugin/src/ApiExtractorPlugin.ts
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

0 comments on commit b71f856

Please sign in to comment.