From 811328ed1fd73322c122ecffffaf36e93a278155 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Aug 2020 10:37:22 +0200 Subject: [PATCH 1/5] config-loader: use multiple config roots --- packages/backend-common/src/config.ts | 2 +- packages/cli/src/commands/app/build.ts | 4 +++- packages/cli/src/commands/app/serve.ts | 4 +++- packages/cli/src/commands/backend/dev.ts | 4 +++- packages/cli/src/commands/plugin/export.ts | 4 +++- packages/cli/src/commands/plugin/serve.ts | 4 +++- packages/config-loader/src/lib/resolver.ts | 16 ++++++++++++---- packages/config-loader/src/loader.ts | 4 ++-- 8 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 8391ed4104..d63f5171d3 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -23,7 +23,7 @@ import { loadConfig } from '@backstage/config-loader'; export async function loadBackendConfig() { const paths = findPaths(__dirname); const configs = await loadConfig({ - rootPath: paths.targetRoot, + rootPaths: [paths.targetDir, paths.targetRoot], shouldReadSecrets: true, }); return configs; diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index b3ec5ad2bf..81eec7a32b 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -21,7 +21,9 @@ import { paths } from '../../lib/paths'; import { buildBundle } from '../../lib/bundler'; export default async (cmd: Command) => { - const appConfigs = await loadConfig({ rootPath: paths.targetRoot }); + const appConfigs = await loadConfig({ + rootPaths: [paths.targetDir, paths.targetRoot], + }); await buildBundle({ entry: 'src/index', statsJsonEnabled: cmd.stats, diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts index 732370ef3b..f4069ca9fa 100644 --- a/packages/cli/src/commands/app/serve.ts +++ b/packages/cli/src/commands/app/serve.ts @@ -21,7 +21,9 @@ import { paths } from '../../lib/paths'; import { serveBundle } from '../../lib/bundler'; export default async (cmd: Command) => { - const appConfigs = await loadConfig({ rootPath: paths.targetRoot }); + const appConfigs = await loadConfig({ + rootPaths: [paths.targetDir, paths.targetRoot], + }); const waitForExit = await serveBundle({ entry: 'src/index', checksEnabled: cmd.check, diff --git a/packages/cli/src/commands/backend/dev.ts b/packages/cli/src/commands/backend/dev.ts index 03d9464722..8d5e6339dc 100644 --- a/packages/cli/src/commands/backend/dev.ts +++ b/packages/cli/src/commands/backend/dev.ts @@ -21,7 +21,9 @@ import { paths } from '../../lib/paths'; import { serveBackend } from '../../lib/bundler/backend'; export default async (cmd: Command) => { - const appConfigs = await loadConfig({ rootPath: paths.targetRoot }); + const appConfigs = await loadConfig({ + rootPaths: [paths.targetDir, paths.targetRoot], + }); const waitForExit = await serveBackend({ entry: 'src/index', checksEnabled: cmd.check, diff --git a/packages/cli/src/commands/plugin/export.ts b/packages/cli/src/commands/plugin/export.ts index be6df707f3..c3beb8b23e 100644 --- a/packages/cli/src/commands/plugin/export.ts +++ b/packages/cli/src/commands/plugin/export.ts @@ -21,7 +21,9 @@ import { paths } from '../../lib/paths'; import { buildBundle } from '../../lib/bundler'; export default async (cmd: Command) => { - const appConfigs = await loadConfig({ rootPath: paths.targetRoot }); + const appConfigs = await loadConfig({ + rootPaths: [paths.targetDir, paths.targetRoot], + }); await buildBundle({ entry: 'dev/index', statsJsonEnabled: cmd.stats, diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts index 5ee4b70a1e..989cf92523 100644 --- a/packages/cli/src/commands/plugin/serve.ts +++ b/packages/cli/src/commands/plugin/serve.ts @@ -21,7 +21,9 @@ import { paths } from '../../lib/paths'; import { serveBundle } from '../../lib/bundler'; export default async (cmd: Command) => { - const appConfigs = await loadConfig({ rootPath: paths.targetRoot }); + const appConfigs = await loadConfig({ + rootPaths: [paths.targetDir, paths.targetRoot], + }); const waitForExit = await serveBundle({ entry: 'dev/index', checksEnabled: cmd.check, diff --git a/packages/config-loader/src/lib/resolver.ts b/packages/config-loader/src/lib/resolver.ts index a3921f07aa..5ee3d2cf09 100644 --- a/packages/config-loader/src/lib/resolver.ts +++ b/packages/config-loader/src/lib/resolver.ts @@ -15,10 +15,11 @@ */ import { resolve as resolvePath } from 'path'; +import { pathExists } from 'fs-extra'; type ResolveOptions = { - // Root path for search for app-config.yaml - rootPath: string; + // Root paths to search for config files. Config from earlier paths has higher priority. + rootPaths: string[]; }; /** @@ -29,7 +30,14 @@ export async function resolveStaticConfig( ): Promise { // TODO: We'll want this to be a bit more elaborate, probably adding configs for // specific env, and maybe local config for plugins. - const configPath = resolvePath(options.rootPath, 'app-config.yaml'); + const resolvedPaths = []; - return [configPath]; + for (const rootPath of options.rootPaths) { + const path = resolvePath(rootPath, 'app-config.yaml'); + if (await pathExists(path)) { + resolvedPaths.push(path); + } + } + + return resolvedPaths; } diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index ce0e09e815..ba3c615ff7 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -25,8 +25,8 @@ import { } from './lib'; export type LoadConfigOptions = { - // Root path for search for app-config.yaml - rootPath: string; + // Root paths to search for config files. Config from earlier paths has higher priority. + rootPaths: string[]; // Whether to read secrets or omit them, defaults to false. shouldReadSecrets?: boolean; From 74edab290a8297ec1700718f0cf44e93716461a8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Aug 2020 10:39:08 +0200 Subject: [PATCH 2/5] config-loader: resolve config files suffixed with local and NODE_ENV --- .gitignore | 3 +++ packages/config-loader/src/lib/resolver.ts | 26 +++++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index c1bcf5a4b4..069be4aa2d 100644 --- a/.gitignore +++ b/.gitignore @@ -125,3 +125,6 @@ dist # MkDocs build output site + +# Local configuration files +*.local.yaml diff --git a/packages/config-loader/src/lib/resolver.ts b/packages/config-loader/src/lib/resolver.ts index 5ee3d2cf09..c1f95cdef7 100644 --- a/packages/config-loader/src/lib/resolver.ts +++ b/packages/config-loader/src/lib/resolver.ts @@ -24,18 +24,34 @@ type ResolveOptions = { /** * Resolves all configuration files that should be loaded in the given environment. + * + * For each root directory, search for the default app-config.yaml, along with suffixed + * NODE_ENV and local variants, e.g. app-config.production.yaml or app-config.development.local.yaml + * + * The priority order of config loaded through suffixes is `env > local > none`, meaning that + * for example app-config.development.yaml has higher priority than `app-config.local.yaml`. + * */ export async function resolveStaticConfig( options: ResolveOptions, ): Promise { - // TODO: We'll want this to be a bit more elaborate, probably adding configs for - // specific env, and maybe local config for plugins. + const env = process.env.NODE_ENV ?? 'development'; + + const filePaths = [ + `app-config.${env}.local.yaml`, + `app-config.${env}.yaml`, + `app-config.local.yaml`, + `app-config.yaml`, + ]; + const resolvedPaths = []; for (const rootPath of options.rootPaths) { - const path = resolvePath(rootPath, 'app-config.yaml'); - if (await pathExists(path)) { - resolvedPaths.push(path); + for (const filePath of filePaths) { + const path = resolvePath(rootPath, filePath); + if (await pathExists(path)) { + resolvedPaths.push(path); + } } } From a37aa1d994cf024e5438e067ec702487dc800da0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Aug 2020 10:52:50 +0200 Subject: [PATCH 3/5] config-loader: pass in env as an option --- packages/backend-common/src/config.ts | 1 + packages/cli/src/commands/app/build.ts | 1 + packages/cli/src/commands/app/serve.ts | 1 + packages/cli/src/commands/backend/dev.ts | 1 + packages/cli/src/commands/plugin/export.ts | 1 + packages/cli/src/commands/plugin/serve.ts | 1 + packages/config-loader/src/lib/resolver.ts | 8 ++++---- packages/config-loader/src/loader.ts | 3 +++ 8 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index d63f5171d3..2295a8f943 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -23,6 +23,7 @@ import { loadConfig } from '@backstage/config-loader'; export async function loadBackendConfig() { const paths = findPaths(__dirname); const configs = await loadConfig({ + env: process.env.NODE_ENV, rootPaths: [paths.targetDir, paths.targetRoot], shouldReadSecrets: true, }); diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index 81eec7a32b..12ea362063 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -22,6 +22,7 @@ import { buildBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ + env: 'production', rootPaths: [paths.targetDir, paths.targetRoot], }); await buildBundle({ diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts index f4069ca9fa..b372663f09 100644 --- a/packages/cli/src/commands/app/serve.ts +++ b/packages/cli/src/commands/app/serve.ts @@ -22,6 +22,7 @@ import { serveBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ + env: 'development', rootPaths: [paths.targetDir, paths.targetRoot], }); const waitForExit = await serveBundle({ diff --git a/packages/cli/src/commands/backend/dev.ts b/packages/cli/src/commands/backend/dev.ts index 8d5e6339dc..cbea2c60cc 100644 --- a/packages/cli/src/commands/backend/dev.ts +++ b/packages/cli/src/commands/backend/dev.ts @@ -22,6 +22,7 @@ import { serveBackend } from '../../lib/bundler/backend'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ + env: 'development', rootPaths: [paths.targetDir, paths.targetRoot], }); const waitForExit = await serveBackend({ diff --git a/packages/cli/src/commands/plugin/export.ts b/packages/cli/src/commands/plugin/export.ts index c3beb8b23e..6de3e14c59 100644 --- a/packages/cli/src/commands/plugin/export.ts +++ b/packages/cli/src/commands/plugin/export.ts @@ -22,6 +22,7 @@ import { buildBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ + env: 'production', rootPaths: [paths.targetDir, paths.targetRoot], }); await buildBundle({ diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts index 989cf92523..c23ae060aa 100644 --- a/packages/cli/src/commands/plugin/serve.ts +++ b/packages/cli/src/commands/plugin/serve.ts @@ -22,6 +22,7 @@ import { serveBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ + env: 'development', rootPaths: [paths.targetDir, paths.targetRoot], }); const waitForExit = await serveBundle({ diff --git a/packages/config-loader/src/lib/resolver.ts b/packages/config-loader/src/lib/resolver.ts index c1f95cdef7..2ee200fbd0 100644 --- a/packages/config-loader/src/lib/resolver.ts +++ b/packages/config-loader/src/lib/resolver.ts @@ -20,6 +20,8 @@ import { pathExists } from 'fs-extra'; type ResolveOptions = { // Root paths to search for config files. Config from earlier paths has higher priority. rootPaths: string[]; + // The environment that we're loading config for, e.g. 'development', 'production'. + env: string; }; /** @@ -35,11 +37,9 @@ type ResolveOptions = { export async function resolveStaticConfig( options: ResolveOptions, ): Promise { - const env = process.env.NODE_ENV ?? 'development'; - const filePaths = [ - `app-config.${env}.local.yaml`, - `app-config.${env}.yaml`, + `app-config.${options.env}.local.yaml`, + `app-config.${options.env}.yaml`, `app-config.local.yaml`, `app-config.yaml`, ]; diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index ba3c615ff7..27df7ac831 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -28,6 +28,9 @@ export type LoadConfigOptions = { // Root paths to search for config files. Config from earlier paths has higher priority. rootPaths: string[]; + // The environment that we're loading config for, e.g. 'development', 'production'. + env: string; + // Whether to read secrets or omit them, defaults to false. shouldReadSecrets?: boolean; }; From f7532bcf1cbadd518a5f38ad8f0b9228e86a20ac Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Aug 2020 11:05:04 +0200 Subject: [PATCH 4/5] config-loader: added tests for resolver --- .../config-loader/src/lib/resolver.test.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 packages/config-loader/src/lib/resolver.test.ts diff --git a/packages/config-loader/src/lib/resolver.test.ts b/packages/config-loader/src/lib/resolver.test.ts new file mode 100644 index 0000000000..78e869e612 --- /dev/null +++ b/packages/config-loader/src/lib/resolver.test.ts @@ -0,0 +1,116 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const pathExists = jest.fn(); + +jest.mock('fs-extra', () => ({ pathExists })); + +import { resolveStaticConfig } from './resolver'; + +describe('resolveStaticConfig', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should resolve no files for empty roots', async () => { + const resolved = await resolveStaticConfig({ + env: 'development', + rootPaths: [], + }); + + expect(resolved).toEqual([]); + expect(pathExists).not.toHaveBeenCalled(); + }); + + it('should resolve a single app-config', async () => { + pathExists.mockImplementation(async (path: string) => + ['/repo/app-config.yaml'].includes(path), + ); + const resolved = await resolveStaticConfig({ + env: 'development', + rootPaths: ['/repo'], + }); + + expect(resolved).toEqual(['/repo/app-config.yaml']); + expect(pathExists).toHaveBeenCalledTimes(4); + }); + + it('should resolve a app-configs in different directories', async () => { + pathExists.mockImplementation(async (path: string) => + ['/repo/app-config.yaml', '/repo/packages/a/app-config.yaml'].includes( + path, + ), + ); + const resolved = await resolveStaticConfig({ + env: 'development', + rootPaths: [ + '/repo/packages/a', + '/repo/packages/b', + '/other-repo', + '/repo', + ], + }); + + expect(resolved).toEqual([ + '/repo/packages/a/app-config.yaml', + '/repo/app-config.yaml', + ]); + expect(pathExists).toHaveBeenCalledTimes(16); + }); + + it('should resolve env and local configs', async () => { + pathExists.mockImplementation(async (path: string) => + [ + '/repo/app-config.yaml', + '/repo/app-config.local.yaml', + '/repo/app-config.production.yaml', + '/repo/app-config.production.local.yaml', + '/repo/app-config.development.local.yaml', + '/repo/packages/a/app-config.development.yaml', + '/repo/packages/a/app-config.local.yaml', + ].includes(path), + ); + const resolved = await resolveStaticConfig({ + env: 'development', + rootPaths: ['/repo/packages/a', '/repo'], + }); + + expect(resolved).toEqual([ + '/repo/packages/a/app-config.development.yaml', + '/repo/packages/a/app-config.local.yaml', + '/repo/app-config.development.local.yaml', + '/repo/app-config.local.yaml', + '/repo/app-config.yaml', + ]); + expect(pathExists).toHaveBeenCalledTimes(8); + }); + + it('resolves suffixed configs in the correct order', async () => { + pathExists.mockImplementation(async () => true); + const resolved = await resolveStaticConfig({ + env: 'production', + rootPaths: ['/repo'], + }); + + expect(resolved).toEqual([ + '/repo/app-config.production.local.yaml', + '/repo/app-config.production.yaml', + '/repo/app-config.local.yaml', + '/repo/app-config.yaml', + ]); + expect(pathExists).toHaveBeenCalledTimes(4); + }); +}); From 1f2216fd772340e217770a61ee3a32712884de3a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Aug 2020 11:27:46 +0200 Subject: [PATCH 5/5] config: flip priority order of ConfigReader.fromConfigs --- packages/backend-common/src/config.ts | 2 +- packages/cli/src/commands/app/build.ts | 2 +- packages/cli/src/commands/app/serve.ts | 2 +- packages/cli/src/commands/backend/dev.ts | 2 +- packages/cli/src/commands/plugin/export.ts | 2 +- packages/cli/src/commands/plugin/serve.ts | 2 +- .../config-loader/src/lib/resolver.test.ts | 22 +++--- packages/config-loader/src/lib/resolver.ts | 8 +- packages/config-loader/src/loader.ts | 6 +- packages/config/src/reader.test.ts | 74 +++++++++---------- packages/config/src/reader.ts | 13 ++-- .../core/src/api-wrappers/createApp.test.tsx | 2 +- packages/core/src/api-wrappers/createApp.tsx | 2 +- 13 files changed, 68 insertions(+), 71 deletions(-) diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 2295a8f943..6f7500bb86 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -24,7 +24,7 @@ export async function loadBackendConfig() { const paths = findPaths(__dirname); const configs = await loadConfig({ env: process.env.NODE_ENV, - rootPaths: [paths.targetDir, paths.targetRoot], + rootPaths: [paths.targetRoot, paths.targetDir], shouldReadSecrets: true, }); return configs; diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index 12ea362063..21d6924b15 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -23,7 +23,7 @@ import { buildBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ env: 'production', - rootPaths: [paths.targetDir, paths.targetRoot], + rootPaths: [paths.targetRoot, paths.targetDir], }); await buildBundle({ entry: 'src/index', diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts index b372663f09..a04e73dceb 100644 --- a/packages/cli/src/commands/app/serve.ts +++ b/packages/cli/src/commands/app/serve.ts @@ -23,7 +23,7 @@ import { serveBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ env: 'development', - rootPaths: [paths.targetDir, paths.targetRoot], + rootPaths: [paths.targetRoot, paths.targetDir], }); const waitForExit = await serveBundle({ entry: 'src/index', diff --git a/packages/cli/src/commands/backend/dev.ts b/packages/cli/src/commands/backend/dev.ts index cbea2c60cc..91c08af201 100644 --- a/packages/cli/src/commands/backend/dev.ts +++ b/packages/cli/src/commands/backend/dev.ts @@ -23,7 +23,7 @@ import { serveBackend } from '../../lib/bundler/backend'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ env: 'development', - rootPaths: [paths.targetDir, paths.targetRoot], + rootPaths: [paths.targetRoot, paths.targetDir], }); const waitForExit = await serveBackend({ entry: 'src/index', diff --git a/packages/cli/src/commands/plugin/export.ts b/packages/cli/src/commands/plugin/export.ts index 6de3e14c59..8f3c132285 100644 --- a/packages/cli/src/commands/plugin/export.ts +++ b/packages/cli/src/commands/plugin/export.ts @@ -23,7 +23,7 @@ import { buildBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ env: 'production', - rootPaths: [paths.targetDir, paths.targetRoot], + rootPaths: [paths.targetRoot, paths.targetDir], }); await buildBundle({ entry: 'dev/index', diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts index c23ae060aa..a677bec918 100644 --- a/packages/cli/src/commands/plugin/serve.ts +++ b/packages/cli/src/commands/plugin/serve.ts @@ -23,7 +23,7 @@ import { serveBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ env: 'development', - rootPaths: [paths.targetDir, paths.targetRoot], + rootPaths: [paths.targetRoot, paths.targetDir], }); const waitForExit = await serveBundle({ entry: 'dev/index', diff --git a/packages/config-loader/src/lib/resolver.test.ts b/packages/config-loader/src/lib/resolver.test.ts index 78e869e612..c804a6cef3 100644 --- a/packages/config-loader/src/lib/resolver.test.ts +++ b/packages/config-loader/src/lib/resolver.test.ts @@ -57,16 +57,16 @@ describe('resolveStaticConfig', () => { const resolved = await resolveStaticConfig({ env: 'development', rootPaths: [ + '/repo', + '/other-repo', '/repo/packages/a', '/repo/packages/b', - '/other-repo', - '/repo', ], }); expect(resolved).toEqual([ - '/repo/packages/a/app-config.yaml', '/repo/app-config.yaml', + '/repo/packages/a/app-config.yaml', ]); expect(pathExists).toHaveBeenCalledTimes(16); }); @@ -85,15 +85,15 @@ describe('resolveStaticConfig', () => { ); const resolved = await resolveStaticConfig({ env: 'development', - rootPaths: ['/repo/packages/a', '/repo'], + rootPaths: ['/repo', '/repo/packages/a'], }); expect(resolved).toEqual([ - '/repo/packages/a/app-config.development.yaml', - '/repo/packages/a/app-config.local.yaml', - '/repo/app-config.development.local.yaml', - '/repo/app-config.local.yaml', '/repo/app-config.yaml', + '/repo/app-config.local.yaml', + '/repo/app-config.development.local.yaml', + '/repo/packages/a/app-config.local.yaml', + '/repo/packages/a/app-config.development.yaml', ]); expect(pathExists).toHaveBeenCalledTimes(8); }); @@ -106,10 +106,10 @@ describe('resolveStaticConfig', () => { }); expect(resolved).toEqual([ - '/repo/app-config.production.local.yaml', - '/repo/app-config.production.yaml', - '/repo/app-config.local.yaml', '/repo/app-config.yaml', + '/repo/app-config.local.yaml', + '/repo/app-config.production.yaml', + '/repo/app-config.production.local.yaml', ]); expect(pathExists).toHaveBeenCalledTimes(4); }); diff --git a/packages/config-loader/src/lib/resolver.ts b/packages/config-loader/src/lib/resolver.ts index 2ee200fbd0..44f0f6ea9e 100644 --- a/packages/config-loader/src/lib/resolver.ts +++ b/packages/config-loader/src/lib/resolver.ts @@ -18,7 +18,7 @@ import { resolve as resolvePath } from 'path'; import { pathExists } from 'fs-extra'; type ResolveOptions = { - // Root paths to search for config files. Config from earlier paths has higher priority. + // Root paths to search for config files. Config from earlier paths has lower priority. rootPaths: string[]; // The environment that we're loading config for, e.g. 'development', 'production'. env: string; @@ -38,10 +38,10 @@ export async function resolveStaticConfig( options: ResolveOptions, ): Promise { const filePaths = [ - `app-config.${options.env}.local.yaml`, - `app-config.${options.env}.yaml`, - `app-config.local.yaml`, `app-config.yaml`, + `app-config.local.yaml`, + `app-config.${options.env}.yaml`, + `app-config.${options.env}.local.yaml`, ]; const resolvedPaths = []; diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 27df7ac831..0cc8ac29f5 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -25,7 +25,7 @@ import { } from './lib'; export type LoadConfigOptions = { - // Root paths to search for config files. Config from earlier paths has higher priority. + // Root paths to search for config files. Config from earlier paths has lower priority. rootPaths: string[]; // The environment that we're loading config for, e.g. 'development', 'production'. @@ -66,8 +66,6 @@ export async function loadConfig( ): Promise { const configs = []; - configs.push(...readEnv(process.env)); - const configPaths = await resolveStaticConfig(options); try { @@ -89,5 +87,7 @@ export async function loadConfig( ); } + configs.push(...readEnv(process.env)); + return configs; } diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index 9d06e0b025..9cfc1d7f34 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -214,9 +214,19 @@ describe('ConfigReader with fallback', () => { const config = ConfigReader.fromConfigs([ { data: { + a: true, + b: true, c: true, + nested1: { + a: true, + b: true, + }, + badBefore: { + a: true, + }, + badAfter: true, }, - context: 'x', + context: 'z', }, { data: { @@ -234,19 +244,9 @@ describe('ConfigReader with fallback', () => { }, { data: { - a: true, - b: true, c: true, - nested1: { - a: true, - b: true, - }, - badBefore: { - a: true, - }, - badAfter: true, }, - context: 'z', + context: 'x', }, ]); @@ -427,42 +427,42 @@ describe('ConfigReader.get()', () => { }); it('should merge in fallback configs', () => { - expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('a')).toEqual( + expect(ConfigReader.fromConfigs([configs[1], configs[0]]).get('a')).toEqual( { x: 'x1', y: ['y11', 'y12', 'y13'], z: false, }, ); - expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('b')).toEqual( + expect(ConfigReader.fromConfigs([configs[1], configs[0]]).get('b')).toEqual( { x: 'x1', y: ['y11'], z: 'z2', }, ); - expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('c')).toEqual( + expect(ConfigReader.fromConfigs([configs[1], configs[0]]).get('c')).toEqual( { c1: { c2: 'c2', }, }, ); - expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('a')).toEqual( + expect(ConfigReader.fromConfigs([configs[1], configs[0]]).get('a')).toEqual( { x: 'x1', y: ['y11', 'y12', 'y13'], z: false, }, ); - expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('b')).toEqual( + expect(ConfigReader.fromConfigs([configs[1], configs[0]]).get('b')).toEqual( { x: 'x1', y: ['y11'], z: 'z2', }, ); - expect(ConfigReader.fromConfigs([configs[0], configs[1]]).get('c')).toEqual( + expect(ConfigReader.fromConfigs([configs[1], configs[0]]).get('c')).toEqual( { c1: { c2: 'c2', @@ -471,14 +471,14 @@ describe('ConfigReader.get()', () => { ); expect( - ConfigReader.fromConfigs([configs[2], configs[1]]).getOptional('b'), + ConfigReader.fromConfigs([configs[1], configs[2]]).getOptional('b'), ).toEqual({ x: 'x2', y: ['y21', 'y22'], z: 'z2', }); expect( - ConfigReader.fromConfigs([configs[2], configs[1]]).getOptional('c'), + ConfigReader.fromConfigs([configs[1], configs[2]]).getOptional('c'), ).toEqual({ c1: 'c1', }); @@ -486,23 +486,6 @@ describe('ConfigReader.get()', () => { it('should not merge non-objects', () => { const config = ConfigReader.fromConfigs([ - { - data: { - a: ['1', '2'], - c: [], - d: { - x: 'x', - }, - e: ['3'], - f: 'foo', - g: { z: 'z' }, - h: { - a: 'a1', - c: 'c1', - }, - }, - context: '1', - }, { data: { a: ['x', 'y', 'z'], @@ -521,6 +504,23 @@ describe('ConfigReader.get()', () => { }, context: '2', }, + { + data: { + a: ['1', '2'], + c: [], + d: { + x: 'x', + }, + e: ['3'], + f: 'foo', + g: { z: 'z' }, + h: { + a: 'a1', + c: 'c1', + }, + }, + context: '1', + }, ]); expect(config.get('a')).toEqual(['1', '2']); expect(config.get('b')).toEqual(['1']); diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index 0669bcb6d8..86fb125a47 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -57,14 +57,11 @@ export class ConfigReader implements Config { return new ConfigReader(undefined); } - // Merge together all configs info a single config with recursive fallback - // readers, giving the first config object in the array the highest priority. - return configs.reduceRight( - (previousReader, { data, context }) => { - return new ConfigReader(data, context, previousReader); - }, - undefined!, - ); + // Merge together all configs into a single config with recursive fallback + // readers, giving the first config object in the array the lowest priority. + return configs.reduce((previousReader, { data, context }) => { + return new ConfigReader(data, context, previousReader); + }, undefined!); } constructor( diff --git a/packages/core/src/api-wrappers/createApp.test.tsx b/packages/core/src/api-wrappers/createApp.test.tsx index 0a3756ad92..33623b4869 100644 --- a/packages/core/src/api-wrappers/createApp.test.tsx +++ b/packages/core/src/api-wrappers/createApp.test.tsx @@ -49,9 +49,9 @@ describe('defaultConfigLoader', () => { '{"my":"runtime-config"}', ); expect(configs).toEqual([ - { data: { my: 'runtime-config' }, context: 'env' }, { data: { my: 'override-config' }, context: 'a' }, { data: { my: 'config' }, context: 'b' }, + { data: { my: 'runtime-config' }, context: 'env' }, ]); }); diff --git a/packages/core/src/api-wrappers/createApp.tsx b/packages/core/src/api-wrappers/createApp.tsx index 60c69d01c5..9d2096cfff 100644 --- a/packages/core/src/api-wrappers/createApp.tsx +++ b/packages/core/src/api-wrappers/createApp.tsx @@ -60,7 +60,7 @@ export const defaultConfigLoader: AppConfigLoader = async ( if (runtimeConfigJson !== '__app_injected_runtime_config__'.toUpperCase()) { try { const data = JSON.parse(runtimeConfigJson) as JsonObject; - configs.unshift({ data, context: 'env' }); + configs.push({ data, context: 'env' }); } catch (error) { throw new Error(`Failed to load runtime configuration, ${error}`); }