From 77d8fb7893881b3072b3c148cb256409955e8b96 Mon Sep 17 00:00:00 2001 From: Josh Gummersall <1235378+joshgummersall@users.noreply.github.com> Date: Thu, 6 May 2021 14:45:01 -0700 Subject: [PATCH] fix: config file overrides (#3660) Fixes #3659 --- .../generated/luis.settings.mocha.westus.json | 3 +- .../package.json | 4 +-- .../src/configuration.ts | 30 ++++++++++++++++++- .../src/index.ts | 13 ++++---- .../test/appsettings.json | 3 +- .../test/index.test.ts | 3 ++ yarn.lock | 4 +-- 7 files changed, 48 insertions(+), 12 deletions(-) diff --git a/libraries/botbuilder-dialogs-adaptive-runtime/generated/luis.settings.mocha.westus.json b/libraries/botbuilder-dialogs-adaptive-runtime/generated/luis.settings.mocha.westus.json index 45f8f655d..de92ce57b 100644 --- a/libraries/botbuilder-dialogs-adaptive-runtime/generated/luis.settings.mocha.westus.json +++ b/libraries/botbuilder-dialogs-adaptive-runtime/generated/luis.settings.mocha.westus.json @@ -1,5 +1,6 @@ { "luis": { - "fancySetting": "fancyValue" + "fancySetting": "fancyValue", + "override": "new value" } } diff --git a/libraries/botbuilder-dialogs-adaptive-runtime/package.json b/libraries/botbuilder-dialogs-adaptive-runtime/package.json index f34154080..3c9af0ed6 100644 --- a/libraries/botbuilder-dialogs-adaptive-runtime/package.json +++ b/libraries/botbuilder-dialogs-adaptive-runtime/package.json @@ -40,12 +40,12 @@ "botbuilder-dialogs-declarative": "4.1.6", "botframework-connector": "4.1.6", "dependency-graph": "^0.10.0", - "nconf": "^0.11.2", + "nconf": "0.11.2", "runtypes": "~6.3.0", "yargs-parser": "^20.2.7" }, "devDependencies": { - "@types/nconf": "^0.10.0", + "@types/nconf": "0.10.0", "mocha": "^8.2.1", "nyc": "^15.1.0" }, diff --git a/libraries/botbuilder-dialogs-adaptive-runtime/src/configuration.ts b/libraries/botbuilder-dialogs-adaptive-runtime/src/configuration.ts index e89f86301..c6b4f0ca5 100644 --- a/libraries/botbuilder-dialogs-adaptive-runtime/src/configuration.ts +++ b/libraries/botbuilder-dialogs-adaptive-runtime/src/configuration.ts @@ -91,10 +91,38 @@ export class Configuration implements CoreConfiguration { * Load a file as a configuration source. * * @param name file name + * @param override optional flag that ensures this file takes precedence over other files * @returns this for chaining */ - file(name: string): this { + file(name: string, override = false): this { this.provider.file(name, name); + + // If we are given a key to override, we need to reach into the nconf provider and rearrange things. + // The nconf provider maintains an object that maps names to stores. When looking up a key, nconf iterates + // through the stores in insertion order and returns the first value it finds. This is not ideal because, + // in order to rearrange stores, we have to essentially reconstruct the object so the insertion order is + // correct. So this code does that. + if (override) { + // Construct list of entries in current insertion order + const stores = this.provider.stores ?? {}; + const entries = Object.entries>(stores); + + // Locate store to override, if it exists + const index = entries.findIndex(([, store]) => store.type === 'file'); + + // If store exists, we need to remove the store we just added, splice it into entries, and then reduce + // it back into an object. + if (index !== -1) { + // insert this store before the store to override + entries.splice(index, 0, [name, stores[name]]); + + // slice this store from end of list, then reduce back into object + this.provider.stores = entries + .slice(0, entries.length - 1) + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); + } + } + return this; } diff --git a/libraries/botbuilder-dialogs-adaptive-runtime/src/index.ts b/libraries/botbuilder-dialogs-adaptive-runtime/src/index.ts index 4a7dff10d..c78e94921 100644 --- a/libraries/botbuilder-dialogs-adaptive-runtime/src/index.ts +++ b/libraries/botbuilder-dialogs-adaptive-runtime/src/index.ts @@ -353,6 +353,7 @@ async function addSettingsBotComponents(services: ServiceCollection, configurati } } +// Note: any generated files take precedence over `appsettings.json`. function addComposerConfiguration(configuration: Configuration): void { const botRoot = configuration.string(['bot']) ?? '.'; configuration.set(['BotRoot'], botRoot); @@ -374,12 +375,12 @@ function addComposerConfiguration(configuration: Configuration): void { environment = userName; } - configuration.file(path.join(botRoot, 'generated', `luis.settings.${environment}.${luisRegion}.json`)); + configuration.file(path.join(botRoot, 'generated', `luis.settings.${environment}.${luisRegion}.json`), true); const qnaRegion = configuration.string(['qna', 'qnaRegion']) ?? 'westus'; - configuration.file(path.join(botRoot, 'generated', `qnamaker.settings.${environment}.${qnaRegion}.json`)); + configuration.file(path.join(botRoot, 'generated', `qnamaker.settings.${environment}.${qnaRegion}.json`), true); - configuration.file(path.join(botRoot, 'generated', `orchestrator.settings.json`)); + configuration.file(path.join(botRoot, 'generated', `orchestrator.settings.json`), true); } async function normalizeConfiguration(configuration: Configuration, applicationRoot: string): Promise { @@ -461,11 +462,13 @@ export async function getRuntimeServices( } files.forEach((file) => configuration.file(path.join(configurationOrSettingsDirectory, file))); + + await normalizeConfiguration(configuration, applicationRoot); } else { configuration = configurationOrSettingsDirectory; - } - await normalizeConfiguration(configuration, applicationRoot); + await normalizeConfiguration(configuration, applicationRoot); + } const services = new ServiceCollection({ customAdapters: new Map(), diff --git a/libraries/botbuilder-dialogs-adaptive-runtime/test/appsettings.json b/libraries/botbuilder-dialogs-adaptive-runtime/test/appsettings.json index f46393b33..8d1e08886 100644 --- a/libraries/botbuilder-dialogs-adaptive-runtime/test/appsettings.json +++ b/libraries/botbuilder-dialogs-adaptive-runtime/test/appsettings.json @@ -1,5 +1,6 @@ { "luis": { - "environment": "mocha" + "environment": "mocha", + "override": "old value" } } diff --git a/libraries/botbuilder-dialogs-adaptive-runtime/test/index.test.ts b/libraries/botbuilder-dialogs-adaptive-runtime/test/index.test.ts index 015f32856..f8f177953 100644 --- a/libraries/botbuilder-dialogs-adaptive-runtime/test/index.test.ts +++ b/libraries/botbuilder-dialogs-adaptive-runtime/test/index.test.ts @@ -33,6 +33,9 @@ describe('getRuntimeServices', function () { // Ensure that a setting in a generated file is merged in strictEqual(configuration.string(['luis', 'fancySetting']), 'fancyValue'); + + // Ensure that a setting in a generated file takes precedence over appsettings + strictEqual(configuration.string(['luis', 'override']), 'new value'); }); it('supports bot components and late binding configuration', async function () { diff --git a/yarn.lock b/yarn.lock index d41ac046c..8eda8ede5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1619,7 +1619,7 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.0.tgz#3eb56d13a1de1d347ecb1957c6860c911704bc44" integrity sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ== -"@types/nconf@^0.10.0": +"@types/nconf@0.10.0": version "0.10.0" resolved "https://registry.yarnpkg.com/@types/nconf/-/nconf-0.10.0.tgz#a5c9753a09c59d44c8e6dc94b57d73f70eb12ebe" integrity sha512-Qh0/DWkz7fQm5h+IPFBIO5ixaFdv86V6gpbA8TPA1hhgXYtzGviv9yriqN1B+KTtmLweemKZD5XxY1cTAQPNMg== @@ -8947,7 +8947,7 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -nconf@^0.11.2: +nconf@0.11.2: version "0.11.2" resolved "https://registry.yarnpkg.com/nconf/-/nconf-0.11.2.tgz#707fa9db383e85ad7e8f1a17be1b053d1bd751c4" integrity sha512-gDmn0Fgt0U0esRE8OCF72tO8AA9dtlG9eZhW4/Ex5hozNC2/LgdhWO4vKLGHNfTxcvsv6Aoxk/ROVYJD2SAdyg==