private async _loadConfigurationFileInnerAsync()

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;
  }