in libraries/heft-config-file/src/ConfigurationFile.ts [328:552]
private async _loadConfigurationFileInnerAsync(
terminal: ITerminal,
resolvedConfigurationFilePath: string,
visitedConfigurationFilePaths: Set<string>,
rigConfig: RigConfig | undefined
): Promise<TConfigurationFile> {
const resolvedConfigurationFilePathForLogging: string = ConfigurationFile._formatPathForLogging(
resolvedConfigurationFilePath
);
let fileText: string;
try {
fileText = await FileSystem.readFileAsync(resolvedConfigurationFilePath);
} catch (e) {
if (FileSystem.isNotExistError(e as Error)) {
if (rigConfig) {
terminal.writeDebugLine(
`Config file "${resolvedConfigurationFilePathForLogging}" does not exist. Attempting to load via rig.`
);
const rigResult: TConfigurationFile | undefined = await this._tryLoadConfigurationFileInRigAsync(
terminal,
rigConfig,
visitedConfigurationFilePaths
);
if (rigResult) {
return rigResult;
}
} else {
terminal.writeDebugLine(
`Configuration file "${resolvedConfigurationFilePathForLogging}" not found.`
);
}
(e as Error).message = `File does not exist: ${resolvedConfigurationFilePathForLogging}`;
}
throw e;
}
let configurationJson: IConfigurationJson & TConfigurationFile;
try {
configurationJson = await JsonFile.parseString(fileText);
} catch (e) {
throw new Error(`In config file "${resolvedConfigurationFilePathForLogging}": ${e}`);
}
this._schema.validateObject(configurationJson, resolvedConfigurationFilePathForLogging);
this._annotateProperties(resolvedConfigurationFilePath, configurationJson);
for (const [jsonPath, metadata] of Object.entries(this._jsonPathMetadata)) {
JSONPath({
path: jsonPath,
json: configurationJson,
callback: (payload: unknown, payloadType: string, fullPayload: IJsonPathCallbackObject) => {
const resolvedPath: string = this._resolvePathProperty(
resolvedConfigurationFilePath,
fullPayload.path,
fullPayload.value,
metadata
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(fullPayload.parent as any)[fullPayload.parentProperty] = resolvedPath;
},
otherTypeCallback: () => {
throw new Error('@other() tags are not supported');
}
});
}
let parentConfiguration: Partial<TConfigurationFile> = {};
if (configurationJson.extends) {
try {
const resolvedParentConfigPath: string = Import.resolveModule({
modulePath: configurationJson.extends,
baseFolderPath: nodeJsPath.dirname(resolvedConfigurationFilePath)
});
parentConfiguration = await this._loadConfigurationFileInnerWithCacheAsync(
terminal,
resolvedParentConfigPath,
visitedConfigurationFilePaths,
undefined
);
} catch (e) {
if (FileSystem.isNotExistError(e as Error)) {
throw new Error(
`In file "${resolvedConfigurationFilePathForLogging}", file referenced in "extends" property ` +
`("${configurationJson.extends}") cannot be resolved.`
);
} else {
throw e;
}
}
}
const propertyNames: Set<string> = new Set<string>([
...Object.keys(parentConfiguration),
...Object.keys(configurationJson)
]);
const resultAnnotation: IConfigurationFileFieldAnnotation<TConfigurationFile> = {
configurationFilePath: resolvedConfigurationFilePath,
originalValues: {} as TConfigurationFile
};
const result: TConfigurationFile = {
[CONFIGURATION_FILE_FIELD_ANNOTATION]: resultAnnotation
} as unknown as TConfigurationFile;
for (const propertyName of propertyNames) {
if (propertyName === '$schema' || propertyName === 'extends') {
continue;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const propertyValue: unknown | undefined = (configurationJson as any)[propertyName];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const parentPropertyValue: unknown | undefined = (parentConfiguration as any)[propertyName];
const bothAreArrays: boolean = Array.isArray(propertyValue) && Array.isArray(parentPropertyValue);
const defaultInheritanceType: IPropertyInheritance<InheritanceType> = bothAreArrays
? { inheritanceType: InheritanceType.append }
: { inheritanceType: InheritanceType.replace };
const propertyInheritance: IPropertyInheritance<InheritanceType> =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this._propertyInheritanceTypes as any)[propertyName] !== undefined
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
(this._propertyInheritanceTypes as any)[propertyName]
: defaultInheritanceType;
let newValue: unknown;
const usePropertyValue: () => void = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(resultAnnotation.originalValues as any)[propertyName] = this.getPropertyOriginalValue<any, any>({
parentObject: configurationJson,
propertyName: propertyName
});
newValue = propertyValue;
};
const useParentPropertyValue: () => void = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(resultAnnotation.originalValues as any)[propertyName] = this.getPropertyOriginalValue<any, any>({
parentObject: parentConfiguration,
propertyName: propertyName
});
newValue = parentPropertyValue;
};
if (propertyValue !== undefined && parentPropertyValue === undefined) {
usePropertyValue();
} else if (parentPropertyValue !== undefined && propertyValue === undefined) {
useParentPropertyValue();
} else {
switch (propertyInheritance.inheritanceType) {
case InheritanceType.replace: {
if (propertyValue !== undefined) {
usePropertyValue();
} else {
useParentPropertyValue();
}
break;
}
case InheritanceType.append: {
if (propertyValue !== undefined && parentPropertyValue === undefined) {
usePropertyValue();
} else if (propertyValue === undefined && parentPropertyValue !== undefined) {
useParentPropertyValue();
} else {
if (!Array.isArray(propertyValue) || !Array.isArray(parentPropertyValue)) {
throw new Error(
`Issue in processing configuration file property "${propertyName}". ` +
`Property is not an array, but the inheritance type is set as "${InheritanceType.append}"`
);
}
newValue = [...parentPropertyValue, ...propertyValue];
(newValue as unknown as IAnnotatedField<unknown[]>)[CONFIGURATION_FILE_FIELD_ANNOTATION] = {
configurationFilePath: undefined,
originalValues: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(parentPropertyValue as any)[CONFIGURATION_FILE_FIELD_ANNOTATION].originalValues,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(propertyValue as any)[CONFIGURATION_FILE_FIELD_ANNOTATION].originalValues
}
};
}
break;
}
case InheritanceType.custom: {
const customInheritance: ICustomPropertyInheritance<unknown> =
propertyInheritance as ICustomPropertyInheritance<unknown>;
if (
!customInheritance.inheritanceFunction ||
typeof customInheritance.inheritanceFunction !== 'function'
) {
throw new Error(
'For property inheritance type "InheritanceType.custom", an inheritanceFunction must be provided.'
);
}
newValue = customInheritance.inheritanceFunction(propertyValue, parentPropertyValue);
break;
}
default: {
throw new Error(`Unknown inheritance type "${propertyInheritance}"`);
}
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(result as any)[propertyName] = newValue;
}
try {
this._schema.validateObject(result, resolvedConfigurationFilePathForLogging);
} catch (e) {
throw new Error(`Resolved configuration object does not match schema: ${e}`);
}
return result;
}