From ea69e4671c0c806f5c2cf45e6b37392ee5977194 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 30 Aug 2024 15:44:40 +0200 Subject: [PATCH] core-app-api: add support for backstage.io/config tags Signed-off-by: Patrik Oldsberg --- .changeset/calm-toes-melt.md | 5 +++ ...r.test.ts => defaultConfigLoader.test.tsx} | 42 +++++++++++++++++++ .../src/app/defaultConfigLoader.ts | 31 +++++++++++++- 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 .changeset/calm-toes-melt.md rename packages/core-app-api/src/app/{defaultConfigLoader.test.ts => defaultConfigLoader.test.tsx} (67%) diff --git a/.changeset/calm-toes-melt.md b/.changeset/calm-toes-melt.md new file mode 100644 index 0000000000..b8a7b12c46 --- /dev/null +++ b/.changeset/calm-toes-melt.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +The `defaultConfigLoader` now also reads configuration from scripts tags with `type="backstage.io/config"`. The tag is expected to contain a JSON-serialized array of `AppConfig` objects. If any of these script tags are present, the injected runtime configuration in the static assets will no longer be used. diff --git a/packages/core-app-api/src/app/defaultConfigLoader.test.ts b/packages/core-app-api/src/app/defaultConfigLoader.test.tsx similarity index 67% rename from packages/core-app-api/src/app/defaultConfigLoader.test.ts rename to packages/core-app-api/src/app/defaultConfigLoader.test.tsx index c70ec3fbff..44d93aa625 100644 --- a/packages/core-app-api/src/app/defaultConfigLoader.test.ts +++ b/packages/core-app-api/src/app/defaultConfigLoader.test.tsx @@ -14,7 +14,10 @@ * limitations under the License. */ +import React from 'react'; +import { render } from '@testing-library/react'; import { defaultConfigLoaderSync } from './defaultConfigLoader'; +import { ConfigReader } from '@backstage/config'; (process as any).env = { NODE_ENV: 'test' }; const anyEnv = process.env as any; @@ -53,6 +56,45 @@ describe('defaultConfigLoaderSync', () => { ]); }); + it('loads config from script tags and ignore static config', () => { + anyEnv.APP_CONFIG = []; + + render( + , + ); + + const configs = (defaultConfigLoaderSync as any)('{"my":"runtime-config"}'); + expect(configs).toEqual([ + { data: { my: 'config' }, context: 'a' }, + { data: { my: 'override-config' }, context: 'b' }, + ]); + expect(ConfigReader.fromConfigs(configs).get('my')).toBe('override-config'); + }); + + it('loads config from all script tags in order', () => { + anyEnv.APP_CONFIG = []; + + render( + <> + + + , + ); + + const configs = (defaultConfigLoaderSync as any)('{"my":"runtime-config"}'); + expect(configs).toEqual([ + { data: { my: 'config' }, context: 'a' }, + { data: { my: 'override-config' }, context: 'b' }, + ]); + expect(ConfigReader.fromConfigs(configs).get('my')).toBe('override-config'); + }); + it('fails to load invalid missing config', () => { expect(() => defaultConfigLoaderSync()).toThrow( 'No static configuration provided', diff --git a/packages/core-app-api/src/app/defaultConfigLoader.ts b/packages/core-app-api/src/app/defaultConfigLoader.ts index 301b64a865..bfcef390ec 100644 --- a/packages/core-app-api/src/app/defaultConfigLoader.ts +++ b/packages/core-app-api/src/app/defaultConfigLoader.ts @@ -49,9 +49,36 @@ export function defaultConfigLoaderSync( } const configs = appConfig.slice() as unknown as AppConfig[]; - // Avoiding this string also being replaced at runtime - if ( + // Check if we have any config script tags, otherwise fall back to injected config + const configScripts = document.querySelectorAll( + 'script[type="backstage.io/config"]', + ); + if (configScripts.length > 0) { + for (const el of configScripts) { + try { + const content = el.textContent; + if (!content) { + throw new Error('tag is empty'); + } + let data; + try { + data = JSON.parse(content); + } catch (error) { + throw new Error(`failed to parse config; ${error}`); + } + if (!Array.isArray(data)) { + throw new Error('data is not an array'); + } + configs.push(...data); + } catch (error) { + throw new Error( + `Failed to load config from script tag, ${error.message}`, + ); + } + } + } else if ( runtimeConfigJson !== + // Avoiding this string also being replaced at runtime '__app_injected_runtime_config__'.toLocaleUpperCase('en-US') ) { try {