diff --git a/.changeset/blue-bikes-explode.md b/.changeset/blue-bikes-explode.md new file mode 100644 index 0000000000..1c10a08a46 --- /dev/null +++ b/.changeset/blue-bikes-explode.md @@ -0,0 +1,14 @@ +--- +'@backstage/config-loader': minor +--- + +Update `loadConfig` to return `LoadConfigResult` instead of an array of `AppConfig`. + +This function is primarily used internally by other config loaders like `loadBackendConfig` which means no changes are required for most users. + +If you use `loadConfig` directly you will need to update your usage from: + +```diff +- const appConfigs = await loadConfig(options) ++ const { appConfigs } = await loadConfig(options) +``` diff --git a/.changeset/hungry-wombats-happen.md b/.changeset/hungry-wombats-happen.md new file mode 100644 index 0000000000..018e7ae9e8 --- /dev/null +++ b/.changeset/hungry-wombats-happen.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-common': patch +'@backstage/cli': patch +--- + +Update internal usage of `configLoader.loadConfig` that now returns an object instead of an array of configs. diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 3fd5e1b44e..941ad26e65 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -200,7 +200,7 @@ export async function loadBackendConfig(options: { }); const config = new ObservableConfigProxy(options.logger); - const configs = await loadConfig({ + const { appConfigs } = await loadConfig({ configRoot: paths.targetRoot, configPaths: [], configTargets: configTargets, @@ -227,14 +227,16 @@ export async function loadBackendConfig(options: { }); options.logger.info( - `Loaded config from ${configs.map(c => c.context).join(', ')}`, + `Loaded config from ${appConfigs.map(c => c.context).join(', ')}`, ); - config.setConfig(ConfigReader.fromConfigs(configs)); + config.setConfig(ConfigReader.fromConfigs(appConfigs)); // Subscribe to config changes and update the redaction list for logging - updateRedactionList(schema, configs, options.logger); - config.subscribe(() => updateRedactionList(schema, configs, options.logger)); + updateRedactionList(schema, appConfigs, options.logger); + config.subscribe(() => + updateRedactionList(schema, appConfigs, options.logger), + ); return config; } diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index 6f93318ead..d343a554f8 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -54,7 +54,7 @@ export async function loadCliConfig(options: Options) { packagePaths: [paths.resolveTargetRoot('package.json')], }); - const appConfigs = await loadConfig({ + const { appConfigs } = await loadConfig({ experimentalEnvFunc: options.mockEnv ? async name => process.env[name] || 'x' : undefined, diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index 6f568df942..e0fab50e5b 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -38,7 +38,9 @@ export type ConfigTarget = export type ConfigVisibility = 'frontend' | 'backend' | 'secret'; // @public -export function loadConfig(options: LoadConfigOptions): Promise; +export function loadConfig( + options: LoadConfigOptions, +): Promise; // @public export type LoadConfigOptions = { @@ -66,6 +68,11 @@ export type LoadConfigOptionsWatch = { stopSignal?: Promise; }; +// @public +export type LoadConfigResult = { + appConfigs: AppConfig[]; +}; + // @public export function loadConfigSchema( options: LoadConfigSchemaOptions, diff --git a/packages/config-loader/src/index.ts b/packages/config-loader/src/index.ts index 97f0d301a3..2699b3571f 100644 --- a/packages/config-loader/src/index.ts +++ b/packages/config-loader/src/index.ts @@ -34,4 +34,5 @@ export type { LoadConfigOptions, LoadConfigOptionsWatch, LoadConfigOptionsRemote, + LoadConfigResult, } from './loader'; diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index 804254afa0..b5c986eeb9 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -122,18 +122,20 @@ describe('loadConfig', () => { configTargets: [], env: 'production', }), - ).resolves.toEqual([ - { - context: 'app-config.yaml', - data: { - app: { - title: 'Example App', - sessionKey: 'abc123', - escaped: '${Escaped}', + ).resolves.toEqual({ + appConfigs: [ + { + context: 'app-config.yaml', + data: { + app: { + title: 'Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, }, }, - }, - ]); + ], + }); }); it('load config from remote path', async () => { @@ -151,18 +153,20 @@ describe('loadConfig', () => { reloadIntervalSeconds: 30, }, }), - ).resolves.toEqual([ - { - context: configUrl, - data: { - app: { - title: 'Remote Example App', - sessionKey: 'abc123', - escaped: '${Escaped}', + ).resolves.toEqual({ + appConfigs: [ + { + context: configUrl, + data: { + app: { + title: 'Remote Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, }, }, - }, - ]); + ], + }); }); it('loads config with secrets from two different files', async () => { @@ -173,28 +177,30 @@ describe('loadConfig', () => { configTargets: [{ path: '/root/app-config.yaml' }], env: 'production', }), - ).resolves.toEqual([ - { - context: 'app-config.yaml', - data: { - app: { - title: 'Example App', - sessionKey: 'abc123', - escaped: '${Escaped}', + ).resolves.toEqual({ + appConfigs: [ + { + context: 'app-config.yaml', + data: { + app: { + title: 'Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, }, }, - }, - { - context: 'app-config2.yaml', - data: { - app: { - title: 'Example App 2', - sessionKey: 'abc123', - escaped: '${Escaped}', + { + context: 'app-config2.yaml', + data: { + app: { + title: 'Example App 2', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, }, }, - }, - ]); + ], + }); }); it('loads config with secrets from single file', async () => { @@ -205,18 +211,20 @@ describe('loadConfig', () => { configTargets: [{ path: '/root/app-config.yaml' }], env: 'production', }), - ).resolves.toEqual([ - { - context: 'app-config.yaml', - data: { - app: { - title: 'Example App', - sessionKey: 'abc123', - escaped: '${Escaped}', + ).resolves.toEqual({ + appConfigs: [ + { + context: 'app-config.yaml', + data: { + app: { + title: 'Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, }, }, - }, - ]); + ], + }); }); it('loads development config with secrets', async () => { @@ -230,34 +238,36 @@ describe('loadConfig', () => { ], env: 'development', }), - ).resolves.toEqual([ - { - context: 'app-config.yaml', - data: { - app: { - title: 'Example App', - sessionKey: 'abc123', - escaped: '${Escaped}', - }, - }, - }, - { - context: 'app-config.development.yaml', - data: { - app: { - sessionKey: 'development-key', - }, - backend: { - foo: { - bar: 'token is-secret', + ).resolves.toEqual({ + appConfigs: [ + { + context: 'app-config.yaml', + data: { + app: { + title: 'Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', }, }, - other: { - secret: 'abc123', + }, + { + context: 'app-config.development.yaml', + data: { + app: { + sessionKey: 'development-key', + }, + backend: { + foo: { + bar: 'token is-secret', + }, + }, + other: { + secret: 'abc123', + }, }, }, - }, - ]); + ], + }); }); it('loads deep substituted config', async () => { @@ -268,19 +278,21 @@ describe('loadConfig', () => { configTargets: [{ path: '/root/app-config.substitute.yaml' }], env: 'development', }), - ).resolves.toEqual([ - { - context: 'app-config.substitute.yaml', - data: { - app: { - someConfig: { - secret: '123abc', + ).resolves.toEqual({ + appConfigs: [ + { + context: 'app-config.substitute.yaml', + data: { + app: { + someConfig: { + secret: '123abc', + }, + noSubstitute: 'notSubstituted', }, - noSubstitute: 'notSubstituted', }, }, - }, - ]); + ], + }); }); it('watches config files', async () => { @@ -297,18 +309,20 @@ describe('loadConfig', () => { stopSignal: stopSignal.promise, }, }), - ).resolves.toEqual([ - { - context: 'app-config.yaml', - data: { - app: { - title: 'Example App', - sessionKey: 'abc123', - escaped: '${Escaped}', + ).resolves.toEqual({ + appConfigs: [ + { + context: 'app-config.yaml', + data: { + app: { + title: 'Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, }, }, - }, - ]); + ], + }); await fs.writeJson('/root/app-config.yaml', { app: { @@ -349,18 +363,20 @@ describe('loadConfig', () => { reloadIntervalSeconds: 1, }, }), - ).resolves.toEqual([ - { - context: configUrl, - data: { - app: { - title: 'Remote Example App', - sessionKey: 'abc123', - escaped: '${Escaped}', + ).resolves.toEqual({ + appConfigs: [ + { + context: configUrl, + data: { + app: { + title: 'Remote Example App', + sessionKey: 'abc123', + escaped: '${Escaped}', + }, }, }, - }, - ]); + ], + }); server.use(reloadHandler); diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 4a5660f46c..6a92ed519d 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -88,6 +88,17 @@ export type LoadConfigOptions = { watch?: LoadConfigOptionsWatch; }; +/** + * Results of loading configuration files. + * @public + */ +export type LoadConfigResult = { + /** + * Array of all loaded configs. + */ + appConfigs: AppConfig[]; +}; + /** * Load configuration data. * @@ -95,7 +106,7 @@ export type LoadConfigOptions = { */ export async function loadConfig( options: LoadConfigOptions, -): Promise { +): Promise { const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options; const configPaths: string[] = options.configTargets @@ -290,7 +301,9 @@ export async function loadConfig( watchRemoteConfig(watch, remote); } - return remote - ? [...remoteConfigs, ...fileConfigs, ...envConfigs] - : [...fileConfigs, ...envConfigs]; + return { + appConfigs: remote + ? [...remoteConfigs, ...fileConfigs, ...envConfigs] + : [...fileConfigs, ...envConfigs], + }; }