@backstage/config-loader 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/index.cjs.js +99 -9
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +31 -11
- package/dist/index.esm.js +98 -9
- package/dist/index.esm.js.map +1 -1
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
package/dist/index.cjs.js
CHANGED
|
@@ -12,6 +12,7 @@ var config = require('@backstage/config');
|
|
|
12
12
|
var fs = require('fs-extra');
|
|
13
13
|
var typescriptJsonSchema = require('typescript-json-schema');
|
|
14
14
|
var chokidar = require('chokidar');
|
|
15
|
+
var fetch = require('node-fetch');
|
|
15
16
|
|
|
16
17
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
17
18
|
|
|
@@ -21,6 +22,7 @@ var mergeAllOf__default = /*#__PURE__*/_interopDefaultLegacy(mergeAllOf);
|
|
|
21
22
|
var traverse__default = /*#__PURE__*/_interopDefaultLegacy(traverse);
|
|
22
23
|
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
23
24
|
var chokidar__default = /*#__PURE__*/_interopDefaultLegacy(chokidar);
|
|
25
|
+
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
24
26
|
|
|
25
27
|
const ENV_PREFIX = "APP_CONFIG_";
|
|
26
28
|
const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;
|
|
@@ -566,10 +568,28 @@ async function loadConfigSchema(options) {
|
|
|
566
568
|
};
|
|
567
569
|
}
|
|
568
570
|
|
|
571
|
+
function isValidUrl(url) {
|
|
572
|
+
try {
|
|
573
|
+
new URL(url);
|
|
574
|
+
return true;
|
|
575
|
+
} catch {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
569
580
|
async function loadConfig(options) {
|
|
570
|
-
const {configRoot, experimentalEnvFunc: envFunc, watch} = options;
|
|
571
|
-
const configPaths = options.
|
|
572
|
-
|
|
581
|
+
const {configRoot, experimentalEnvFunc: envFunc, watch, remote} = options;
|
|
582
|
+
const configPaths = options.configTargets.slice().filter((e) => e.hasOwnProperty("path")).map((configTarget) => configTarget.path);
|
|
583
|
+
options.configPaths.forEach((cp) => {
|
|
584
|
+
if (!configPaths.includes(cp)) {
|
|
585
|
+
configPaths.push(cp);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
const configUrls = options.configTargets.slice().filter((e) => e.hasOwnProperty("url")).map((configTarget) => configTarget.url);
|
|
589
|
+
if (remote === void 0 && configUrls.length > 0) {
|
|
590
|
+
throw new Error(`Remote config detected but this feature is turned off`);
|
|
591
|
+
}
|
|
592
|
+
if (configPaths.length === 0 && configUrls.length === 0) {
|
|
573
593
|
configPaths.push(path.resolve(configRoot, "app-config.yaml"));
|
|
574
594
|
const localConfig = path.resolve(configRoot, "app-config.local.yaml");
|
|
575
595
|
if (await fs__default['default'].pathExists(localConfig)) {
|
|
@@ -595,18 +615,53 @@ async function loadConfig(options) {
|
|
|
595
615
|
}
|
|
596
616
|
return configs;
|
|
597
617
|
};
|
|
618
|
+
const loadRemoteConfigFiles = async () => {
|
|
619
|
+
const configs = [];
|
|
620
|
+
const readConfigFromUrl = async (url) => {
|
|
621
|
+
const response = await fetch__default['default'](url);
|
|
622
|
+
if (!response.ok) {
|
|
623
|
+
throw new Error(`Could not read config file at ${url}`);
|
|
624
|
+
}
|
|
625
|
+
return await response.text();
|
|
626
|
+
};
|
|
627
|
+
for (let i = 0; i < configUrls.length; i++) {
|
|
628
|
+
const configUrl = configUrls[i];
|
|
629
|
+
if (!isValidUrl(configUrl)) {
|
|
630
|
+
throw new Error(`Config load path is not valid: '${configUrl}'`);
|
|
631
|
+
}
|
|
632
|
+
const remoteConfigContent = await readConfigFromUrl(configUrl);
|
|
633
|
+
if (!remoteConfigContent) {
|
|
634
|
+
throw new Error(`Config is not valid`);
|
|
635
|
+
}
|
|
636
|
+
const configYaml = yaml__default['default'].parse(remoteConfigContent);
|
|
637
|
+
const substitutionTransform = createSubstitutionTransform(env);
|
|
638
|
+
const data = await applyConfigTransforms(configRoot, configYaml, [
|
|
639
|
+
substitutionTransform
|
|
640
|
+
]);
|
|
641
|
+
configs.push({data, context: configUrl});
|
|
642
|
+
}
|
|
643
|
+
return configs;
|
|
644
|
+
};
|
|
598
645
|
let fileConfigs;
|
|
599
646
|
try {
|
|
600
647
|
fileConfigs = await loadConfigFiles();
|
|
601
648
|
} catch (error) {
|
|
602
649
|
throw new errors.ForwardedError("Failed to read static configuration file", error);
|
|
603
650
|
}
|
|
651
|
+
let remoteConfigs = [];
|
|
652
|
+
if (remote) {
|
|
653
|
+
try {
|
|
654
|
+
remoteConfigs = await loadRemoteConfigFiles();
|
|
655
|
+
} catch (error) {
|
|
656
|
+
throw new errors.ForwardedError(`Failed to read remote configuration file`, error);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
604
659
|
const envConfigs = await readEnvConfig(process.env);
|
|
605
|
-
|
|
606
|
-
let currentSerializedConfig = JSON.stringify(fileConfigs);
|
|
660
|
+
const watchConfigFile = (watchProp) => {
|
|
607
661
|
const watcher = chokidar__default['default'].watch(configPaths, {
|
|
608
662
|
usePolling: process.env.NODE_ENV === "test"
|
|
609
663
|
});
|
|
664
|
+
let currentSerializedConfig = JSON.stringify(fileConfigs);
|
|
610
665
|
watcher.on("change", async () => {
|
|
611
666
|
try {
|
|
612
667
|
const newConfigs = await loadConfigFiles();
|
|
@@ -615,18 +670,53 @@ async function loadConfig(options) {
|
|
|
615
670
|
return;
|
|
616
671
|
}
|
|
617
672
|
currentSerializedConfig = newSerializedConfig;
|
|
618
|
-
|
|
673
|
+
watchProp.onChange([...remoteConfigs, ...newConfigs, ...envConfigs]);
|
|
619
674
|
} catch (error) {
|
|
620
675
|
console.error(`Failed to reload configuration files, ${error}`);
|
|
621
676
|
}
|
|
622
677
|
});
|
|
623
|
-
if (
|
|
624
|
-
|
|
678
|
+
if (watchProp.stopSignal) {
|
|
679
|
+
watchProp.stopSignal.then(() => {
|
|
625
680
|
watcher.close();
|
|
626
681
|
});
|
|
627
682
|
}
|
|
683
|
+
};
|
|
684
|
+
const watchRemoteConfig = (watchProp, remoteProp) => {
|
|
685
|
+
const hasConfigChanged = async (oldRemoteConfigs, newRemoteConfigs) => {
|
|
686
|
+
return JSON.stringify(oldRemoteConfigs) !== JSON.stringify(newRemoteConfigs);
|
|
687
|
+
};
|
|
688
|
+
let handle;
|
|
689
|
+
try {
|
|
690
|
+
handle = setInterval(async () => {
|
|
691
|
+
console.info(`Checking for config update`);
|
|
692
|
+
const newRemoteConfigs = await loadRemoteConfigFiles();
|
|
693
|
+
if (await hasConfigChanged(remoteConfigs, newRemoteConfigs)) {
|
|
694
|
+
remoteConfigs = newRemoteConfigs;
|
|
695
|
+
console.info(`Remote config change, reloading config ...`);
|
|
696
|
+
watchProp.onChange([...remoteConfigs, ...fileConfigs, ...envConfigs]);
|
|
697
|
+
console.info(`Remote config reloaded`);
|
|
698
|
+
}
|
|
699
|
+
}, remoteProp.reloadIntervalSeconds * 1e3);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
console.error(`Failed to reload configuration files, ${error}`);
|
|
702
|
+
}
|
|
703
|
+
if (watchProp.stopSignal) {
|
|
704
|
+
watchProp.stopSignal.then(() => {
|
|
705
|
+
if (handle !== void 0) {
|
|
706
|
+
console.info(`Stopping remote config watch`);
|
|
707
|
+
clearInterval(handle);
|
|
708
|
+
handle = void 0;
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
if (watch) {
|
|
714
|
+
watchConfigFile(watch);
|
|
715
|
+
}
|
|
716
|
+
if (watch && remote) {
|
|
717
|
+
watchRemoteConfig(watch, remote);
|
|
628
718
|
}
|
|
629
|
-
return [...fileConfigs, ...envConfigs];
|
|
719
|
+
return remote ? [...remoteConfigs, ...fileConfigs, ...envConfigs] : [...fileConfigs, ...envConfigs];
|
|
630
720
|
}
|
|
631
721
|
|
|
632
722
|
exports.loadConfig = loadConfig;
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/lib/env.ts","../src/lib/transform/utils.ts","../src/lib/transform/apply.ts","../src/lib/transform/include.ts","../src/lib/transform/substitution.ts","../src/lib/schema/types.ts","../src/lib/schema/compile.ts","../src/lib/schema/collect.ts","../src/lib/schema/filtering.ts","../src/lib/schema/load.ts","../src/loader.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\nconst ENV_PREFIX = 'APP_CONFIG_';\n\n// Update the same pattern in config package if this is changed\nconst CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;\n\n/**\n * Read runtime configuration from the environment.\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n */\nexport function readEnvConfig(env: {\n [name: string]: string | undefined;\n}): AppConfig[] {\n let data: JsonObject | undefined = undefined;\n\n for (const [name, value] of Object.entries(env)) {\n if (!value) {\n continue;\n }\n if (name.startsWith(ENV_PREFIX)) {\n const key = name.replace(ENV_PREFIX, '');\n const keyParts = key.split('_');\n\n let obj = (data = data ?? {});\n for (const [index, part] of keyParts.entries()) {\n if (!CONFIG_KEY_PART_PATTERN.test(part)) {\n throw new TypeError(`Invalid env config key '${key}'`);\n }\n if (index < keyParts.length - 1) {\n obj = (obj[part] = obj[part] ?? {}) as JsonObject;\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n const subKey = keyParts.slice(0, index + 1).join('_');\n throw new TypeError(\n `Could not nest config for key '${key}' under existing value '${subKey}'`,\n );\n }\n } else {\n if (part in obj) {\n throw new TypeError(\n `Refusing to override existing config at key '${key}'`,\n );\n }\n try {\n const [, parsedValue] = safeJsonParse(value);\n if (parsedValue === null) {\n throw new Error('value may not be null');\n }\n obj[part] = parsedValue;\n } catch (error) {\n throw new TypeError(\n `Failed to parse JSON-serialized config value for key '${key}', ${error}`,\n );\n }\n }\n }\n }\n }\n\n return data ? [{ data, context: 'env' }] : [];\n}\n\nfunction safeJsonParse(str: string): [Error | null, any] {\n try {\n return [null, JSON.parse(str)];\n } catch (err) {\n assertError(err);\n return [err, str];\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue, JsonObject } from '@backstage/types';\n\nexport function isObject(obj: JsonValue | undefined): obj is JsonObject {\n if (typeof obj !== 'object') {\n return false;\n } else if (Array.isArray(obj)) {\n return false;\n }\n return obj !== null;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\nimport { TransformFunc } from './types';\nimport { isObject } from './utils';\n\n/**\n * Applies a set of transforms to raw configuration data.\n */\nexport async function applyConfigTransforms(\n initialDir: string,\n input: JsonValue,\n transforms: TransformFunc[],\n): Promise<JsonObject> {\n async function transform(\n inputObj: JsonValue,\n path: string,\n baseDir: string,\n ): Promise<JsonValue | undefined> {\n let obj = inputObj;\n let dir = baseDir;\n\n for (const tf of transforms) {\n try {\n const result = await tf(inputObj, baseDir);\n if (result.applied) {\n if (result.value === undefined) {\n return undefined;\n }\n obj = result.value;\n dir = result.newBaseDir ?? dir;\n break;\n }\n } catch (error) {\n assertError(error);\n throw new Error(`error at ${path}, ${error.message}`);\n }\n }\n\n if (typeof obj !== 'object') {\n return obj;\n } else if (obj === null) {\n return undefined;\n } else if (Array.isArray(obj)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of obj.entries()) {\n const out = await transform(value, `${path}[${index}]`, dir);\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n return arr;\n }\n\n const out: JsonObject = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // undefined covers optional fields\n if (value !== undefined) {\n const result = await transform(value, `${path}.${key}`, dir);\n if (result !== undefined) {\n out[key] = result;\n }\n }\n }\n\n return out;\n }\n\n const finalData = await transform(input, '', initialDir);\n if (!isObject(finalData)) {\n throw new TypeError('expected object at config root');\n }\n return finalData;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport yaml from 'yaml';\nimport { extname, dirname, resolve as resolvePath } from 'path';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { isObject } from './utils';\nimport { TransformFunc, EnvFunc, ReadFileFunc } from './types';\n\n// Parsers for each type of included file\nconst includeFileParser: {\n [ext in string]: (content: string) => Promise<JsonObject>;\n} = {\n '.json': async content => JSON.parse(content),\n '.yaml': async content => yaml.parse(content),\n '.yml': async content => yaml.parse(content),\n};\n\n/**\n * Transforms a include description into the actual included value.\n */\nexport function createIncludeTransform(\n env: EnvFunc,\n readFile: ReadFileFunc,\n substitute: TransformFunc,\n): TransformFunc {\n return async (input: JsonValue, baseDir: string) => {\n if (!isObject(input)) {\n return { applied: false };\n }\n // Check if there's any key that starts with a '$', in that case we treat\n // this entire object as an include description.\n const [includeKey] = Object.keys(input).filter(key => key.startsWith('$'));\n if (includeKey) {\n if (Object.keys(input).length !== 1) {\n throw new Error(\n `include key ${includeKey} should not have adjacent keys`,\n );\n }\n } else {\n return { applied: false };\n }\n\n const rawIncludedValue = input[includeKey];\n if (typeof rawIncludedValue !== 'string') {\n throw new Error(`${includeKey} include value is not a string`);\n }\n\n const substituteResults = await substitute(rawIncludedValue, baseDir);\n const includeValue = substituteResults.applied\n ? substituteResults.value\n : rawIncludedValue;\n\n // The second string check is needed for Typescript to know this is a string.\n if (includeValue === undefined || typeof includeValue !== 'string') {\n throw new Error(`${includeKey} substitution value was undefined`);\n }\n\n switch (includeKey) {\n case '$file':\n try {\n const value = await readFile(resolvePath(baseDir, includeValue));\n return { applied: true, value };\n } catch (error) {\n throw new Error(`failed to read file ${includeValue}, ${error}`);\n }\n case '$env':\n try {\n return { applied: true, value: await env(includeValue) };\n } catch (error) {\n throw new Error(`failed to read env ${includeValue}, ${error}`);\n }\n\n case '$include': {\n const [filePath, dataPath] = includeValue.split(/#(.*)/);\n\n const ext = extname(filePath);\n const parser = includeFileParser[ext];\n if (!parser) {\n throw new Error(\n `no configuration parser available for included file ${filePath}`,\n );\n }\n\n const path = resolvePath(baseDir, filePath);\n const content = await readFile(path);\n const newBaseDir = dirname(path);\n\n const parts = dataPath ? dataPath.split('.') : [];\n\n let value: JsonValue | undefined;\n try {\n value = await parser(content);\n } catch (error) {\n throw new Error(\n `failed to parse included file ${filePath}, ${error}`,\n );\n }\n\n // This bit handles selecting a subtree in the included file, if a path was provided after a #\n for (const [index, part] of parts.entries()) {\n if (!isObject(value)) {\n const errPath = parts.slice(0, index).join('.');\n throw new Error(\n `value at '${errPath}' in included file ${filePath} is not an object`,\n );\n }\n value = value[part];\n }\n\n return {\n applied: true,\n value,\n newBaseDir: newBaseDir !== baseDir ? newBaseDir : undefined,\n };\n }\n\n default:\n throw new Error(`unknown include ${includeKey}`);\n }\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue } from '@backstage/types';\nimport { TransformFunc, EnvFunc } from './types';\n\n/**\n * A environment variable substitution transform that transforms e.g. 'token ${MY_TOKEN}'\n * to 'token abc' if MY_TOKEN is 'abc'. If any of the substituted variables are undefined,\n * the entire expression ends up undefined.\n */\nexport function createSubstitutionTransform(env: EnvFunc): TransformFunc {\n return async (input: JsonValue) => {\n if (typeof input !== 'string') {\n return { applied: false };\n }\n\n const parts: (string | undefined)[] = input.split(/(\\$?\\$\\{[^{}]*\\})/);\n for (let i = 1; i < parts.length; i += 2) {\n const part = parts[i]!;\n if (part.startsWith('$$')) {\n parts[i] = part.slice(1);\n } else {\n parts[i] = await env(part.slice(2, -1).trim());\n }\n }\n\n if (parts.some(part => part === undefined)) {\n return { applied: true, value: undefined };\n }\n return { applied: true, value: parts.join('') };\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\n\n/**\n * An sub-set of configuration schema.\n */\nexport type ConfigSchemaPackageEntry = {\n /**\n * The configuration schema itself.\n */\n value: JsonObject;\n /**\n * The relative path that the configuration schema was discovered at.\n */\n path: string;\n};\n\n/**\n * A list of all possible configuration value visibilities.\n */\nexport const CONFIG_VISIBILITIES = ['frontend', 'backend', 'secret'] as const;\n\n/**\n * A type representing the possible configuration value visibilities\n *\n * @public\n */\nexport type ConfigVisibility = 'frontend' | 'backend' | 'secret';\n\n/**\n * The default configuration visibility if no other values is given.\n */\nexport const DEFAULT_CONFIG_VISIBILITY: ConfigVisibility = 'backend';\n\n/**\n * An explanation of a configuration validation error.\n */\nexport type ValidationError = {\n keyword: string;\n dataPath: string;\n schemaPath: string;\n params: Record<string, any>;\n propertyName?: string;\n message?: string;\n};\n\n/**\n * The result of validating configuration data using a schema.\n */\ntype ValidationResult = {\n /**\n * Errors that where emitted during validation, if any.\n */\n errors?: ValidationError[];\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/<key>/<sub-key>/<array-index>/<leaf-key>`\n */\n visibilityByDataPath: Map<string, ConfigVisibility>;\n\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/properties/<key>/items/additionalProperties/<leaf-key>`\n */\n visibilityBySchemaPath: Map<string, ConfigVisibility>;\n};\n\n/**\n * A function used validate configuration data.\n */\nexport type ValidationFunc = (configs: AppConfig[]) => ValidationResult;\n\n/**\n * A function used to transform primitive configuration values.\n *\n * @public\n */\nexport type TransformFunc<T extends number | string | boolean> = (\n value: T,\n context: { visibility: ConfigVisibility },\n) => T | undefined;\n\n/**\n * Options used to process configuration data with a schema.\n *\n * @public\n */\nexport type ConfigSchemaProcessingOptions = {\n /**\n * The visibilities that should be included in the output data.\n * If omitted, the data will not be filtered by visibility.\n */\n visibility?: ConfigVisibility[];\n\n /**\n * A transform function that can be used to transform primitive configuration values\n * during validation. The value returned from the transform function will be used\n * instead of the original value. If the transform returns `undefined`, the value\n * will be omitted.\n */\n valueTransform?: TransformFunc<any>;\n\n /**\n * Whether or not to include the `filteredKeys` property in the output `AppConfig`s.\n *\n * Default: `false`.\n */\n withFilteredKeys?: boolean;\n};\n\n/**\n * A loaded configuration schema that is ready to process configuration data.\n *\n * @public\n */\nexport type ConfigSchema = {\n process(\n appConfigs: AppConfig[],\n options?: ConfigSchemaProcessingOptions,\n ): AppConfig[];\n\n serialize(): JsonObject;\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Ajv from 'ajv';\nimport { JSONSchema7 as JSONSchema } from 'json-schema';\nimport mergeAllOf, { Resolvers } from 'json-schema-merge-allof';\nimport traverse from 'json-schema-traverse';\nimport { ConfigReader } from '@backstage/config';\nimport {\n ConfigSchemaPackageEntry,\n ValidationFunc,\n CONFIG_VISIBILITIES,\n ConfigVisibility,\n} from './types';\n\n/**\n * This takes a collection of Backstage configuration schemas from various\n * sources and compiles them down into a single schema validation function.\n *\n * It also handles the implementation of the custom \"visibility\" keyword used\n * to specify the scope of different config paths.\n */\nexport function compileConfigSchemas(\n schemas: ConfigSchemaPackageEntry[],\n): ValidationFunc {\n // The ajv instance below is stateful and doesn't really allow for additional\n // output during validation. We work around this by having this extra piece\n // of state that we reset before each validation.\n const visibilityByDataPath = new Map<string, ConfigVisibility>();\n\n const ajv = new Ajv({\n allErrors: true,\n allowUnionTypes: true,\n schemas: {\n 'https://backstage.io/schema/config-v1': true,\n },\n }).addKeyword({\n keyword: 'visibility',\n metaSchema: {\n type: 'string',\n enum: CONFIG_VISIBILITIES,\n },\n compile(visibility: ConfigVisibility) {\n return (_data, context) => {\n if (context?.dataPath === undefined) {\n return false;\n }\n if (visibility && visibility !== 'backend') {\n const normalizedPath = context.dataPath.replace(\n /\\['?(.*?)'?\\]/g,\n (_, segment) => `/${segment}`,\n );\n visibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n });\n\n for (const schema of schemas) {\n try {\n ajv.compile(schema.value);\n } catch (error) {\n throw new Error(`Schema at ${schema.path} is invalid, ${error}`);\n }\n }\n\n const merged = mergeConfigSchemas(schemas.map(_ => _.value));\n const validate = ajv.compile(merged);\n\n const visibilityBySchemaPath = new Map<string, ConfigVisibility>();\n traverse(merged, (schema, path) => {\n if (schema.visibility && schema.visibility !== 'backend') {\n visibilityBySchemaPath.set(path, schema.visibility);\n }\n });\n\n return configs => {\n const config = ConfigReader.fromConfigs(configs).get();\n\n visibilityByDataPath.clear();\n\n const valid = validate(config);\n if (!valid) {\n return {\n errors: validate.errors ?? [],\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n }\n\n return {\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n };\n}\n\n/**\n * Given a list of configuration schemas from packages, merge them\n * into a single json schema.\n *\n * @public\n */\nexport function mergeConfigSchemas(schemas: JSONSchema[]): JSONSchema {\n const merged = mergeAllOf(\n { allOf: schemas },\n {\n // JSONSchema is typically subtractive, as in it always reduces the set of allowed\n // inputs through constraints. This changes the object property merging to be additive\n // rather than subtractive.\n ignoreAdditionalProperties: true,\n resolvers: {\n // This ensures that the visibilities across different schemas are sound, and\n // selects the most specific visibility for each path.\n visibility(values: string[], path: string[]) {\n const hasFrontend = values.some(_ => _ === 'frontend');\n const hasSecret = values.some(_ => _ === 'secret');\n if (hasFrontend && hasSecret) {\n throw new Error(\n `Config schema visibility is both 'frontend' and 'secret' for ${path.join(\n '/',\n )}`,\n );\n } else if (hasFrontend) {\n return 'frontend';\n } else if (hasSecret) {\n return 'secret';\n }\n\n return 'backend';\n },\n } as Partial<Resolvers<JSONSchema>>,\n },\n );\n return merged;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport {\n resolve as resolvePath,\n relative as relativePath,\n dirname,\n sep,\n} from 'path';\nimport { ConfigSchemaPackageEntry } from './types';\nimport { getProgramFromFiles, generateSchema } from 'typescript-json-schema';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\ntype Item = {\n name?: string;\n parentPath?: string;\n packagePath?: string;\n};\n\nconst req =\n typeof __non_webpack_require__ === 'undefined'\n ? require\n : __non_webpack_require__;\n\n/**\n * This collects all known config schemas across all dependencies of the app.\n */\nexport async function collectConfigSchemas(\n packageNames: string[],\n packagePaths: string[],\n): Promise<ConfigSchemaPackageEntry[]> {\n const schemas = new Array<ConfigSchemaPackageEntry>();\n const tsSchemaPaths = new Array<string>();\n const visitedPackageVersions = new Map<string, Set<string>>(); // pkgName: [versions...]\n\n const currentDir = await fs.realpath(process.cwd());\n\n async function processItem(item: Item) {\n let pkgPath = item.packagePath;\n\n if (pkgPath) {\n const pkgExists = await fs.pathExists(pkgPath);\n if (!pkgExists) {\n return;\n }\n } else if (item.name) {\n const { name, parentPath } = item;\n\n try {\n pkgPath = req.resolve(\n `${name}/package.json`,\n parentPath && {\n paths: [parentPath],\n },\n );\n } catch {\n // We can somewhat safely ignore packages that don't export package.json,\n // as they are likely not part of the Backstage ecosystem anyway.\n }\n }\n if (!pkgPath) {\n return;\n }\n\n const pkg = await fs.readJson(pkgPath);\n\n // Ensures that we only process the same version of each package once.\n let versions = visitedPackageVersions.get(pkg.name);\n if (versions?.has(pkg.version)) {\n return;\n }\n if (!versions) {\n versions = new Set();\n visitedPackageVersions.set(pkg.name, versions);\n }\n versions.add(pkg.version);\n\n const depNames = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ...Object.keys(pkg.optionalDependencies ?? {}),\n ...Object.keys(pkg.peerDependencies ?? {}),\n ];\n\n // TODO(Rugvip): Trying this out to avoid having to traverse the full dependency graph,\n // since that's pretty slow. We probably need a better way to determine when\n // we've left the Backstage ecosystem, but this will do for now.\n const hasSchema = 'configSchema' in pkg;\n const hasBackstageDep = depNames.some(_ => _.startsWith('@backstage/'));\n if (!hasSchema && !hasBackstageDep) {\n return;\n }\n if (hasSchema) {\n if (typeof pkg.configSchema === 'string') {\n const isJson = pkg.configSchema.endsWith('.json');\n const isDts = pkg.configSchema.endsWith('.d.ts');\n if (!isJson && !isDts) {\n throw new Error(\n `Config schema files must be .json or .d.ts, got ${pkg.configSchema}`,\n );\n }\n if (isDts) {\n tsSchemaPaths.push(\n relativePath(\n currentDir,\n resolvePath(dirname(pkgPath), pkg.configSchema),\n ),\n );\n } else {\n const path = resolvePath(dirname(pkgPath), pkg.configSchema);\n const value = await fs.readJson(path);\n schemas.push({\n value,\n path: relativePath(currentDir, path),\n });\n }\n } else {\n schemas.push({\n value: pkg.configSchema,\n path: relativePath(currentDir, pkgPath),\n });\n }\n }\n\n await Promise.all(\n depNames.map(depName =>\n processItem({ name: depName, parentPath: pkgPath }),\n ),\n );\n }\n\n await Promise.all([\n ...packageNames.map(name => processItem({ name, parentPath: currentDir })),\n ...packagePaths.map(path => processItem({ name: path, packagePath: path })),\n ]);\n\n const tsSchemas = compileTsSchemas(tsSchemaPaths);\n\n return schemas.concat(tsSchemas);\n}\n\n// This handles the support of TypeScript .d.ts config schema declarations.\n// We collect all typescript schema definition and compile them all in one go.\n// This is much faster than compiling them separately.\nfunction compileTsSchemas(paths: string[]) {\n if (paths.length === 0) {\n return [];\n }\n\n const program = getProgramFromFiles(paths, {\n incremental: false,\n isolatedModules: true,\n lib: ['ES5'], // Skipping most libs speeds processing up a lot, we just need the primitive types anyway\n noEmit: true,\n noResolve: true,\n skipLibCheck: true, // Skipping lib checks speeds things up\n skipDefaultLibCheck: true,\n strict: true,\n typeRoots: [], // Do not include any additional types\n types: [],\n });\n\n const tsSchemas = paths.map(path => {\n let value;\n try {\n value = generateSchema(\n program,\n // All schemas should export a `Config` symbol\n 'Config',\n // This enables usage of @visibility is doc comments\n {\n required: true,\n validationKeywords: ['visibility'],\n },\n [path.split(sep).join('/')], // Unix paths are expected for all OSes here\n ) as JsonObject | null;\n } catch (error) {\n assertError(error);\n if (error.message !== 'type Config not found') {\n throw error;\n }\n }\n\n if (!value) {\n throw new Error(`Invalid schema in ${path}, missing Config export`);\n }\n return { path, value };\n });\n\n return tsSchemas;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport {\n ConfigVisibility,\n DEFAULT_CONFIG_VISIBILITY,\n TransformFunc,\n ValidationError,\n} from './types';\n\n/**\n * This filters data by visibility by discovering the visibility of each\n * value, and then only keeping the ones that are specified in `includeVisibilities`.\n */\nexport function filterByVisibility(\n data: JsonObject,\n includeVisibilities: ConfigVisibility[],\n visibilityByDataPath: Map<string, ConfigVisibility>,\n transformFunc?: TransformFunc<number | string | boolean>,\n withFilteredKeys?: boolean,\n): { data: JsonObject; filteredKeys?: string[] } {\n const filteredKeys = new Array<string>();\n\n function transform(\n jsonVal: JsonValue,\n visibilityPath: string, // Matches the format we get from ajv\n filterPath: string, // Matches the format of the ConfigReader\n ): JsonValue | undefined {\n const visibility =\n visibilityByDataPath.get(visibilityPath) ?? DEFAULT_CONFIG_VISIBILITY;\n const isVisible = includeVisibilities.includes(visibility);\n\n if (typeof jsonVal !== 'object') {\n if (isVisible) {\n if (transformFunc) {\n return transformFunc(jsonVal, { visibility });\n }\n return jsonVal;\n }\n if (withFilteredKeys) {\n filteredKeys.push(filterPath);\n }\n return undefined;\n } else if (jsonVal === null) {\n return undefined;\n } else if (Array.isArray(jsonVal)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of jsonVal.entries()) {\n const out = transform(\n value,\n `${visibilityPath}/${index}`,\n `${filterPath}[${index}]`,\n );\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n if (arr.length > 0 || isVisible) {\n return arr;\n }\n return undefined;\n }\n\n const outObj: JsonObject = {};\n let hasOutput = false;\n\n for (const [key, value] of Object.entries(jsonVal)) {\n if (value === undefined) {\n continue;\n }\n const out = transform(\n value,\n `${visibilityPath}/${key}`,\n filterPath ? `${filterPath}.${key}` : key,\n );\n if (out !== undefined) {\n outObj[key] = out;\n hasOutput = true;\n }\n }\n\n if (hasOutput || isVisible) {\n return outObj;\n }\n return undefined;\n }\n\n return {\n filteredKeys: withFilteredKeys ? filteredKeys : undefined,\n data: (transform(data, '', '') as JsonObject) ?? {},\n };\n}\n\nexport function filterErrorsByVisibility(\n errors: ValidationError[] | undefined,\n includeVisibilities: ConfigVisibility[] | undefined,\n visibilityByDataPath: Map<string, ConfigVisibility>,\n visibilityBySchemaPath: Map<string, ConfigVisibility>,\n): ValidationError[] {\n if (!errors) {\n return [];\n }\n if (!includeVisibilities) {\n return errors;\n }\n\n const visibleSchemaPaths = Array.from(visibilityBySchemaPath)\n .filter(([, v]) => includeVisibilities.includes(v))\n .map(([k]) => k);\n\n // If we're filtering by visibility we only care about the errors that happened\n // in a visible path.\n return errors.filter(error => {\n // We always include structural errors as we don't know whether there are\n // any visible paths within the structures.\n if (\n error.keyword === 'type' &&\n ['object', 'array'].includes(error.params.type)\n ) {\n return true;\n }\n\n // For fields that were required we use the schema path to determine whether\n // it was visible in addition to the data path. This is because the data path\n // visibilities are only populated for values that we reached, which we won't\n // if the value is missing.\n // We don't use this method for all the errors as the data path is more robust\n // and doesn't require us to properly trim the schema path.\n if (error.keyword === 'required') {\n const trimmedPath = error.schemaPath.slice(1, -'/required'.length);\n const fullPath = `${trimmedPath}/properties/${error.params.missingProperty}`;\n if (\n visibleSchemaPaths.some(visiblePath => visiblePath.startsWith(fullPath))\n ) {\n return true;\n }\n }\n\n const vis =\n visibilityByDataPath.get(error.dataPath) ?? DEFAULT_CONFIG_VISIBILITY;\n return vis && includeVisibilities.includes(vis);\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { compileConfigSchemas } from './compile';\nimport { collectConfigSchemas } from './collect';\nimport { filterByVisibility, filterErrorsByVisibility } from './filtering';\nimport {\n ValidationError,\n ConfigSchema,\n ConfigSchemaPackageEntry,\n CONFIG_VISIBILITIES,\n} from './types';\n\n/**\n * Options that control the loading of configuration schema files in the backend.\n *\n * @public\n */\nexport type LoadConfigSchemaOptions =\n | {\n dependencies: string[];\n packagePaths?: string[];\n }\n | {\n serialized: JsonObject;\n };\n\nfunction errorsToError(errors: ValidationError[]): Error {\n const messages = errors.map(({ dataPath, message, params }) => {\n const paramStr = Object.entries(params)\n .map(([name, value]) => `${name}=${value}`)\n .join(' ');\n return `Config ${message || ''} { ${paramStr} } at ${dataPath}`;\n });\n const error = new Error(`Config validation failed, ${messages.join('; ')}`);\n (error as any).messages = messages;\n return error;\n}\n\n/**\n * Loads config schema for a Backstage instance.\n *\n * @public\n */\nexport async function loadConfigSchema(\n options: LoadConfigSchemaOptions,\n): Promise<ConfigSchema> {\n let schemas: ConfigSchemaPackageEntry[];\n\n if ('dependencies' in options) {\n schemas = await collectConfigSchemas(\n options.dependencies,\n options.packagePaths ?? [],\n );\n } else {\n const { serialized } = options;\n if (serialized?.backstageConfigSchemaVersion !== 1) {\n throw new Error(\n 'Serialized configuration schema is invalid or has an invalid version number',\n );\n }\n schemas = serialized.schemas as ConfigSchemaPackageEntry[];\n }\n\n const validate = compileConfigSchemas(schemas);\n\n return {\n process(\n configs: AppConfig[],\n { visibility, valueTransform, withFilteredKeys } = {},\n ): AppConfig[] {\n const result = validate(configs);\n\n const visibleErrors = filterErrorsByVisibility(\n result.errors,\n visibility,\n result.visibilityByDataPath,\n result.visibilityBySchemaPath,\n );\n if (visibleErrors.length > 0) {\n throw errorsToError(visibleErrors);\n }\n\n let processedConfigs = configs;\n\n if (visibility) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n visibility,\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n } else if (valueTransform) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n Array.from(CONFIG_VISIBILITIES),\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n }\n\n return processedConfigs;\n },\n serialize(): JsonObject {\n return {\n schemas,\n backstageConfigSchemaVersion: 1,\n };\n },\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport yaml from 'yaml';\nimport chokidar from 'chokidar';\nimport { resolve as resolvePath, dirname, isAbsolute, basename } from 'path';\nimport { AppConfig } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport {\n applyConfigTransforms,\n readEnvConfig,\n createIncludeTransform,\n createSubstitutionTransform,\n} from './lib';\n\n/**\n * Options that control the loading of configuration files in the backend.\n *\n * @public\n */\nexport type LoadConfigOptions = {\n // The root directory of the config loading context. Used to find default configs.\n configRoot: string;\n\n // Absolute paths to load config files from. Configs from earlier paths have lower priority.\n configPaths: string[];\n\n /** @deprecated This option has been removed */\n env?: string;\n\n /**\n * Custom environment variable loading function\n *\n * @experimental This API is not stable and may change at any point\n */\n experimentalEnvFunc?: (name: string) => Promise<string | undefined>;\n\n /**\n * An optional configuration that enables watching of config files.\n */\n watch?: {\n /**\n * A listener that is called when a config file is changed.\n */\n onChange: (configs: AppConfig[]) => void;\n\n /**\n * An optional signal that stops the watcher once the promise resolves.\n */\n stopSignal?: Promise<void>;\n };\n};\n\n/**\n * Load configuration data.\n *\n * @public\n */\nexport async function loadConfig(\n options: LoadConfigOptions,\n): Promise<AppConfig[]> {\n const { configRoot, experimentalEnvFunc: envFunc, watch } = options;\n const configPaths = options.configPaths.slice();\n\n // If no paths are provided, we default to reading\n // `app-config.yaml` and, if it exists, `app-config.local.yaml`\n if (configPaths.length === 0) {\n configPaths.push(resolvePath(configRoot, 'app-config.yaml'));\n\n const localConfig = resolvePath(configRoot, 'app-config.local.yaml');\n if (await fs.pathExists(localConfig)) {\n configPaths.push(localConfig);\n }\n }\n\n const env = envFunc ?? (async (name: string) => process.env[name]);\n\n const loadConfigFiles = async () => {\n const configs = [];\n\n for (const configPath of configPaths) {\n if (!isAbsolute(configPath)) {\n throw new Error(`Config load path is not absolute: '${configPath}'`);\n }\n\n const dir = dirname(configPath);\n const readFile = (path: string) =>\n fs.readFile(resolvePath(dir, path), 'utf8');\n\n const input = yaml.parse(await readFile(configPath));\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(dir, input, [\n createIncludeTransform(env, readFile, substitutionTransform),\n substitutionTransform,\n ]);\n\n configs.push({ data, context: basename(configPath) });\n }\n\n return configs;\n };\n\n let fileConfigs;\n try {\n fileConfigs = await loadConfigFiles();\n } catch (error) {\n throw new ForwardedError('Failed to read static configuration file', error);\n }\n\n const envConfigs = await readEnvConfig(process.env);\n\n // Set up config file watching if requested by the caller\n if (watch) {\n let currentSerializedConfig = JSON.stringify(fileConfigs);\n\n const watcher = chokidar.watch(configPaths, {\n usePolling: process.env.NODE_ENV === 'test',\n });\n watcher.on('change', async () => {\n try {\n const newConfigs = await loadConfigFiles();\n const newSerializedConfig = JSON.stringify(newConfigs);\n\n if (currentSerializedConfig === newSerializedConfig) {\n return;\n }\n currentSerializedConfig = newSerializedConfig;\n\n watch.onChange([...newConfigs, ...envConfigs]);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n });\n\n if (watch.stopSignal) {\n watch.stopSignal.then(() => {\n watcher.close();\n });\n }\n }\n\n return [...fileConfigs, ...envConfigs];\n}\n"],"names":["yaml","resolvePath","extname","path","dirname","Ajv","config","ConfigReader","mergeAllOf","fs","relativePath","getProgramFromFiles","generateSchema","sep","isAbsolute","basename","ForwardedError","chokidar"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAoBA,MAAM,aAAa;AAGnB,MAAM,0BAA0B;uBAsBF,KAEd;AA/ChB;AAgDE,MAAI,OAA+B;AAEnC,aAAW,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM;AAC/C,QAAI,CAAC,OAAO;AACV;AAAA;AAEF,QAAI,KAAK,WAAW,aAAa;AAC/B,YAAM,MAAM,KAAK,QAAQ,YAAY;AACrC,YAAM,WAAW,IAAI,MAAM;AAE3B,UAAI,MAAO,OAAO,sBAAQ;AAC1B,iBAAW,CAAC,OAAO,SAAS,SAAS,WAAW;AAC9C,YAAI,CAAC,wBAAwB,KAAK,OAAO;AACvC,gBAAM,IAAI,UAAU,2BAA2B;AAAA;AAEjD,YAAI,QAAQ,SAAS,SAAS,GAAG;AAC/B,gBAAO,IAAI,QAAQ,UAAI,UAAJ,YAAa;AAChC,cAAI,OAAO,QAAQ,YAAY,MAAM,QAAQ,MAAM;AACjD,kBAAM,SAAS,SAAS,MAAM,GAAG,QAAQ,GAAG,KAAK;AACjD,kBAAM,IAAI,UACR,kCAAkC,8BAA8B;AAAA;AAAA,eAG/D;AACL,cAAI,QAAQ,KAAK;AACf,kBAAM,IAAI,UACR,gDAAgD;AAAA;AAGpD,cAAI;AACF,kBAAM,GAAG,eAAe,cAAc;AACtC,gBAAI,gBAAgB,MAAM;AACxB,oBAAM,IAAI,MAAM;AAAA;AAElB,gBAAI,QAAQ;AAAA,mBACL,OAAP;AACA,kBAAM,IAAI,UACR,yDAAyD,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAQ9E,SAAO,OAAO,CAAC,CAAE,MAAM,SAAS,UAAW;AAAA;AAG7C,uBAAuB,KAAkC;AACvD,MAAI;AACF,WAAO,CAAC,MAAM,KAAK,MAAM;AAAA,WAClB,KAAP;AACA,uBAAY;AACZ,WAAO,CAAC,KAAK;AAAA;AAAA;;kBCnFQ,KAA+C;AACtE,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO;AAAA,aACE,MAAM,QAAQ,MAAM;AAC7B,WAAO;AAAA;AAET,SAAO,QAAQ;AAAA;;qCCCf,YACA,OACA,YACqB;AACrB,2BACE,UACA,MACA,SACgC;AAjCpC;AAkCI,QAAI,MAAM;AACV,QAAI,MAAM;AAEV,eAAW,MAAM,YAAY;AAC3B,UAAI;AACF,cAAM,SAAS,MAAM,GAAG,UAAU;AAClC,YAAI,OAAO,SAAS;AAClB,cAAI,OAAO,UAAU,QAAW;AAC9B,mBAAO;AAAA;AAET,gBAAM,OAAO;AACb,gBAAM,aAAO,eAAP,YAAqB;AAC3B;AAAA;AAAA,eAEK,OAAP;AACA,2BAAY;AACZ,cAAM,IAAI,MAAM,YAAY,SAAS,MAAM;AAAA;AAAA;AAI/C,QAAI,OAAO,QAAQ,UAAU;AAC3B,aAAO;AAAA,eACE,QAAQ,MAAM;AACvB,aAAO;AAAA,eACE,MAAM,QAAQ,MAAM;AAC7B,YAAM,MAAM,IAAI;AAEhB,iBAAW,CAAC,OAAO,UAAU,IAAI,WAAW;AAC1C,cAAM,OAAM,MAAM,UAAU,OAAO,GAAG,QAAQ,UAAU;AACxD,YAAI,SAAQ,QAAW;AACrB,cAAI,KAAK;AAAA;AAAA;AAIb,aAAO;AAAA;AAGT,UAAM,MAAkB;AAExB,eAAW,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM;AAE9C,UAAI,UAAU,QAAW;AACvB,cAAM,SAAS,MAAM,UAAU,OAAO,GAAG,QAAQ,OAAO;AACxD,YAAI,WAAW,QAAW;AACxB,cAAI,OAAO;AAAA;AAAA;AAAA;AAKjB,WAAO;AAAA;AAGT,QAAM,YAAY,MAAM,UAAU,OAAO,IAAI;AAC7C,MAAI,CAAC,SAAS,YAAY;AACxB,UAAM,IAAI,UAAU;AAAA;AAEtB,SAAO;AAAA;;ACnET,MAAM,oBAEF;AAAA,EACF,SAAS,OAAM,YAAW,KAAK,MAAM;AAAA,EACrC,SAAS,OAAM,YAAWA,yBAAK,MAAM;AAAA,EACrC,QAAQ,OAAM,YAAWA,yBAAK,MAAM;AAAA;gCAOpC,KACA,UACA,YACe;AACf,SAAO,OAAO,OAAkB,YAAoB;AAClD,QAAI,CAAC,SAAS,QAAQ;AACpB,aAAO,CAAE,SAAS;AAAA;AAIpB,UAAM,CAAC,cAAc,OAAO,KAAK,OAAO,OAAO,SAAO,IAAI,WAAW;AACrE,QAAI,YAAY;AACd,UAAI,OAAO,KAAK,OAAO,WAAW,GAAG;AACnC,cAAM,IAAI,MACR,eAAe;AAAA;AAAA,WAGd;AACL,aAAO,CAAE,SAAS;AAAA;AAGpB,UAAM,mBAAmB,MAAM;AAC/B,QAAI,OAAO,qBAAqB,UAAU;AACxC,YAAM,IAAI,MAAM,GAAG;AAAA;AAGrB,UAAM,oBAAoB,MAAM,WAAW,kBAAkB;AAC7D,UAAM,eAAe,kBAAkB,UACnC,kBAAkB,QAClB;AAGJ,QAAI,iBAAiB,UAAa,OAAO,iBAAiB,UAAU;AAClE,YAAM,IAAI,MAAM,GAAG;AAAA;AAGrB,YAAQ;AAAA,WACD;AACH,YAAI;AACF,gBAAM,QAAQ,MAAM,SAASC,aAAY,SAAS;AAClD,iBAAO,CAAE,SAAS,MAAM;AAAA,iBACjB,OAAP;AACA,gBAAM,IAAI,MAAM,uBAAuB,iBAAiB;AAAA;AAAA,WAEvD;AACH,YAAI;AACF,iBAAO,CAAE,SAAS,MAAM,OAAO,MAAM,IAAI;AAAA,iBAClC,OAAP;AACA,gBAAM,IAAI,MAAM,sBAAsB,iBAAiB;AAAA;AAAA,WAGtD,YAAY;AACf,cAAM,CAAC,UAAU,YAAY,aAAa,MAAM;AAEhD,cAAM,MAAMC,aAAQ;AACpB,cAAM,SAAS,kBAAkB;AACjC,YAAI,CAAC,QAAQ;AACX,gBAAM,IAAI,MACR,uDAAuD;AAAA;AAI3D,cAAMC,SAAOF,aAAY,SAAS;AAClC,cAAM,UAAU,MAAM,SAASE;AAC/B,cAAM,aAAaC,aAAQD;AAE3B,cAAM,QAAQ,WAAW,SAAS,MAAM,OAAO;AAE/C,YAAI;AACJ,YAAI;AACF,kBAAQ,MAAM,OAAO;AAAA,iBACd,OAAP;AACA,gBAAM,IAAI,MACR,iCAAiC,aAAa;AAAA;AAKlD,mBAAW,CAAC,OAAO,SAAS,MAAM,WAAW;AAC3C,cAAI,CAAC,SAAS,QAAQ;AACpB,kBAAM,UAAU,MAAM,MAAM,GAAG,OAAO,KAAK;AAC3C,kBAAM,IAAI,MACR,aAAa,6BAA6B;AAAA;AAG9C,kBAAQ,MAAM;AAAA;AAGhB,eAAO;AAAA,UACL,SAAS;AAAA,UACT;AAAA,UACA,YAAY,eAAe,UAAU,aAAa;AAAA;AAAA;AAAA;AAKpD,cAAM,IAAI,MAAM,mBAAmB;AAAA;AAAA;AAAA;;qCC3GC,KAA6B;AACvE,SAAO,OAAO,UAAqB;AACjC,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO,CAAE,SAAS;AAAA;AAGpB,UAAM,QAAgC,MAAM,MAAM;AAClD,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,YAAM,OAAO,MAAM;AACnB,UAAI,KAAK,WAAW,OAAO;AACzB,cAAM,KAAK,KAAK,MAAM;AAAA,aACjB;AACL,cAAM,KAAK,MAAM,IAAI,KAAK,MAAM,GAAG,IAAI;AAAA;AAAA;AAI3C,QAAI,MAAM,KAAK,UAAQ,SAAS,SAAY;AAC1C,aAAO,CAAE,SAAS,MAAM,OAAO;AAAA;AAEjC,WAAO,CAAE,SAAS,MAAM,OAAO,MAAM,KAAK;AAAA;AAAA;;MCPjC,sBAAsB,CAAC,YAAY,WAAW;MAY9C,4BAA8C;;8BCZzD,SACgB;AAIhB,QAAM,uBAAuB,IAAI;AAEjC,QAAM,MAAM,IAAIE,wBAAI;AAAA,IAClB,WAAW;AAAA,IACX,iBAAiB;AAAA,IACjB,SAAS;AAAA,MACP,yCAAyC;AAAA;AAAA,KAE1C,WAAW;AAAA,IACZ,SAAS;AAAA,IACT,YAAY;AAAA,MACV,MAAM;AAAA,MACN,MAAM;AAAA;AAAA,IAER,QAAQ,YAA8B;AACpC,aAAO,CAAC,OAAO,YAAY;AACzB,YAAI,oCAAS,cAAa,QAAW;AACnC,iBAAO;AAAA;AAET,YAAI,cAAc,eAAe,WAAW;AAC1C,gBAAM,iBAAiB,QAAQ,SAAS,QACtC,kBACA,CAAC,GAAG,YAAY,IAAI;AAEtB,+BAAqB,IAAI,gBAAgB;AAAA;AAE3C,eAAO;AAAA;AAAA;AAAA;AAKb,aAAW,UAAU,SAAS;AAC5B,QAAI;AACF,UAAI,QAAQ,OAAO;AAAA,aACZ,OAAP;AACA,YAAM,IAAI,MAAM,aAAa,OAAO,oBAAoB;AAAA;AAAA;AAI5D,QAAM,SAAS,mBAAmB,QAAQ,IAAI,OAAK,EAAE;AACrD,QAAM,WAAW,IAAI,QAAQ;AAE7B,QAAM,yBAAyB,IAAI;AACnC,+BAAS,QAAQ,CAAC,QAAQ,SAAS;AACjC,QAAI,OAAO,cAAc,OAAO,eAAe,WAAW;AACxD,6BAAuB,IAAI,MAAM,OAAO;AAAA;AAAA;AAI5C,SAAO,aAAW;AA1FpB;AA2FI,UAAMC,WAASC,oBAAa,YAAY,SAAS;AAEjD,yBAAqB;AAErB,UAAM,QAAQ,SAASD;AACvB,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,QACL,QAAQ,eAAS,WAAT,YAAmB;AAAA,QAC3B,sBAAsB,IAAI,IAAI;AAAA,QAC9B;AAAA;AAAA;AAIJ,WAAO;AAAA,MACL,sBAAsB,IAAI,IAAI;AAAA,MAC9B;AAAA;AAAA;AAAA;4BAW6B,SAAmC;AACpE,QAAM,SAASE,+BACb,CAAE,OAAO,UACT;AAAA,IAIE,4BAA4B;AAAA,IAC5B,WAAW;AAAA,MAGT,WAAW,QAAkB,MAAgB;AAC3C,cAAM,cAAc,OAAO,KAAK,OAAK,MAAM;AAC3C,cAAM,YAAY,OAAO,KAAK,OAAK,MAAM;AACzC,YAAI,eAAe,WAAW;AAC5B,gBAAM,IAAI,MACR,gEAAgE,KAAK,KACnE;AAAA,mBAGK,aAAa;AACtB,iBAAO;AAAA,mBACE,WAAW;AACpB,iBAAO;AAAA;AAGT,eAAO;AAAA;AAAA;AAAA;AAKf,SAAO;AAAA;;AClHT,MAAM,MACJ,OAAO,4BAA4B,cAC/B,UACA;oCAMJ,cACA,cACqC;AACrC,QAAM,UAAU,IAAI;AACpB,QAAM,gBAAgB,IAAI;AAC1B,QAAM,yBAAyB,IAAI;AAEnC,QAAM,aAAa,MAAMC,uBAAG,SAAS,QAAQ;AAE7C,6BAA2B,MAAY;AApDzC;AAqDI,QAAI,UAAU,KAAK;AAEnB,QAAI,SAAS;AACX,YAAM,YAAY,MAAMA,uBAAG,WAAW;AACtC,UAAI,CAAC,WAAW;AACd;AAAA;AAAA,eAEO,KAAK,MAAM;AACpB,YAAM,CAAE,MAAM,cAAe;AAE7B,UAAI;AACF,kBAAU,IAAI,QACZ,GAAG,qBACH,cAAc;AAAA,UACZ,OAAO,CAAC;AAAA;AAAA,cAGZ;AAAA;AAAA;AAKJ,QAAI,CAAC,SAAS;AACZ;AAAA;AAGF,UAAM,MAAM,MAAMA,uBAAG,SAAS;AAG9B,QAAI,WAAW,uBAAuB,IAAI,IAAI;AAC9C,QAAI,qCAAU,IAAI,IAAI,UAAU;AAC9B;AAAA;AAEF,QAAI,CAAC,UAAU;AACb,iBAAW,IAAI;AACf,6BAAuB,IAAI,IAAI,MAAM;AAAA;AAEvC,aAAS,IAAI,IAAI;AAEjB,UAAM,WAAW;AAAA,MACf,GAAG,OAAO,KAAK,UAAI,iBAAJ,YAAoB;AAAA,MACnC,GAAG,OAAO,KAAK,UAAI,oBAAJ,YAAuB;AAAA,MACtC,GAAG,OAAO,KAAK,UAAI,yBAAJ,YAA4B;AAAA,MAC3C,GAAG,OAAO,KAAK,UAAI,qBAAJ,YAAwB;AAAA;AAMzC,UAAM,YAAY,kBAAkB;AACpC,UAAM,kBAAkB,SAAS,KAAK,OAAK,EAAE,WAAW;AACxD,QAAI,CAAC,aAAa,CAAC,iBAAiB;AAClC;AAAA;AAEF,QAAI,WAAW;AACb,UAAI,OAAO,IAAI,iBAAiB,UAAU;AACxC,cAAM,SAAS,IAAI,aAAa,SAAS;AACzC,cAAM,QAAQ,IAAI,aAAa,SAAS;AACxC,YAAI,CAAC,UAAU,CAAC,OAAO;AACrB,gBAAM,IAAI,MACR,mDAAmD,IAAI;AAAA;AAG3D,YAAI,OAAO;AACT,wBAAc,KACZC,cACE,YACAT,aAAYG,aAAQ,UAAU,IAAI;AAAA,eAGjC;AACL,gBAAMD,SAAOF,aAAYG,aAAQ,UAAU,IAAI;AAC/C,gBAAM,QAAQ,MAAMK,uBAAG,SAASN;AAChC,kBAAQ,KAAK;AAAA,YACX;AAAA,YACA,MAAMO,cAAa,YAAYP;AAAA;AAAA;AAAA,aAG9B;AACL,gBAAQ,KAAK;AAAA,UACX,OAAO,IAAI;AAAA,UACX,MAAMO,cAAa,YAAY;AAAA;AAAA;AAAA;AAKrC,UAAM,QAAQ,IACZ,SAAS,IAAI,aACX,YAAY,CAAE,MAAM,SAAS,YAAY;AAAA;AAK/C,QAAM,QAAQ,IAAI;AAAA,IAChB,GAAG,aAAa,IAAI,UAAQ,YAAY,CAAE,MAAM,YAAY;AAAA,IAC5D,GAAG,aAAa,IAAI,UAAQ,YAAY,CAAE,MAAM,MAAM,aAAa;AAAA;AAGrE,QAAM,YAAY,iBAAiB;AAEnC,SAAO,QAAQ,OAAO;AAAA;AAMxB,0BAA0B,OAAiB;AACzC,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO;AAAA;AAGT,QAAM,UAAUC,yCAAoB,OAAO;AAAA,IACzC,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,KAAK,CAAC;AAAA,IACN,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,qBAAqB;AAAA,IACrB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,OAAO;AAAA;AAGT,QAAM,YAAY,MAAM,IAAI,YAAQ;AAClC,QAAI;AACJ,QAAI;AACF,cAAQC,oCACN,SAEA,UAEA;AAAA,QACE,UAAU;AAAA,QACV,oBAAoB,CAAC;AAAA,SAEvB,CAACT,OAAK,MAAMU,UAAK,KAAK;AAAA,aAEjB,OAAP;AACA,yBAAY;AACZ,UAAI,MAAM,YAAY,yBAAyB;AAC7C,cAAM;AAAA;AAAA;AAIV,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,qBAAqBV;AAAA;AAEvC,WAAO,OAAEA,QAAM;AAAA;AAGjB,SAAO;AAAA;;4BC/KP,MACA,qBACA,sBACA,eACA,kBAC+C;AAlCjD;AAmCE,QAAM,eAAe,IAAI;AAEzB,qBACE,SACA,gBACA,YACuB;AAzC3B;AA0CI,UAAM,aACJ,4BAAqB,IAAI,oBAAzB,aAA4C;AAC9C,UAAM,YAAY,oBAAoB,SAAS;AAE/C,QAAI,OAAO,YAAY,UAAU;AAC/B,UAAI,WAAW;AACb,YAAI,eAAe;AACjB,iBAAO,cAAc,SAAS,CAAE;AAAA;AAElC,eAAO;AAAA;AAET,UAAI,kBAAkB;AACpB,qBAAa,KAAK;AAAA;AAEpB,aAAO;AAAA,eACE,YAAY,MAAM;AAC3B,aAAO;AAAA,eACE,MAAM,QAAQ,UAAU;AACjC,YAAM,MAAM,IAAI;AAEhB,iBAAW,CAAC,OAAO,UAAU,QAAQ,WAAW;AAC9C,cAAM,MAAM,UACV,OACA,GAAG,kBAAkB,SACrB,GAAG,cAAc;AAEnB,YAAI,QAAQ,QAAW;AACrB,cAAI,KAAK;AAAA;AAAA;AAIb,UAAI,IAAI,SAAS,KAAK,WAAW;AAC/B,eAAO;AAAA;AAET,aAAO;AAAA;AAGT,UAAM,SAAqB;AAC3B,QAAI,YAAY;AAEhB,eAAW,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU;AAClD,UAAI,UAAU,QAAW;AACvB;AAAA;AAEF,YAAM,MAAM,UACV,OACA,GAAG,kBAAkB,OACrB,aAAa,GAAG,cAAc,QAAQ;AAExC,UAAI,QAAQ,QAAW;AACrB,eAAO,OAAO;AACd,oBAAY;AAAA;AAAA;AAIhB,QAAI,aAAa,WAAW;AAC1B,aAAO;AAAA;AAET,WAAO;AAAA;AAGT,SAAO;AAAA,IACL,cAAc,mBAAmB,eAAe;AAAA,IAChD,MAAO,gBAAU,MAAM,IAAI,QAApB,YAA0C;AAAA;AAAA;kCAKnD,QACA,qBACA,sBACA,wBACmB;AACnB,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA;AAET,MAAI,CAAC,qBAAqB;AACxB,WAAO;AAAA;AAGT,QAAM,qBAAqB,MAAM,KAAK,wBACnC,OAAO,CAAC,GAAG,OAAO,oBAAoB,SAAS,IAC/C,IAAI,CAAC,CAAC,OAAO;AAIhB,SAAO,OAAO,OAAO,WAAS;AAhIhC;AAmII,QACE,MAAM,YAAY,UAClB,CAAC,UAAU,SAAS,SAAS,MAAM,OAAO,OAC1C;AACA,aAAO;AAAA;AAST,QAAI,MAAM,YAAY,YAAY;AAChC,YAAM,cAAc,MAAM,WAAW,MAAM,GAAG,CAAC,YAAY;AAC3D,YAAM,WAAW,GAAG,0BAA0B,MAAM,OAAO;AAC3D,UACE,mBAAmB,KAAK,iBAAe,YAAY,WAAW,YAC9D;AACA,eAAO;AAAA;AAAA;AAIX,UAAM,MACJ,2BAAqB,IAAI,MAAM,cAA/B,YAA4C;AAC9C,WAAO,OAAO,oBAAoB,SAAS;AAAA;AAAA;;AClH/C,uBAAuB,QAAkC;AACvD,QAAM,WAAW,OAAO,IAAI,CAAC,CAAE,UAAU,SAAS,YAAa;AAC7D,UAAM,WAAW,OAAO,QAAQ,QAC7B,IAAI,CAAC,CAAC,MAAM,WAAW,GAAG,QAAQ,SAClC,KAAK;AACR,WAAO,UAAU,WAAW,QAAQ,iBAAiB;AAAA;AAEvD,QAAM,QAAQ,IAAI,MAAM,6BAA6B,SAAS,KAAK;AACnE,EAAC,MAAc,WAAW;AAC1B,SAAO;AAAA;gCASP,SACuB;AA7DzB;AA8DE,MAAI;AAEJ,MAAI,kBAAkB,SAAS;AAC7B,cAAU,MAAM,qBACd,QAAQ,cACR,cAAQ,iBAAR,YAAwB;AAAA,SAErB;AACL,UAAM,CAAE,cAAe;AACvB,QAAI,0CAAY,kCAAiC,GAAG;AAClD,YAAM,IAAI,MACR;AAAA;AAGJ,cAAU,WAAW;AAAA;AAGvB,QAAM,WAAW,qBAAqB;AAEtC,SAAO;AAAA,IACL,QACE,SACA,CAAE,YAAY,gBAAgB,oBAAqB,IACtC;AACb,YAAM,SAAS,SAAS;AAExB,YAAM,gBAAgB,yBACpB,OAAO,QACP,YACA,OAAO,sBACP,OAAO;AAET,UAAI,cAAc,SAAS,GAAG;AAC5B,cAAM,cAAc;AAAA;AAGtB,UAAI,mBAAmB;AAEvB,UAAI,YAAY;AACd,2BAAmB,iBAAiB,IAAI,CAAC,CAAE,MAAM;AAAe,UAC9D;AAAA,aACG,mBACD,MACA,YACA,OAAO,sBACP,gBACA;AAAA;AAAA,iBAGK,gBAAgB;AACzB,2BAAmB,iBAAiB,IAAI,CAAC,CAAE,MAAM;AAAe,UAC9D;AAAA,aACG,mBACD,MACA,MAAM,KAAK,sBACX,OAAO,sBACP,gBACA;AAAA;AAAA;AAKN,aAAO;AAAA;AAAA,IAET,YAAwB;AACtB,aAAO;AAAA,QACL;AAAA,QACA,8BAA8B;AAAA;AAAA;AAAA;AAAA;;0BCxDpC,SACsB;AACtB,QAAM,CAAE,YAAY,qBAAqB,SAAS,SAAU;AAC5D,QAAM,cAAc,QAAQ,YAAY;AAIxC,MAAI,YAAY,WAAW,GAAG;AAC5B,gBAAY,KAAKF,aAAY,YAAY;AAEzC,UAAM,cAAcA,aAAY,YAAY;AAC5C,QAAI,MAAMQ,uBAAG,WAAW,cAAc;AACpC,kBAAY,KAAK;AAAA;AAAA;AAIrB,QAAM,MAAM,4BAAY,OAAO,SAAiB,QAAQ,IAAI;AAE5D,QAAM,kBAAkB,YAAY;AAClC,UAAM,UAAU;AAEhB,eAAW,cAAc,aAAa;AACpC,UAAI,CAACK,gBAAW,aAAa;AAC3B,cAAM,IAAI,MAAM,sCAAsC;AAAA;AAGxD,YAAM,MAAMV,aAAQ;AACpB,YAAM,WAAW,CAACD,WAChBM,uBAAG,SAASR,aAAY,KAAKE,SAAO;AAEtC,YAAM,QAAQH,yBAAK,MAAM,MAAM,SAAS;AACxC,YAAM,wBAAwB,4BAA4B;AAC1D,YAAM,OAAO,MAAM,sBAAsB,KAAK,OAAO;AAAA,QACnD,uBAAuB,KAAK,UAAU;AAAA,QACtC;AAAA;AAGF,cAAQ,KAAK,CAAE,MAAM,SAASe,cAAS;AAAA;AAGzC,WAAO;AAAA;AAGT,MAAI;AACJ,MAAI;AACF,kBAAc,MAAM;AAAA,WACb,OAAP;AACA,UAAM,IAAIC,sBAAe,4CAA4C;AAAA;AAGvE,QAAM,aAAa,MAAM,cAAc,QAAQ;AAG/C,MAAI,OAAO;AACT,QAAI,0BAA0B,KAAK,UAAU;AAE7C,UAAM,UAAUC,6BAAS,MAAM,aAAa;AAAA,MAC1C,YAAY,QAAQ,IAAI,aAAa;AAAA;AAEvC,YAAQ,GAAG,UAAU,YAAY;AAC/B,UAAI;AACF,cAAM,aAAa,MAAM;AACzB,cAAM,sBAAsB,KAAK,UAAU;AAE3C,YAAI,4BAA4B,qBAAqB;AACnD;AAAA;AAEF,kCAA0B;AAE1B,cAAM,SAAS,CAAC,GAAG,YAAY,GAAG;AAAA,eAC3B,OAAP;AACA,gBAAQ,MAAM,yCAAyC;AAAA;AAAA;AAI3D,QAAI,MAAM,YAAY;AACpB,YAAM,WAAW,KAAK,MAAM;AAC1B,gBAAQ;AAAA;AAAA;AAAA;AAKd,SAAO,CAAC,GAAG,aAAa,GAAG;AAAA;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/lib/env.ts","../src/lib/transform/utils.ts","../src/lib/transform/apply.ts","../src/lib/transform/include.ts","../src/lib/transform/substitution.ts","../src/lib/schema/types.ts","../src/lib/schema/compile.ts","../src/lib/schema/collect.ts","../src/lib/schema/filtering.ts","../src/lib/schema/load.ts","../src/lib/urls.ts","../src/loader.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\nconst ENV_PREFIX = 'APP_CONFIG_';\n\n// Update the same pattern in config package if this is changed\nconst CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;\n\n/**\n * Read runtime configuration from the environment.\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n */\nexport function readEnvConfig(env: {\n [name: string]: string | undefined;\n}): AppConfig[] {\n let data: JsonObject | undefined = undefined;\n\n for (const [name, value] of Object.entries(env)) {\n if (!value) {\n continue;\n }\n if (name.startsWith(ENV_PREFIX)) {\n const key = name.replace(ENV_PREFIX, '');\n const keyParts = key.split('_');\n\n let obj = (data = data ?? {});\n for (const [index, part] of keyParts.entries()) {\n if (!CONFIG_KEY_PART_PATTERN.test(part)) {\n throw new TypeError(`Invalid env config key '${key}'`);\n }\n if (index < keyParts.length - 1) {\n obj = (obj[part] = obj[part] ?? {}) as JsonObject;\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n const subKey = keyParts.slice(0, index + 1).join('_');\n throw new TypeError(\n `Could not nest config for key '${key}' under existing value '${subKey}'`,\n );\n }\n } else {\n if (part in obj) {\n throw new TypeError(\n `Refusing to override existing config at key '${key}'`,\n );\n }\n try {\n const [, parsedValue] = safeJsonParse(value);\n if (parsedValue === null) {\n throw new Error('value may not be null');\n }\n obj[part] = parsedValue;\n } catch (error) {\n throw new TypeError(\n `Failed to parse JSON-serialized config value for key '${key}', ${error}`,\n );\n }\n }\n }\n }\n }\n\n return data ? [{ data, context: 'env' }] : [];\n}\n\nfunction safeJsonParse(str: string): [Error | null, any] {\n try {\n return [null, JSON.parse(str)];\n } catch (err) {\n assertError(err);\n return [err, str];\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue, JsonObject } from '@backstage/types';\n\nexport function isObject(obj: JsonValue | undefined): obj is JsonObject {\n if (typeof obj !== 'object') {\n return false;\n } else if (Array.isArray(obj)) {\n return false;\n }\n return obj !== null;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\nimport { TransformFunc } from './types';\nimport { isObject } from './utils';\n\n/**\n * Applies a set of transforms to raw configuration data.\n */\nexport async function applyConfigTransforms(\n initialDir: string,\n input: JsonValue,\n transforms: TransformFunc[],\n): Promise<JsonObject> {\n async function transform(\n inputObj: JsonValue,\n path: string,\n baseDir: string,\n ): Promise<JsonValue | undefined> {\n let obj = inputObj;\n let dir = baseDir;\n\n for (const tf of transforms) {\n try {\n const result = await tf(inputObj, baseDir);\n if (result.applied) {\n if (result.value === undefined) {\n return undefined;\n }\n obj = result.value;\n dir = result.newBaseDir ?? dir;\n break;\n }\n } catch (error) {\n assertError(error);\n throw new Error(`error at ${path}, ${error.message}`);\n }\n }\n\n if (typeof obj !== 'object') {\n return obj;\n } else if (obj === null) {\n return undefined;\n } else if (Array.isArray(obj)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of obj.entries()) {\n const out = await transform(value, `${path}[${index}]`, dir);\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n return arr;\n }\n\n const out: JsonObject = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // undefined covers optional fields\n if (value !== undefined) {\n const result = await transform(value, `${path}.${key}`, dir);\n if (result !== undefined) {\n out[key] = result;\n }\n }\n }\n\n return out;\n }\n\n const finalData = await transform(input, '', initialDir);\n if (!isObject(finalData)) {\n throw new TypeError('expected object at config root');\n }\n return finalData;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport yaml from 'yaml';\nimport { extname, dirname, resolve as resolvePath } from 'path';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { isObject } from './utils';\nimport { TransformFunc, EnvFunc, ReadFileFunc } from './types';\n\n// Parsers for each type of included file\nconst includeFileParser: {\n [ext in string]: (content: string) => Promise<JsonObject>;\n} = {\n '.json': async content => JSON.parse(content),\n '.yaml': async content => yaml.parse(content),\n '.yml': async content => yaml.parse(content),\n};\n\n/**\n * Transforms a include description into the actual included value.\n */\nexport function createIncludeTransform(\n env: EnvFunc,\n readFile: ReadFileFunc,\n substitute: TransformFunc,\n): TransformFunc {\n return async (input: JsonValue, baseDir: string) => {\n if (!isObject(input)) {\n return { applied: false };\n }\n // Check if there's any key that starts with a '$', in that case we treat\n // this entire object as an include description.\n const [includeKey] = Object.keys(input).filter(key => key.startsWith('$'));\n if (includeKey) {\n if (Object.keys(input).length !== 1) {\n throw new Error(\n `include key ${includeKey} should not have adjacent keys`,\n );\n }\n } else {\n return { applied: false };\n }\n\n const rawIncludedValue = input[includeKey];\n if (typeof rawIncludedValue !== 'string') {\n throw new Error(`${includeKey} include value is not a string`);\n }\n\n const substituteResults = await substitute(rawIncludedValue, baseDir);\n const includeValue = substituteResults.applied\n ? substituteResults.value\n : rawIncludedValue;\n\n // The second string check is needed for Typescript to know this is a string.\n if (includeValue === undefined || typeof includeValue !== 'string') {\n throw new Error(`${includeKey} substitution value was undefined`);\n }\n\n switch (includeKey) {\n case '$file':\n try {\n const value = await readFile(resolvePath(baseDir, includeValue));\n return { applied: true, value };\n } catch (error) {\n throw new Error(`failed to read file ${includeValue}, ${error}`);\n }\n case '$env':\n try {\n return { applied: true, value: await env(includeValue) };\n } catch (error) {\n throw new Error(`failed to read env ${includeValue}, ${error}`);\n }\n\n case '$include': {\n const [filePath, dataPath] = includeValue.split(/#(.*)/);\n\n const ext = extname(filePath);\n const parser = includeFileParser[ext];\n if (!parser) {\n throw new Error(\n `no configuration parser available for included file ${filePath}`,\n );\n }\n\n const path = resolvePath(baseDir, filePath);\n const content = await readFile(path);\n const newBaseDir = dirname(path);\n\n const parts = dataPath ? dataPath.split('.') : [];\n\n let value: JsonValue | undefined;\n try {\n value = await parser(content);\n } catch (error) {\n throw new Error(\n `failed to parse included file ${filePath}, ${error}`,\n );\n }\n\n // This bit handles selecting a subtree in the included file, if a path was provided after a #\n for (const [index, part] of parts.entries()) {\n if (!isObject(value)) {\n const errPath = parts.slice(0, index).join('.');\n throw new Error(\n `value at '${errPath}' in included file ${filePath} is not an object`,\n );\n }\n value = value[part];\n }\n\n return {\n applied: true,\n value,\n newBaseDir: newBaseDir !== baseDir ? newBaseDir : undefined,\n };\n }\n\n default:\n throw new Error(`unknown include ${includeKey}`);\n }\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue } from '@backstage/types';\nimport { TransformFunc, EnvFunc } from './types';\n\n/**\n * A environment variable substitution transform that transforms e.g. 'token ${MY_TOKEN}'\n * to 'token abc' if MY_TOKEN is 'abc'. If any of the substituted variables are undefined,\n * the entire expression ends up undefined.\n */\nexport function createSubstitutionTransform(env: EnvFunc): TransformFunc {\n return async (input: JsonValue) => {\n if (typeof input !== 'string') {\n return { applied: false };\n }\n\n const parts: (string | undefined)[] = input.split(/(\\$?\\$\\{[^{}]*\\})/);\n for (let i = 1; i < parts.length; i += 2) {\n const part = parts[i]!;\n if (part.startsWith('$$')) {\n parts[i] = part.slice(1);\n } else {\n parts[i] = await env(part.slice(2, -1).trim());\n }\n }\n\n if (parts.some(part => part === undefined)) {\n return { applied: true, value: undefined };\n }\n return { applied: true, value: parts.join('') };\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\n\n/**\n * An sub-set of configuration schema.\n */\nexport type ConfigSchemaPackageEntry = {\n /**\n * The configuration schema itself.\n */\n value: JsonObject;\n /**\n * The relative path that the configuration schema was discovered at.\n */\n path: string;\n};\n\n/**\n * A list of all possible configuration value visibilities.\n */\nexport const CONFIG_VISIBILITIES = ['frontend', 'backend', 'secret'] as const;\n\n/**\n * A type representing the possible configuration value visibilities\n *\n * @public\n */\nexport type ConfigVisibility = 'frontend' | 'backend' | 'secret';\n\n/**\n * The default configuration visibility if no other values is given.\n */\nexport const DEFAULT_CONFIG_VISIBILITY: ConfigVisibility = 'backend';\n\n/**\n * An explanation of a configuration validation error.\n */\nexport type ValidationError = {\n keyword: string;\n dataPath: string;\n schemaPath: string;\n params: Record<string, any>;\n propertyName?: string;\n message?: string;\n};\n\n/**\n * The result of validating configuration data using a schema.\n */\ntype ValidationResult = {\n /**\n * Errors that where emitted during validation, if any.\n */\n errors?: ValidationError[];\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/<key>/<sub-key>/<array-index>/<leaf-key>`\n */\n visibilityByDataPath: Map<string, ConfigVisibility>;\n\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/properties/<key>/items/additionalProperties/<leaf-key>`\n */\n visibilityBySchemaPath: Map<string, ConfigVisibility>;\n};\n\n/**\n * A function used validate configuration data.\n */\nexport type ValidationFunc = (configs: AppConfig[]) => ValidationResult;\n\n/**\n * A function used to transform primitive configuration values.\n *\n * @public\n */\nexport type TransformFunc<T extends number | string | boolean> = (\n value: T,\n context: { visibility: ConfigVisibility },\n) => T | undefined;\n\n/**\n * Options used to process configuration data with a schema.\n *\n * @public\n */\nexport type ConfigSchemaProcessingOptions = {\n /**\n * The visibilities that should be included in the output data.\n * If omitted, the data will not be filtered by visibility.\n */\n visibility?: ConfigVisibility[];\n\n /**\n * A transform function that can be used to transform primitive configuration values\n * during validation. The value returned from the transform function will be used\n * instead of the original value. If the transform returns `undefined`, the value\n * will be omitted.\n */\n valueTransform?: TransformFunc<any>;\n\n /**\n * Whether or not to include the `filteredKeys` property in the output `AppConfig`s.\n *\n * Default: `false`.\n */\n withFilteredKeys?: boolean;\n};\n\n/**\n * A loaded configuration schema that is ready to process configuration data.\n *\n * @public\n */\nexport type ConfigSchema = {\n process(\n appConfigs: AppConfig[],\n options?: ConfigSchemaProcessingOptions,\n ): AppConfig[];\n\n serialize(): JsonObject;\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Ajv from 'ajv';\nimport { JSONSchema7 as JSONSchema } from 'json-schema';\nimport mergeAllOf, { Resolvers } from 'json-schema-merge-allof';\nimport traverse from 'json-schema-traverse';\nimport { ConfigReader } from '@backstage/config';\nimport {\n ConfigSchemaPackageEntry,\n ValidationFunc,\n CONFIG_VISIBILITIES,\n ConfigVisibility,\n} from './types';\n\n/**\n * This takes a collection of Backstage configuration schemas from various\n * sources and compiles them down into a single schema validation function.\n *\n * It also handles the implementation of the custom \"visibility\" keyword used\n * to specify the scope of different config paths.\n */\nexport function compileConfigSchemas(\n schemas: ConfigSchemaPackageEntry[],\n): ValidationFunc {\n // The ajv instance below is stateful and doesn't really allow for additional\n // output during validation. We work around this by having this extra piece\n // of state that we reset before each validation.\n const visibilityByDataPath = new Map<string, ConfigVisibility>();\n\n const ajv = new Ajv({\n allErrors: true,\n allowUnionTypes: true,\n schemas: {\n 'https://backstage.io/schema/config-v1': true,\n },\n }).addKeyword({\n keyword: 'visibility',\n metaSchema: {\n type: 'string',\n enum: CONFIG_VISIBILITIES,\n },\n compile(visibility: ConfigVisibility) {\n return (_data, context) => {\n if (context?.dataPath === undefined) {\n return false;\n }\n if (visibility && visibility !== 'backend') {\n const normalizedPath = context.dataPath.replace(\n /\\['?(.*?)'?\\]/g,\n (_, segment) => `/${segment}`,\n );\n visibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n });\n\n for (const schema of schemas) {\n try {\n ajv.compile(schema.value);\n } catch (error) {\n throw new Error(`Schema at ${schema.path} is invalid, ${error}`);\n }\n }\n\n const merged = mergeConfigSchemas(schemas.map(_ => _.value));\n const validate = ajv.compile(merged);\n\n const visibilityBySchemaPath = new Map<string, ConfigVisibility>();\n traverse(merged, (schema, path) => {\n if (schema.visibility && schema.visibility !== 'backend') {\n visibilityBySchemaPath.set(path, schema.visibility);\n }\n });\n\n return configs => {\n const config = ConfigReader.fromConfigs(configs).get();\n\n visibilityByDataPath.clear();\n\n const valid = validate(config);\n if (!valid) {\n return {\n errors: validate.errors ?? [],\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n }\n\n return {\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n };\n}\n\n/**\n * Given a list of configuration schemas from packages, merge them\n * into a single json schema.\n *\n * @public\n */\nexport function mergeConfigSchemas(schemas: JSONSchema[]): JSONSchema {\n const merged = mergeAllOf(\n { allOf: schemas },\n {\n // JSONSchema is typically subtractive, as in it always reduces the set of allowed\n // inputs through constraints. This changes the object property merging to be additive\n // rather than subtractive.\n ignoreAdditionalProperties: true,\n resolvers: {\n // This ensures that the visibilities across different schemas are sound, and\n // selects the most specific visibility for each path.\n visibility(values: string[], path: string[]) {\n const hasFrontend = values.some(_ => _ === 'frontend');\n const hasSecret = values.some(_ => _ === 'secret');\n if (hasFrontend && hasSecret) {\n throw new Error(\n `Config schema visibility is both 'frontend' and 'secret' for ${path.join(\n '/',\n )}`,\n );\n } else if (hasFrontend) {\n return 'frontend';\n } else if (hasSecret) {\n return 'secret';\n }\n\n return 'backend';\n },\n } as Partial<Resolvers<JSONSchema>>,\n },\n );\n return merged;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport {\n resolve as resolvePath,\n relative as relativePath,\n dirname,\n sep,\n} from 'path';\nimport { ConfigSchemaPackageEntry } from './types';\nimport { getProgramFromFiles, generateSchema } from 'typescript-json-schema';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\ntype Item = {\n name?: string;\n parentPath?: string;\n packagePath?: string;\n};\n\nconst req =\n typeof __non_webpack_require__ === 'undefined'\n ? require\n : __non_webpack_require__;\n\n/**\n * This collects all known config schemas across all dependencies of the app.\n */\nexport async function collectConfigSchemas(\n packageNames: string[],\n packagePaths: string[],\n): Promise<ConfigSchemaPackageEntry[]> {\n const schemas = new Array<ConfigSchemaPackageEntry>();\n const tsSchemaPaths = new Array<string>();\n const visitedPackageVersions = new Map<string, Set<string>>(); // pkgName: [versions...]\n\n const currentDir = await fs.realpath(process.cwd());\n\n async function processItem(item: Item) {\n let pkgPath = item.packagePath;\n\n if (pkgPath) {\n const pkgExists = await fs.pathExists(pkgPath);\n if (!pkgExists) {\n return;\n }\n } else if (item.name) {\n const { name, parentPath } = item;\n\n try {\n pkgPath = req.resolve(\n `${name}/package.json`,\n parentPath && {\n paths: [parentPath],\n },\n );\n } catch {\n // We can somewhat safely ignore packages that don't export package.json,\n // as they are likely not part of the Backstage ecosystem anyway.\n }\n }\n if (!pkgPath) {\n return;\n }\n\n const pkg = await fs.readJson(pkgPath);\n\n // Ensures that we only process the same version of each package once.\n let versions = visitedPackageVersions.get(pkg.name);\n if (versions?.has(pkg.version)) {\n return;\n }\n if (!versions) {\n versions = new Set();\n visitedPackageVersions.set(pkg.name, versions);\n }\n versions.add(pkg.version);\n\n const depNames = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ...Object.keys(pkg.optionalDependencies ?? {}),\n ...Object.keys(pkg.peerDependencies ?? {}),\n ];\n\n // TODO(Rugvip): Trying this out to avoid having to traverse the full dependency graph,\n // since that's pretty slow. We probably need a better way to determine when\n // we've left the Backstage ecosystem, but this will do for now.\n const hasSchema = 'configSchema' in pkg;\n const hasBackstageDep = depNames.some(_ => _.startsWith('@backstage/'));\n if (!hasSchema && !hasBackstageDep) {\n return;\n }\n if (hasSchema) {\n if (typeof pkg.configSchema === 'string') {\n const isJson = pkg.configSchema.endsWith('.json');\n const isDts = pkg.configSchema.endsWith('.d.ts');\n if (!isJson && !isDts) {\n throw new Error(\n `Config schema files must be .json or .d.ts, got ${pkg.configSchema}`,\n );\n }\n if (isDts) {\n tsSchemaPaths.push(\n relativePath(\n currentDir,\n resolvePath(dirname(pkgPath), pkg.configSchema),\n ),\n );\n } else {\n const path = resolvePath(dirname(pkgPath), pkg.configSchema);\n const value = await fs.readJson(path);\n schemas.push({\n value,\n path: relativePath(currentDir, path),\n });\n }\n } else {\n schemas.push({\n value: pkg.configSchema,\n path: relativePath(currentDir, pkgPath),\n });\n }\n }\n\n await Promise.all(\n depNames.map(depName =>\n processItem({ name: depName, parentPath: pkgPath }),\n ),\n );\n }\n\n await Promise.all([\n ...packageNames.map(name => processItem({ name, parentPath: currentDir })),\n ...packagePaths.map(path => processItem({ name: path, packagePath: path })),\n ]);\n\n const tsSchemas = compileTsSchemas(tsSchemaPaths);\n\n return schemas.concat(tsSchemas);\n}\n\n// This handles the support of TypeScript .d.ts config schema declarations.\n// We collect all typescript schema definition and compile them all in one go.\n// This is much faster than compiling them separately.\nfunction compileTsSchemas(paths: string[]) {\n if (paths.length === 0) {\n return [];\n }\n\n const program = getProgramFromFiles(paths, {\n incremental: false,\n isolatedModules: true,\n lib: ['ES5'], // Skipping most libs speeds processing up a lot, we just need the primitive types anyway\n noEmit: true,\n noResolve: true,\n skipLibCheck: true, // Skipping lib checks speeds things up\n skipDefaultLibCheck: true,\n strict: true,\n typeRoots: [], // Do not include any additional types\n types: [],\n });\n\n const tsSchemas = paths.map(path => {\n let value;\n try {\n value = generateSchema(\n program,\n // All schemas should export a `Config` symbol\n 'Config',\n // This enables usage of @visibility is doc comments\n {\n required: true,\n validationKeywords: ['visibility'],\n },\n [path.split(sep).join('/')], // Unix paths are expected for all OSes here\n ) as JsonObject | null;\n } catch (error) {\n assertError(error);\n if (error.message !== 'type Config not found') {\n throw error;\n }\n }\n\n if (!value) {\n throw new Error(`Invalid schema in ${path}, missing Config export`);\n }\n return { path, value };\n });\n\n return tsSchemas;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport {\n ConfigVisibility,\n DEFAULT_CONFIG_VISIBILITY,\n TransformFunc,\n ValidationError,\n} from './types';\n\n/**\n * This filters data by visibility by discovering the visibility of each\n * value, and then only keeping the ones that are specified in `includeVisibilities`.\n */\nexport function filterByVisibility(\n data: JsonObject,\n includeVisibilities: ConfigVisibility[],\n visibilityByDataPath: Map<string, ConfigVisibility>,\n transformFunc?: TransformFunc<number | string | boolean>,\n withFilteredKeys?: boolean,\n): { data: JsonObject; filteredKeys?: string[] } {\n const filteredKeys = new Array<string>();\n\n function transform(\n jsonVal: JsonValue,\n visibilityPath: string, // Matches the format we get from ajv\n filterPath: string, // Matches the format of the ConfigReader\n ): JsonValue | undefined {\n const visibility =\n visibilityByDataPath.get(visibilityPath) ?? DEFAULT_CONFIG_VISIBILITY;\n const isVisible = includeVisibilities.includes(visibility);\n\n if (typeof jsonVal !== 'object') {\n if (isVisible) {\n if (transformFunc) {\n return transformFunc(jsonVal, { visibility });\n }\n return jsonVal;\n }\n if (withFilteredKeys) {\n filteredKeys.push(filterPath);\n }\n return undefined;\n } else if (jsonVal === null) {\n return undefined;\n } else if (Array.isArray(jsonVal)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of jsonVal.entries()) {\n const out = transform(\n value,\n `${visibilityPath}/${index}`,\n `${filterPath}[${index}]`,\n );\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n if (arr.length > 0 || isVisible) {\n return arr;\n }\n return undefined;\n }\n\n const outObj: JsonObject = {};\n let hasOutput = false;\n\n for (const [key, value] of Object.entries(jsonVal)) {\n if (value === undefined) {\n continue;\n }\n const out = transform(\n value,\n `${visibilityPath}/${key}`,\n filterPath ? `${filterPath}.${key}` : key,\n );\n if (out !== undefined) {\n outObj[key] = out;\n hasOutput = true;\n }\n }\n\n if (hasOutput || isVisible) {\n return outObj;\n }\n return undefined;\n }\n\n return {\n filteredKeys: withFilteredKeys ? filteredKeys : undefined,\n data: (transform(data, '', '') as JsonObject) ?? {},\n };\n}\n\nexport function filterErrorsByVisibility(\n errors: ValidationError[] | undefined,\n includeVisibilities: ConfigVisibility[] | undefined,\n visibilityByDataPath: Map<string, ConfigVisibility>,\n visibilityBySchemaPath: Map<string, ConfigVisibility>,\n): ValidationError[] {\n if (!errors) {\n return [];\n }\n if (!includeVisibilities) {\n return errors;\n }\n\n const visibleSchemaPaths = Array.from(visibilityBySchemaPath)\n .filter(([, v]) => includeVisibilities.includes(v))\n .map(([k]) => k);\n\n // If we're filtering by visibility we only care about the errors that happened\n // in a visible path.\n return errors.filter(error => {\n // We always include structural errors as we don't know whether there are\n // any visible paths within the structures.\n if (\n error.keyword === 'type' &&\n ['object', 'array'].includes(error.params.type)\n ) {\n return true;\n }\n\n // For fields that were required we use the schema path to determine whether\n // it was visible in addition to the data path. This is because the data path\n // visibilities are only populated for values that we reached, which we won't\n // if the value is missing.\n // We don't use this method for all the errors as the data path is more robust\n // and doesn't require us to properly trim the schema path.\n if (error.keyword === 'required') {\n const trimmedPath = error.schemaPath.slice(1, -'/required'.length);\n const fullPath = `${trimmedPath}/properties/${error.params.missingProperty}`;\n if (\n visibleSchemaPaths.some(visiblePath => visiblePath.startsWith(fullPath))\n ) {\n return true;\n }\n }\n\n const vis =\n visibilityByDataPath.get(error.dataPath) ?? DEFAULT_CONFIG_VISIBILITY;\n return vis && includeVisibilities.includes(vis);\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { compileConfigSchemas } from './compile';\nimport { collectConfigSchemas } from './collect';\nimport { filterByVisibility, filterErrorsByVisibility } from './filtering';\nimport {\n ValidationError,\n ConfigSchema,\n ConfigSchemaPackageEntry,\n CONFIG_VISIBILITIES,\n} from './types';\n\n/**\n * Options that control the loading of configuration schema files in the backend.\n *\n * @public\n */\nexport type LoadConfigSchemaOptions =\n | {\n dependencies: string[];\n packagePaths?: string[];\n }\n | {\n serialized: JsonObject;\n };\n\nfunction errorsToError(errors: ValidationError[]): Error {\n const messages = errors.map(({ dataPath, message, params }) => {\n const paramStr = Object.entries(params)\n .map(([name, value]) => `${name}=${value}`)\n .join(' ');\n return `Config ${message || ''} { ${paramStr} } at ${dataPath}`;\n });\n const error = new Error(`Config validation failed, ${messages.join('; ')}`);\n (error as any).messages = messages;\n return error;\n}\n\n/**\n * Loads config schema for a Backstage instance.\n *\n * @public\n */\nexport async function loadConfigSchema(\n options: LoadConfigSchemaOptions,\n): Promise<ConfigSchema> {\n let schemas: ConfigSchemaPackageEntry[];\n\n if ('dependencies' in options) {\n schemas = await collectConfigSchemas(\n options.dependencies,\n options.packagePaths ?? [],\n );\n } else {\n const { serialized } = options;\n if (serialized?.backstageConfigSchemaVersion !== 1) {\n throw new Error(\n 'Serialized configuration schema is invalid or has an invalid version number',\n );\n }\n schemas = serialized.schemas as ConfigSchemaPackageEntry[];\n }\n\n const validate = compileConfigSchemas(schemas);\n\n return {\n process(\n configs: AppConfig[],\n { visibility, valueTransform, withFilteredKeys } = {},\n ): AppConfig[] {\n const result = validate(configs);\n\n const visibleErrors = filterErrorsByVisibility(\n result.errors,\n visibility,\n result.visibilityByDataPath,\n result.visibilityBySchemaPath,\n );\n if (visibleErrors.length > 0) {\n throw errorsToError(visibleErrors);\n }\n\n let processedConfigs = configs;\n\n if (visibility) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n visibility,\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n } else if (valueTransform) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n Array.from(CONFIG_VISIBILITIES),\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n }\n\n return processedConfigs;\n },\n serialize(): JsonObject {\n return {\n schemas,\n backstageConfigSchemaVersion: 1,\n };\n },\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport function isValidUrl(url: string): boolean {\n try {\n // eslint-disable-next-line no-new\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport yaml from 'yaml';\nimport chokidar from 'chokidar';\nimport { basename, dirname, isAbsolute, resolve as resolvePath } from 'path';\nimport { AppConfig } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport {\n applyConfigTransforms,\n createIncludeTransform,\n createSubstitutionTransform,\n isValidUrl,\n readEnvConfig,\n} from './lib';\nimport fetch from 'node-fetch';\n\nexport type ConfigTarget = { path: string } | { url: string };\n\nexport type LoadConfigOptionsWatch = {\n /**\n * A listener that is called when a config file is changed.\n */\n onChange: (configs: AppConfig[]) => void;\n\n /**\n * An optional signal that stops the watcher once the promise resolves.\n */\n stopSignal?: Promise<void>;\n};\n\nexport type LoadConfigOptionsRemote = {\n /**\n * An optional remote config reloading period, in seconds\n */\n reloadIntervalSeconds: number;\n};\n\n/**\n * Options that control the loading of configuration files in the backend.\n *\n * @public\n */\nexport type LoadConfigOptions = {\n // The root directory of the config loading context. Used to find default configs.\n configRoot: string;\n\n /** Absolute paths to load config files from. Configs from earlier paths have lower priority.\n * @deprecated Use {@link configTargets} instead.\n */\n configPaths: string[];\n\n // Paths to load config files from. Configs from earlier paths have lower priority.\n configTargets: ConfigTarget[];\n\n /** @deprecated This option has been removed */\n env?: string;\n\n /**\n * Custom environment variable loading function\n *\n * @experimental This API is not stable and may change at any point\n */\n experimentalEnvFunc?: (name: string) => Promise<string | undefined>;\n\n /**\n * An optional remote config\n */\n remote?: LoadConfigOptionsRemote;\n\n /**\n * An optional configuration that enables watching of config files.\n */\n watch?: LoadConfigOptionsWatch;\n};\n\n/**\n * Load configuration data.\n *\n * @public\n */\nexport async function loadConfig(\n options: LoadConfigOptions,\n): Promise<AppConfig[]> {\n const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options;\n\n const configPaths: string[] = options.configTargets\n .slice()\n .filter((e): e is { path: string } => e.hasOwnProperty('path'))\n .map(configTarget => configTarget.path);\n\n // Append deprecated configPaths to the absolute config paths received via configTargets.\n options.configPaths.forEach(cp => {\n if (!configPaths.includes(cp)) {\n configPaths.push(cp);\n }\n });\n\n const configUrls: string[] = options.configTargets\n .slice()\n .filter((e): e is { url: string } => e.hasOwnProperty('url'))\n .map(configTarget => configTarget.url);\n\n if (remote === undefined && configUrls.length > 0) {\n throw new Error(`Remote config detected but this feature is turned off`);\n }\n\n // If no paths are provided, we default to reading\n // `app-config.yaml` and, if it exists, `app-config.local.yaml`\n if (configPaths.length === 0 && configUrls.length === 0) {\n configPaths.push(resolvePath(configRoot, 'app-config.yaml'));\n\n const localConfig = resolvePath(configRoot, 'app-config.local.yaml');\n if (await fs.pathExists(localConfig)) {\n configPaths.push(localConfig);\n }\n }\n\n const env = envFunc ?? (async (name: string) => process.env[name]);\n\n const loadConfigFiles = async () => {\n const configs = [];\n\n for (const configPath of configPaths) {\n if (!isAbsolute(configPath)) {\n throw new Error(`Config load path is not absolute: '${configPath}'`);\n }\n\n const dir = dirname(configPath);\n const readFile = (path: string) =>\n fs.readFile(resolvePath(dir, path), 'utf8');\n\n const input = yaml.parse(await readFile(configPath));\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(dir, input, [\n createIncludeTransform(env, readFile, substitutionTransform),\n substitutionTransform,\n ]);\n\n configs.push({ data, context: basename(configPath) });\n }\n\n return configs;\n };\n\n const loadRemoteConfigFiles = async () => {\n const configs: AppConfig[] = [];\n\n const readConfigFromUrl = async (url: string) => {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Could not read config file at ${url}`);\n }\n\n return await response.text();\n };\n\n for (let i = 0; i < configUrls.length; i++) {\n const configUrl = configUrls[i];\n if (!isValidUrl(configUrl)) {\n throw new Error(`Config load path is not valid: '${configUrl}'`);\n }\n\n const remoteConfigContent = await readConfigFromUrl(configUrl);\n if (!remoteConfigContent) {\n throw new Error(`Config is not valid`);\n }\n const configYaml = yaml.parse(remoteConfigContent);\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(configRoot, configYaml, [\n substitutionTransform,\n ]);\n\n configs.push({ data, context: configUrl });\n }\n\n return configs;\n };\n\n let fileConfigs: AppConfig[];\n try {\n fileConfigs = await loadConfigFiles();\n } catch (error) {\n throw new ForwardedError('Failed to read static configuration file', error);\n }\n\n let remoteConfigs: AppConfig[] = [];\n if (remote) {\n try {\n remoteConfigs = await loadRemoteConfigFiles();\n } catch (error) {\n throw new ForwardedError(\n `Failed to read remote configuration file`,\n error,\n );\n }\n }\n\n const envConfigs = await readEnvConfig(process.env);\n\n const watchConfigFile = (watchProp: LoadConfigOptionsWatch) => {\n const watcher = chokidar.watch(configPaths, {\n usePolling: process.env.NODE_ENV === 'test',\n });\n\n let currentSerializedConfig = JSON.stringify(fileConfigs);\n watcher.on('change', async () => {\n try {\n const newConfigs = await loadConfigFiles();\n const newSerializedConfig = JSON.stringify(newConfigs);\n\n if (currentSerializedConfig === newSerializedConfig) {\n return;\n }\n currentSerializedConfig = newSerializedConfig;\n\n watchProp.onChange([...remoteConfigs, ...newConfigs, ...envConfigs]);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n });\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n watcher.close();\n });\n }\n };\n\n const watchRemoteConfig = (\n watchProp: LoadConfigOptionsWatch,\n remoteProp: LoadConfigOptionsRemote,\n ) => {\n const hasConfigChanged = async (\n oldRemoteConfigs: AppConfig[],\n newRemoteConfigs: AppConfig[],\n ) => {\n return (\n JSON.stringify(oldRemoteConfigs) !== JSON.stringify(newRemoteConfigs)\n );\n };\n\n let handle: NodeJS.Timeout | undefined;\n try {\n handle = setInterval(async () => {\n console.info(`Checking for config update`);\n const newRemoteConfigs = await loadRemoteConfigFiles();\n if (await hasConfigChanged(remoteConfigs, newRemoteConfigs)) {\n remoteConfigs = newRemoteConfigs;\n console.info(`Remote config change, reloading config ...`);\n watchProp.onChange([...remoteConfigs, ...fileConfigs, ...envConfigs]);\n console.info(`Remote config reloaded`);\n }\n }, remoteProp.reloadIntervalSeconds * 1000);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n if (handle !== undefined) {\n console.info(`Stopping remote config watch`);\n clearInterval(handle);\n handle = undefined;\n }\n });\n }\n };\n\n // Set up config file watching if requested by the caller\n if (watch) {\n watchConfigFile(watch);\n }\n\n if (watch && remote) {\n watchRemoteConfig(watch, remote);\n }\n\n return remote\n ? [...remoteConfigs, ...fileConfigs, ...envConfigs]\n : [...fileConfigs, ...envConfigs];\n}\n"],"names":["yaml","resolvePath","extname","path","dirname","Ajv","config","ConfigReader","mergeAllOf","fs","relativePath","getProgramFromFiles","generateSchema","sep","isAbsolute","basename","fetch","ForwardedError","chokidar"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -101,6 +101,27 @@ declare type LoadConfigSchemaOptions = {
|
|
|
101
101
|
*/
|
|
102
102
|
declare function loadConfigSchema(options: LoadConfigSchemaOptions): Promise<ConfigSchema>;
|
|
103
103
|
|
|
104
|
+
declare type ConfigTarget = {
|
|
105
|
+
path: string;
|
|
106
|
+
} | {
|
|
107
|
+
url: string;
|
|
108
|
+
};
|
|
109
|
+
declare type LoadConfigOptionsWatch = {
|
|
110
|
+
/**
|
|
111
|
+
* A listener that is called when a config file is changed.
|
|
112
|
+
*/
|
|
113
|
+
onChange: (configs: AppConfig[]) => void;
|
|
114
|
+
/**
|
|
115
|
+
* An optional signal that stops the watcher once the promise resolves.
|
|
116
|
+
*/
|
|
117
|
+
stopSignal?: Promise<void>;
|
|
118
|
+
};
|
|
119
|
+
declare type LoadConfigOptionsRemote = {
|
|
120
|
+
/**
|
|
121
|
+
* An optional remote config reloading period, in seconds
|
|
122
|
+
*/
|
|
123
|
+
reloadIntervalSeconds: number;
|
|
124
|
+
};
|
|
104
125
|
/**
|
|
105
126
|
* Options that control the loading of configuration files in the backend.
|
|
106
127
|
*
|
|
@@ -108,7 +129,11 @@ declare function loadConfigSchema(options: LoadConfigSchemaOptions): Promise<Con
|
|
|
108
129
|
*/
|
|
109
130
|
declare type LoadConfigOptions = {
|
|
110
131
|
configRoot: string;
|
|
132
|
+
/** Absolute paths to load config files from. Configs from earlier paths have lower priority.
|
|
133
|
+
* @deprecated Use {@link configTargets} instead.
|
|
134
|
+
*/
|
|
111
135
|
configPaths: string[];
|
|
136
|
+
configTargets: ConfigTarget[];
|
|
112
137
|
/** @deprecated This option has been removed */
|
|
113
138
|
env?: string;
|
|
114
139
|
/**
|
|
@@ -117,19 +142,14 @@ declare type LoadConfigOptions = {
|
|
|
117
142
|
* @experimental This API is not stable and may change at any point
|
|
118
143
|
*/
|
|
119
144
|
experimentalEnvFunc?: (name: string) => Promise<string | undefined>;
|
|
145
|
+
/**
|
|
146
|
+
* An optional remote config
|
|
147
|
+
*/
|
|
148
|
+
remote?: LoadConfigOptionsRemote;
|
|
120
149
|
/**
|
|
121
150
|
* An optional configuration that enables watching of config files.
|
|
122
151
|
*/
|
|
123
|
-
watch?:
|
|
124
|
-
/**
|
|
125
|
-
* A listener that is called when a config file is changed.
|
|
126
|
-
*/
|
|
127
|
-
onChange: (configs: AppConfig[]) => void;
|
|
128
|
-
/**
|
|
129
|
-
* An optional signal that stops the watcher once the promise resolves.
|
|
130
|
-
*/
|
|
131
|
-
stopSignal?: Promise<void>;
|
|
132
|
-
};
|
|
152
|
+
watch?: LoadConfigOptionsWatch;
|
|
133
153
|
};
|
|
134
154
|
/**
|
|
135
155
|
* Load configuration data.
|
|
@@ -138,4 +158,4 @@ declare type LoadConfigOptions = {
|
|
|
138
158
|
*/
|
|
139
159
|
declare function loadConfig(options: LoadConfigOptions): Promise<AppConfig[]>;
|
|
140
160
|
|
|
141
|
-
export { ConfigSchema, ConfigSchemaProcessingOptions, ConfigVisibility, LoadConfigOptions, LoadConfigSchemaOptions, TransformFunc, loadConfig, loadConfigSchema, mergeConfigSchemas, readEnvConfig };
|
|
161
|
+
export { ConfigSchema, ConfigSchemaProcessingOptions, ConfigTarget, ConfigVisibility, LoadConfigOptions, LoadConfigOptionsRemote, LoadConfigOptionsWatch, LoadConfigSchemaOptions, TransformFunc, loadConfig, loadConfigSchema, mergeConfigSchemas, readEnvConfig };
|
package/dist/index.esm.js
CHANGED
|
@@ -8,6 +8,7 @@ import { ConfigReader } from '@backstage/config';
|
|
|
8
8
|
import fs from 'fs-extra';
|
|
9
9
|
import { getProgramFromFiles, generateSchema } from 'typescript-json-schema';
|
|
10
10
|
import chokidar from 'chokidar';
|
|
11
|
+
import fetch from 'node-fetch';
|
|
11
12
|
|
|
12
13
|
const ENV_PREFIX = "APP_CONFIG_";
|
|
13
14
|
const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;
|
|
@@ -553,10 +554,28 @@ async function loadConfigSchema(options) {
|
|
|
553
554
|
};
|
|
554
555
|
}
|
|
555
556
|
|
|
557
|
+
function isValidUrl(url) {
|
|
558
|
+
try {
|
|
559
|
+
new URL(url);
|
|
560
|
+
return true;
|
|
561
|
+
} catch {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
556
566
|
async function loadConfig(options) {
|
|
557
|
-
const {configRoot, experimentalEnvFunc: envFunc, watch} = options;
|
|
558
|
-
const configPaths = options.
|
|
559
|
-
|
|
567
|
+
const {configRoot, experimentalEnvFunc: envFunc, watch, remote} = options;
|
|
568
|
+
const configPaths = options.configTargets.slice().filter((e) => e.hasOwnProperty("path")).map((configTarget) => configTarget.path);
|
|
569
|
+
options.configPaths.forEach((cp) => {
|
|
570
|
+
if (!configPaths.includes(cp)) {
|
|
571
|
+
configPaths.push(cp);
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
const configUrls = options.configTargets.slice().filter((e) => e.hasOwnProperty("url")).map((configTarget) => configTarget.url);
|
|
575
|
+
if (remote === void 0 && configUrls.length > 0) {
|
|
576
|
+
throw new Error(`Remote config detected but this feature is turned off`);
|
|
577
|
+
}
|
|
578
|
+
if (configPaths.length === 0 && configUrls.length === 0) {
|
|
560
579
|
configPaths.push(resolve(configRoot, "app-config.yaml"));
|
|
561
580
|
const localConfig = resolve(configRoot, "app-config.local.yaml");
|
|
562
581
|
if (await fs.pathExists(localConfig)) {
|
|
@@ -582,18 +601,53 @@ async function loadConfig(options) {
|
|
|
582
601
|
}
|
|
583
602
|
return configs;
|
|
584
603
|
};
|
|
604
|
+
const loadRemoteConfigFiles = async () => {
|
|
605
|
+
const configs = [];
|
|
606
|
+
const readConfigFromUrl = async (url) => {
|
|
607
|
+
const response = await fetch(url);
|
|
608
|
+
if (!response.ok) {
|
|
609
|
+
throw new Error(`Could not read config file at ${url}`);
|
|
610
|
+
}
|
|
611
|
+
return await response.text();
|
|
612
|
+
};
|
|
613
|
+
for (let i = 0; i < configUrls.length; i++) {
|
|
614
|
+
const configUrl = configUrls[i];
|
|
615
|
+
if (!isValidUrl(configUrl)) {
|
|
616
|
+
throw new Error(`Config load path is not valid: '${configUrl}'`);
|
|
617
|
+
}
|
|
618
|
+
const remoteConfigContent = await readConfigFromUrl(configUrl);
|
|
619
|
+
if (!remoteConfigContent) {
|
|
620
|
+
throw new Error(`Config is not valid`);
|
|
621
|
+
}
|
|
622
|
+
const configYaml = yaml.parse(remoteConfigContent);
|
|
623
|
+
const substitutionTransform = createSubstitutionTransform(env);
|
|
624
|
+
const data = await applyConfigTransforms(configRoot, configYaml, [
|
|
625
|
+
substitutionTransform
|
|
626
|
+
]);
|
|
627
|
+
configs.push({data, context: configUrl});
|
|
628
|
+
}
|
|
629
|
+
return configs;
|
|
630
|
+
};
|
|
585
631
|
let fileConfigs;
|
|
586
632
|
try {
|
|
587
633
|
fileConfigs = await loadConfigFiles();
|
|
588
634
|
} catch (error) {
|
|
589
635
|
throw new ForwardedError("Failed to read static configuration file", error);
|
|
590
636
|
}
|
|
637
|
+
let remoteConfigs = [];
|
|
638
|
+
if (remote) {
|
|
639
|
+
try {
|
|
640
|
+
remoteConfigs = await loadRemoteConfigFiles();
|
|
641
|
+
} catch (error) {
|
|
642
|
+
throw new ForwardedError(`Failed to read remote configuration file`, error);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
591
645
|
const envConfigs = await readEnvConfig(process.env);
|
|
592
|
-
|
|
593
|
-
let currentSerializedConfig = JSON.stringify(fileConfigs);
|
|
646
|
+
const watchConfigFile = (watchProp) => {
|
|
594
647
|
const watcher = chokidar.watch(configPaths, {
|
|
595
648
|
usePolling: process.env.NODE_ENV === "test"
|
|
596
649
|
});
|
|
650
|
+
let currentSerializedConfig = JSON.stringify(fileConfigs);
|
|
597
651
|
watcher.on("change", async () => {
|
|
598
652
|
try {
|
|
599
653
|
const newConfigs = await loadConfigFiles();
|
|
@@ -602,18 +656,53 @@ async function loadConfig(options) {
|
|
|
602
656
|
return;
|
|
603
657
|
}
|
|
604
658
|
currentSerializedConfig = newSerializedConfig;
|
|
605
|
-
|
|
659
|
+
watchProp.onChange([...remoteConfigs, ...newConfigs, ...envConfigs]);
|
|
606
660
|
} catch (error) {
|
|
607
661
|
console.error(`Failed to reload configuration files, ${error}`);
|
|
608
662
|
}
|
|
609
663
|
});
|
|
610
|
-
if (
|
|
611
|
-
|
|
664
|
+
if (watchProp.stopSignal) {
|
|
665
|
+
watchProp.stopSignal.then(() => {
|
|
612
666
|
watcher.close();
|
|
613
667
|
});
|
|
614
668
|
}
|
|
669
|
+
};
|
|
670
|
+
const watchRemoteConfig = (watchProp, remoteProp) => {
|
|
671
|
+
const hasConfigChanged = async (oldRemoteConfigs, newRemoteConfigs) => {
|
|
672
|
+
return JSON.stringify(oldRemoteConfigs) !== JSON.stringify(newRemoteConfigs);
|
|
673
|
+
};
|
|
674
|
+
let handle;
|
|
675
|
+
try {
|
|
676
|
+
handle = setInterval(async () => {
|
|
677
|
+
console.info(`Checking for config update`);
|
|
678
|
+
const newRemoteConfigs = await loadRemoteConfigFiles();
|
|
679
|
+
if (await hasConfigChanged(remoteConfigs, newRemoteConfigs)) {
|
|
680
|
+
remoteConfigs = newRemoteConfigs;
|
|
681
|
+
console.info(`Remote config change, reloading config ...`);
|
|
682
|
+
watchProp.onChange([...remoteConfigs, ...fileConfigs, ...envConfigs]);
|
|
683
|
+
console.info(`Remote config reloaded`);
|
|
684
|
+
}
|
|
685
|
+
}, remoteProp.reloadIntervalSeconds * 1e3);
|
|
686
|
+
} catch (error) {
|
|
687
|
+
console.error(`Failed to reload configuration files, ${error}`);
|
|
688
|
+
}
|
|
689
|
+
if (watchProp.stopSignal) {
|
|
690
|
+
watchProp.stopSignal.then(() => {
|
|
691
|
+
if (handle !== void 0) {
|
|
692
|
+
console.info(`Stopping remote config watch`);
|
|
693
|
+
clearInterval(handle);
|
|
694
|
+
handle = void 0;
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
if (watch) {
|
|
700
|
+
watchConfigFile(watch);
|
|
701
|
+
}
|
|
702
|
+
if (watch && remote) {
|
|
703
|
+
watchRemoteConfig(watch, remote);
|
|
615
704
|
}
|
|
616
|
-
return [...fileConfigs, ...envConfigs];
|
|
705
|
+
return remote ? [...remoteConfigs, ...fileConfigs, ...envConfigs] : [...fileConfigs, ...envConfigs];
|
|
617
706
|
}
|
|
618
707
|
|
|
619
708
|
export { loadConfig, loadConfigSchema, mergeConfigSchemas, readEnvConfig };
|
package/dist/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":["../src/lib/env.ts","../src/lib/transform/utils.ts","../src/lib/transform/apply.ts","../src/lib/transform/include.ts","../src/lib/transform/substitution.ts","../src/lib/schema/types.ts","../src/lib/schema/compile.ts","../src/lib/schema/collect.ts","../src/lib/schema/filtering.ts","../src/lib/schema/load.ts","../src/loader.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\nconst ENV_PREFIX = 'APP_CONFIG_';\n\n// Update the same pattern in config package if this is changed\nconst CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;\n\n/**\n * Read runtime configuration from the environment.\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n */\nexport function readEnvConfig(env: {\n [name: string]: string | undefined;\n}): AppConfig[] {\n let data: JsonObject | undefined = undefined;\n\n for (const [name, value] of Object.entries(env)) {\n if (!value) {\n continue;\n }\n if (name.startsWith(ENV_PREFIX)) {\n const key = name.replace(ENV_PREFIX, '');\n const keyParts = key.split('_');\n\n let obj = (data = data ?? {});\n for (const [index, part] of keyParts.entries()) {\n if (!CONFIG_KEY_PART_PATTERN.test(part)) {\n throw new TypeError(`Invalid env config key '${key}'`);\n }\n if (index < keyParts.length - 1) {\n obj = (obj[part] = obj[part] ?? {}) as JsonObject;\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n const subKey = keyParts.slice(0, index + 1).join('_');\n throw new TypeError(\n `Could not nest config for key '${key}' under existing value '${subKey}'`,\n );\n }\n } else {\n if (part in obj) {\n throw new TypeError(\n `Refusing to override existing config at key '${key}'`,\n );\n }\n try {\n const [, parsedValue] = safeJsonParse(value);\n if (parsedValue === null) {\n throw new Error('value may not be null');\n }\n obj[part] = parsedValue;\n } catch (error) {\n throw new TypeError(\n `Failed to parse JSON-serialized config value for key '${key}', ${error}`,\n );\n }\n }\n }\n }\n }\n\n return data ? [{ data, context: 'env' }] : [];\n}\n\nfunction safeJsonParse(str: string): [Error | null, any] {\n try {\n return [null, JSON.parse(str)];\n } catch (err) {\n assertError(err);\n return [err, str];\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue, JsonObject } from '@backstage/types';\n\nexport function isObject(obj: JsonValue | undefined): obj is JsonObject {\n if (typeof obj !== 'object') {\n return false;\n } else if (Array.isArray(obj)) {\n return false;\n }\n return obj !== null;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\nimport { TransformFunc } from './types';\nimport { isObject } from './utils';\n\n/**\n * Applies a set of transforms to raw configuration data.\n */\nexport async function applyConfigTransforms(\n initialDir: string,\n input: JsonValue,\n transforms: TransformFunc[],\n): Promise<JsonObject> {\n async function transform(\n inputObj: JsonValue,\n path: string,\n baseDir: string,\n ): Promise<JsonValue | undefined> {\n let obj = inputObj;\n let dir = baseDir;\n\n for (const tf of transforms) {\n try {\n const result = await tf(inputObj, baseDir);\n if (result.applied) {\n if (result.value === undefined) {\n return undefined;\n }\n obj = result.value;\n dir = result.newBaseDir ?? dir;\n break;\n }\n } catch (error) {\n assertError(error);\n throw new Error(`error at ${path}, ${error.message}`);\n }\n }\n\n if (typeof obj !== 'object') {\n return obj;\n } else if (obj === null) {\n return undefined;\n } else if (Array.isArray(obj)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of obj.entries()) {\n const out = await transform(value, `${path}[${index}]`, dir);\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n return arr;\n }\n\n const out: JsonObject = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // undefined covers optional fields\n if (value !== undefined) {\n const result = await transform(value, `${path}.${key}`, dir);\n if (result !== undefined) {\n out[key] = result;\n }\n }\n }\n\n return out;\n }\n\n const finalData = await transform(input, '', initialDir);\n if (!isObject(finalData)) {\n throw new TypeError('expected object at config root');\n }\n return finalData;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport yaml from 'yaml';\nimport { extname, dirname, resolve as resolvePath } from 'path';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { isObject } from './utils';\nimport { TransformFunc, EnvFunc, ReadFileFunc } from './types';\n\n// Parsers for each type of included file\nconst includeFileParser: {\n [ext in string]: (content: string) => Promise<JsonObject>;\n} = {\n '.json': async content => JSON.parse(content),\n '.yaml': async content => yaml.parse(content),\n '.yml': async content => yaml.parse(content),\n};\n\n/**\n * Transforms a include description into the actual included value.\n */\nexport function createIncludeTransform(\n env: EnvFunc,\n readFile: ReadFileFunc,\n substitute: TransformFunc,\n): TransformFunc {\n return async (input: JsonValue, baseDir: string) => {\n if (!isObject(input)) {\n return { applied: false };\n }\n // Check if there's any key that starts with a '$', in that case we treat\n // this entire object as an include description.\n const [includeKey] = Object.keys(input).filter(key => key.startsWith('$'));\n if (includeKey) {\n if (Object.keys(input).length !== 1) {\n throw new Error(\n `include key ${includeKey} should not have adjacent keys`,\n );\n }\n } else {\n return { applied: false };\n }\n\n const rawIncludedValue = input[includeKey];\n if (typeof rawIncludedValue !== 'string') {\n throw new Error(`${includeKey} include value is not a string`);\n }\n\n const substituteResults = await substitute(rawIncludedValue, baseDir);\n const includeValue = substituteResults.applied\n ? substituteResults.value\n : rawIncludedValue;\n\n // The second string check is needed for Typescript to know this is a string.\n if (includeValue === undefined || typeof includeValue !== 'string') {\n throw new Error(`${includeKey} substitution value was undefined`);\n }\n\n switch (includeKey) {\n case '$file':\n try {\n const value = await readFile(resolvePath(baseDir, includeValue));\n return { applied: true, value };\n } catch (error) {\n throw new Error(`failed to read file ${includeValue}, ${error}`);\n }\n case '$env':\n try {\n return { applied: true, value: await env(includeValue) };\n } catch (error) {\n throw new Error(`failed to read env ${includeValue}, ${error}`);\n }\n\n case '$include': {\n const [filePath, dataPath] = includeValue.split(/#(.*)/);\n\n const ext = extname(filePath);\n const parser = includeFileParser[ext];\n if (!parser) {\n throw new Error(\n `no configuration parser available for included file ${filePath}`,\n );\n }\n\n const path = resolvePath(baseDir, filePath);\n const content = await readFile(path);\n const newBaseDir = dirname(path);\n\n const parts = dataPath ? dataPath.split('.') : [];\n\n let value: JsonValue | undefined;\n try {\n value = await parser(content);\n } catch (error) {\n throw new Error(\n `failed to parse included file ${filePath}, ${error}`,\n );\n }\n\n // This bit handles selecting a subtree in the included file, if a path was provided after a #\n for (const [index, part] of parts.entries()) {\n if (!isObject(value)) {\n const errPath = parts.slice(0, index).join('.');\n throw new Error(\n `value at '${errPath}' in included file ${filePath} is not an object`,\n );\n }\n value = value[part];\n }\n\n return {\n applied: true,\n value,\n newBaseDir: newBaseDir !== baseDir ? newBaseDir : undefined,\n };\n }\n\n default:\n throw new Error(`unknown include ${includeKey}`);\n }\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue } from '@backstage/types';\nimport { TransformFunc, EnvFunc } from './types';\n\n/**\n * A environment variable substitution transform that transforms e.g. 'token ${MY_TOKEN}'\n * to 'token abc' if MY_TOKEN is 'abc'. If any of the substituted variables are undefined,\n * the entire expression ends up undefined.\n */\nexport function createSubstitutionTransform(env: EnvFunc): TransformFunc {\n return async (input: JsonValue) => {\n if (typeof input !== 'string') {\n return { applied: false };\n }\n\n const parts: (string | undefined)[] = input.split(/(\\$?\\$\\{[^{}]*\\})/);\n for (let i = 1; i < parts.length; i += 2) {\n const part = parts[i]!;\n if (part.startsWith('$$')) {\n parts[i] = part.slice(1);\n } else {\n parts[i] = await env(part.slice(2, -1).trim());\n }\n }\n\n if (parts.some(part => part === undefined)) {\n return { applied: true, value: undefined };\n }\n return { applied: true, value: parts.join('') };\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\n\n/**\n * An sub-set of configuration schema.\n */\nexport type ConfigSchemaPackageEntry = {\n /**\n * The configuration schema itself.\n */\n value: JsonObject;\n /**\n * The relative path that the configuration schema was discovered at.\n */\n path: string;\n};\n\n/**\n * A list of all possible configuration value visibilities.\n */\nexport const CONFIG_VISIBILITIES = ['frontend', 'backend', 'secret'] as const;\n\n/**\n * A type representing the possible configuration value visibilities\n *\n * @public\n */\nexport type ConfigVisibility = 'frontend' | 'backend' | 'secret';\n\n/**\n * The default configuration visibility if no other values is given.\n */\nexport const DEFAULT_CONFIG_VISIBILITY: ConfigVisibility = 'backend';\n\n/**\n * An explanation of a configuration validation error.\n */\nexport type ValidationError = {\n keyword: string;\n dataPath: string;\n schemaPath: string;\n params: Record<string, any>;\n propertyName?: string;\n message?: string;\n};\n\n/**\n * The result of validating configuration data using a schema.\n */\ntype ValidationResult = {\n /**\n * Errors that where emitted during validation, if any.\n */\n errors?: ValidationError[];\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/<key>/<sub-key>/<array-index>/<leaf-key>`\n */\n visibilityByDataPath: Map<string, ConfigVisibility>;\n\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/properties/<key>/items/additionalProperties/<leaf-key>`\n */\n visibilityBySchemaPath: Map<string, ConfigVisibility>;\n};\n\n/**\n * A function used validate configuration data.\n */\nexport type ValidationFunc = (configs: AppConfig[]) => ValidationResult;\n\n/**\n * A function used to transform primitive configuration values.\n *\n * @public\n */\nexport type TransformFunc<T extends number | string | boolean> = (\n value: T,\n context: { visibility: ConfigVisibility },\n) => T | undefined;\n\n/**\n * Options used to process configuration data with a schema.\n *\n * @public\n */\nexport type ConfigSchemaProcessingOptions = {\n /**\n * The visibilities that should be included in the output data.\n * If omitted, the data will not be filtered by visibility.\n */\n visibility?: ConfigVisibility[];\n\n /**\n * A transform function that can be used to transform primitive configuration values\n * during validation. The value returned from the transform function will be used\n * instead of the original value. If the transform returns `undefined`, the value\n * will be omitted.\n */\n valueTransform?: TransformFunc<any>;\n\n /**\n * Whether or not to include the `filteredKeys` property in the output `AppConfig`s.\n *\n * Default: `false`.\n */\n withFilteredKeys?: boolean;\n};\n\n/**\n * A loaded configuration schema that is ready to process configuration data.\n *\n * @public\n */\nexport type ConfigSchema = {\n process(\n appConfigs: AppConfig[],\n options?: ConfigSchemaProcessingOptions,\n ): AppConfig[];\n\n serialize(): JsonObject;\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Ajv from 'ajv';\nimport { JSONSchema7 as JSONSchema } from 'json-schema';\nimport mergeAllOf, { Resolvers } from 'json-schema-merge-allof';\nimport traverse from 'json-schema-traverse';\nimport { ConfigReader } from '@backstage/config';\nimport {\n ConfigSchemaPackageEntry,\n ValidationFunc,\n CONFIG_VISIBILITIES,\n ConfigVisibility,\n} from './types';\n\n/**\n * This takes a collection of Backstage configuration schemas from various\n * sources and compiles them down into a single schema validation function.\n *\n * It also handles the implementation of the custom \"visibility\" keyword used\n * to specify the scope of different config paths.\n */\nexport function compileConfigSchemas(\n schemas: ConfigSchemaPackageEntry[],\n): ValidationFunc {\n // The ajv instance below is stateful and doesn't really allow for additional\n // output during validation. We work around this by having this extra piece\n // of state that we reset before each validation.\n const visibilityByDataPath = new Map<string, ConfigVisibility>();\n\n const ajv = new Ajv({\n allErrors: true,\n allowUnionTypes: true,\n schemas: {\n 'https://backstage.io/schema/config-v1': true,\n },\n }).addKeyword({\n keyword: 'visibility',\n metaSchema: {\n type: 'string',\n enum: CONFIG_VISIBILITIES,\n },\n compile(visibility: ConfigVisibility) {\n return (_data, context) => {\n if (context?.dataPath === undefined) {\n return false;\n }\n if (visibility && visibility !== 'backend') {\n const normalizedPath = context.dataPath.replace(\n /\\['?(.*?)'?\\]/g,\n (_, segment) => `/${segment}`,\n );\n visibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n });\n\n for (const schema of schemas) {\n try {\n ajv.compile(schema.value);\n } catch (error) {\n throw new Error(`Schema at ${schema.path} is invalid, ${error}`);\n }\n }\n\n const merged = mergeConfigSchemas(schemas.map(_ => _.value));\n const validate = ajv.compile(merged);\n\n const visibilityBySchemaPath = new Map<string, ConfigVisibility>();\n traverse(merged, (schema, path) => {\n if (schema.visibility && schema.visibility !== 'backend') {\n visibilityBySchemaPath.set(path, schema.visibility);\n }\n });\n\n return configs => {\n const config = ConfigReader.fromConfigs(configs).get();\n\n visibilityByDataPath.clear();\n\n const valid = validate(config);\n if (!valid) {\n return {\n errors: validate.errors ?? [],\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n }\n\n return {\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n };\n}\n\n/**\n * Given a list of configuration schemas from packages, merge them\n * into a single json schema.\n *\n * @public\n */\nexport function mergeConfigSchemas(schemas: JSONSchema[]): JSONSchema {\n const merged = mergeAllOf(\n { allOf: schemas },\n {\n // JSONSchema is typically subtractive, as in it always reduces the set of allowed\n // inputs through constraints. This changes the object property merging to be additive\n // rather than subtractive.\n ignoreAdditionalProperties: true,\n resolvers: {\n // This ensures that the visibilities across different schemas are sound, and\n // selects the most specific visibility for each path.\n visibility(values: string[], path: string[]) {\n const hasFrontend = values.some(_ => _ === 'frontend');\n const hasSecret = values.some(_ => _ === 'secret');\n if (hasFrontend && hasSecret) {\n throw new Error(\n `Config schema visibility is both 'frontend' and 'secret' for ${path.join(\n '/',\n )}`,\n );\n } else if (hasFrontend) {\n return 'frontend';\n } else if (hasSecret) {\n return 'secret';\n }\n\n return 'backend';\n },\n } as Partial<Resolvers<JSONSchema>>,\n },\n );\n return merged;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport {\n resolve as resolvePath,\n relative as relativePath,\n dirname,\n sep,\n} from 'path';\nimport { ConfigSchemaPackageEntry } from './types';\nimport { getProgramFromFiles, generateSchema } from 'typescript-json-schema';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\ntype Item = {\n name?: string;\n parentPath?: string;\n packagePath?: string;\n};\n\nconst req =\n typeof __non_webpack_require__ === 'undefined'\n ? require\n : __non_webpack_require__;\n\n/**\n * This collects all known config schemas across all dependencies of the app.\n */\nexport async function collectConfigSchemas(\n packageNames: string[],\n packagePaths: string[],\n): Promise<ConfigSchemaPackageEntry[]> {\n const schemas = new Array<ConfigSchemaPackageEntry>();\n const tsSchemaPaths = new Array<string>();\n const visitedPackageVersions = new Map<string, Set<string>>(); // pkgName: [versions...]\n\n const currentDir = await fs.realpath(process.cwd());\n\n async function processItem(item: Item) {\n let pkgPath = item.packagePath;\n\n if (pkgPath) {\n const pkgExists = await fs.pathExists(pkgPath);\n if (!pkgExists) {\n return;\n }\n } else if (item.name) {\n const { name, parentPath } = item;\n\n try {\n pkgPath = req.resolve(\n `${name}/package.json`,\n parentPath && {\n paths: [parentPath],\n },\n );\n } catch {\n // We can somewhat safely ignore packages that don't export package.json,\n // as they are likely not part of the Backstage ecosystem anyway.\n }\n }\n if (!pkgPath) {\n return;\n }\n\n const pkg = await fs.readJson(pkgPath);\n\n // Ensures that we only process the same version of each package once.\n let versions = visitedPackageVersions.get(pkg.name);\n if (versions?.has(pkg.version)) {\n return;\n }\n if (!versions) {\n versions = new Set();\n visitedPackageVersions.set(pkg.name, versions);\n }\n versions.add(pkg.version);\n\n const depNames = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ...Object.keys(pkg.optionalDependencies ?? {}),\n ...Object.keys(pkg.peerDependencies ?? {}),\n ];\n\n // TODO(Rugvip): Trying this out to avoid having to traverse the full dependency graph,\n // since that's pretty slow. We probably need a better way to determine when\n // we've left the Backstage ecosystem, but this will do for now.\n const hasSchema = 'configSchema' in pkg;\n const hasBackstageDep = depNames.some(_ => _.startsWith('@backstage/'));\n if (!hasSchema && !hasBackstageDep) {\n return;\n }\n if (hasSchema) {\n if (typeof pkg.configSchema === 'string') {\n const isJson = pkg.configSchema.endsWith('.json');\n const isDts = pkg.configSchema.endsWith('.d.ts');\n if (!isJson && !isDts) {\n throw new Error(\n `Config schema files must be .json or .d.ts, got ${pkg.configSchema}`,\n );\n }\n if (isDts) {\n tsSchemaPaths.push(\n relativePath(\n currentDir,\n resolvePath(dirname(pkgPath), pkg.configSchema),\n ),\n );\n } else {\n const path = resolvePath(dirname(pkgPath), pkg.configSchema);\n const value = await fs.readJson(path);\n schemas.push({\n value,\n path: relativePath(currentDir, path),\n });\n }\n } else {\n schemas.push({\n value: pkg.configSchema,\n path: relativePath(currentDir, pkgPath),\n });\n }\n }\n\n await Promise.all(\n depNames.map(depName =>\n processItem({ name: depName, parentPath: pkgPath }),\n ),\n );\n }\n\n await Promise.all([\n ...packageNames.map(name => processItem({ name, parentPath: currentDir })),\n ...packagePaths.map(path => processItem({ name: path, packagePath: path })),\n ]);\n\n const tsSchemas = compileTsSchemas(tsSchemaPaths);\n\n return schemas.concat(tsSchemas);\n}\n\n// This handles the support of TypeScript .d.ts config schema declarations.\n// We collect all typescript schema definition and compile them all in one go.\n// This is much faster than compiling them separately.\nfunction compileTsSchemas(paths: string[]) {\n if (paths.length === 0) {\n return [];\n }\n\n const program = getProgramFromFiles(paths, {\n incremental: false,\n isolatedModules: true,\n lib: ['ES5'], // Skipping most libs speeds processing up a lot, we just need the primitive types anyway\n noEmit: true,\n noResolve: true,\n skipLibCheck: true, // Skipping lib checks speeds things up\n skipDefaultLibCheck: true,\n strict: true,\n typeRoots: [], // Do not include any additional types\n types: [],\n });\n\n const tsSchemas = paths.map(path => {\n let value;\n try {\n value = generateSchema(\n program,\n // All schemas should export a `Config` symbol\n 'Config',\n // This enables usage of @visibility is doc comments\n {\n required: true,\n validationKeywords: ['visibility'],\n },\n [path.split(sep).join('/')], // Unix paths are expected for all OSes here\n ) as JsonObject | null;\n } catch (error) {\n assertError(error);\n if (error.message !== 'type Config not found') {\n throw error;\n }\n }\n\n if (!value) {\n throw new Error(`Invalid schema in ${path}, missing Config export`);\n }\n return { path, value };\n });\n\n return tsSchemas;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport {\n ConfigVisibility,\n DEFAULT_CONFIG_VISIBILITY,\n TransformFunc,\n ValidationError,\n} from './types';\n\n/**\n * This filters data by visibility by discovering the visibility of each\n * value, and then only keeping the ones that are specified in `includeVisibilities`.\n */\nexport function filterByVisibility(\n data: JsonObject,\n includeVisibilities: ConfigVisibility[],\n visibilityByDataPath: Map<string, ConfigVisibility>,\n transformFunc?: TransformFunc<number | string | boolean>,\n withFilteredKeys?: boolean,\n): { data: JsonObject; filteredKeys?: string[] } {\n const filteredKeys = new Array<string>();\n\n function transform(\n jsonVal: JsonValue,\n visibilityPath: string, // Matches the format we get from ajv\n filterPath: string, // Matches the format of the ConfigReader\n ): JsonValue | undefined {\n const visibility =\n visibilityByDataPath.get(visibilityPath) ?? DEFAULT_CONFIG_VISIBILITY;\n const isVisible = includeVisibilities.includes(visibility);\n\n if (typeof jsonVal !== 'object') {\n if (isVisible) {\n if (transformFunc) {\n return transformFunc(jsonVal, { visibility });\n }\n return jsonVal;\n }\n if (withFilteredKeys) {\n filteredKeys.push(filterPath);\n }\n return undefined;\n } else if (jsonVal === null) {\n return undefined;\n } else if (Array.isArray(jsonVal)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of jsonVal.entries()) {\n const out = transform(\n value,\n `${visibilityPath}/${index}`,\n `${filterPath}[${index}]`,\n );\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n if (arr.length > 0 || isVisible) {\n return arr;\n }\n return undefined;\n }\n\n const outObj: JsonObject = {};\n let hasOutput = false;\n\n for (const [key, value] of Object.entries(jsonVal)) {\n if (value === undefined) {\n continue;\n }\n const out = transform(\n value,\n `${visibilityPath}/${key}`,\n filterPath ? `${filterPath}.${key}` : key,\n );\n if (out !== undefined) {\n outObj[key] = out;\n hasOutput = true;\n }\n }\n\n if (hasOutput || isVisible) {\n return outObj;\n }\n return undefined;\n }\n\n return {\n filteredKeys: withFilteredKeys ? filteredKeys : undefined,\n data: (transform(data, '', '') as JsonObject) ?? {},\n };\n}\n\nexport function filterErrorsByVisibility(\n errors: ValidationError[] | undefined,\n includeVisibilities: ConfigVisibility[] | undefined,\n visibilityByDataPath: Map<string, ConfigVisibility>,\n visibilityBySchemaPath: Map<string, ConfigVisibility>,\n): ValidationError[] {\n if (!errors) {\n return [];\n }\n if (!includeVisibilities) {\n return errors;\n }\n\n const visibleSchemaPaths = Array.from(visibilityBySchemaPath)\n .filter(([, v]) => includeVisibilities.includes(v))\n .map(([k]) => k);\n\n // If we're filtering by visibility we only care about the errors that happened\n // in a visible path.\n return errors.filter(error => {\n // We always include structural errors as we don't know whether there are\n // any visible paths within the structures.\n if (\n error.keyword === 'type' &&\n ['object', 'array'].includes(error.params.type)\n ) {\n return true;\n }\n\n // For fields that were required we use the schema path to determine whether\n // it was visible in addition to the data path. This is because the data path\n // visibilities are only populated for values that we reached, which we won't\n // if the value is missing.\n // We don't use this method for all the errors as the data path is more robust\n // and doesn't require us to properly trim the schema path.\n if (error.keyword === 'required') {\n const trimmedPath = error.schemaPath.slice(1, -'/required'.length);\n const fullPath = `${trimmedPath}/properties/${error.params.missingProperty}`;\n if (\n visibleSchemaPaths.some(visiblePath => visiblePath.startsWith(fullPath))\n ) {\n return true;\n }\n }\n\n const vis =\n visibilityByDataPath.get(error.dataPath) ?? DEFAULT_CONFIG_VISIBILITY;\n return vis && includeVisibilities.includes(vis);\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { compileConfigSchemas } from './compile';\nimport { collectConfigSchemas } from './collect';\nimport { filterByVisibility, filterErrorsByVisibility } from './filtering';\nimport {\n ValidationError,\n ConfigSchema,\n ConfigSchemaPackageEntry,\n CONFIG_VISIBILITIES,\n} from './types';\n\n/**\n * Options that control the loading of configuration schema files in the backend.\n *\n * @public\n */\nexport type LoadConfigSchemaOptions =\n | {\n dependencies: string[];\n packagePaths?: string[];\n }\n | {\n serialized: JsonObject;\n };\n\nfunction errorsToError(errors: ValidationError[]): Error {\n const messages = errors.map(({ dataPath, message, params }) => {\n const paramStr = Object.entries(params)\n .map(([name, value]) => `${name}=${value}`)\n .join(' ');\n return `Config ${message || ''} { ${paramStr} } at ${dataPath}`;\n });\n const error = new Error(`Config validation failed, ${messages.join('; ')}`);\n (error as any).messages = messages;\n return error;\n}\n\n/**\n * Loads config schema for a Backstage instance.\n *\n * @public\n */\nexport async function loadConfigSchema(\n options: LoadConfigSchemaOptions,\n): Promise<ConfigSchema> {\n let schemas: ConfigSchemaPackageEntry[];\n\n if ('dependencies' in options) {\n schemas = await collectConfigSchemas(\n options.dependencies,\n options.packagePaths ?? [],\n );\n } else {\n const { serialized } = options;\n if (serialized?.backstageConfigSchemaVersion !== 1) {\n throw new Error(\n 'Serialized configuration schema is invalid or has an invalid version number',\n );\n }\n schemas = serialized.schemas as ConfigSchemaPackageEntry[];\n }\n\n const validate = compileConfigSchemas(schemas);\n\n return {\n process(\n configs: AppConfig[],\n { visibility, valueTransform, withFilteredKeys } = {},\n ): AppConfig[] {\n const result = validate(configs);\n\n const visibleErrors = filterErrorsByVisibility(\n result.errors,\n visibility,\n result.visibilityByDataPath,\n result.visibilityBySchemaPath,\n );\n if (visibleErrors.length > 0) {\n throw errorsToError(visibleErrors);\n }\n\n let processedConfigs = configs;\n\n if (visibility) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n visibility,\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n } else if (valueTransform) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n Array.from(CONFIG_VISIBILITIES),\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n }\n\n return processedConfigs;\n },\n serialize(): JsonObject {\n return {\n schemas,\n backstageConfigSchemaVersion: 1,\n };\n },\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport yaml from 'yaml';\nimport chokidar from 'chokidar';\nimport { resolve as resolvePath, dirname, isAbsolute, basename } from 'path';\nimport { AppConfig } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport {\n applyConfigTransforms,\n readEnvConfig,\n createIncludeTransform,\n createSubstitutionTransform,\n} from './lib';\n\n/**\n * Options that control the loading of configuration files in the backend.\n *\n * @public\n */\nexport type LoadConfigOptions = {\n // The root directory of the config loading context. Used to find default configs.\n configRoot: string;\n\n // Absolute paths to load config files from. Configs from earlier paths have lower priority.\n configPaths: string[];\n\n /** @deprecated This option has been removed */\n env?: string;\n\n /**\n * Custom environment variable loading function\n *\n * @experimental This API is not stable and may change at any point\n */\n experimentalEnvFunc?: (name: string) => Promise<string | undefined>;\n\n /**\n * An optional configuration that enables watching of config files.\n */\n watch?: {\n /**\n * A listener that is called when a config file is changed.\n */\n onChange: (configs: AppConfig[]) => void;\n\n /**\n * An optional signal that stops the watcher once the promise resolves.\n */\n stopSignal?: Promise<void>;\n };\n};\n\n/**\n * Load configuration data.\n *\n * @public\n */\nexport async function loadConfig(\n options: LoadConfigOptions,\n): Promise<AppConfig[]> {\n const { configRoot, experimentalEnvFunc: envFunc, watch } = options;\n const configPaths = options.configPaths.slice();\n\n // If no paths are provided, we default to reading\n // `app-config.yaml` and, if it exists, `app-config.local.yaml`\n if (configPaths.length === 0) {\n configPaths.push(resolvePath(configRoot, 'app-config.yaml'));\n\n const localConfig = resolvePath(configRoot, 'app-config.local.yaml');\n if (await fs.pathExists(localConfig)) {\n configPaths.push(localConfig);\n }\n }\n\n const env = envFunc ?? (async (name: string) => process.env[name]);\n\n const loadConfigFiles = async () => {\n const configs = [];\n\n for (const configPath of configPaths) {\n if (!isAbsolute(configPath)) {\n throw new Error(`Config load path is not absolute: '${configPath}'`);\n }\n\n const dir = dirname(configPath);\n const readFile = (path: string) =>\n fs.readFile(resolvePath(dir, path), 'utf8');\n\n const input = yaml.parse(await readFile(configPath));\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(dir, input, [\n createIncludeTransform(env, readFile, substitutionTransform),\n substitutionTransform,\n ]);\n\n configs.push({ data, context: basename(configPath) });\n }\n\n return configs;\n };\n\n let fileConfigs;\n try {\n fileConfigs = await loadConfigFiles();\n } catch (error) {\n throw new ForwardedError('Failed to read static configuration file', error);\n }\n\n const envConfigs = await readEnvConfig(process.env);\n\n // Set up config file watching if requested by the caller\n if (watch) {\n let currentSerializedConfig = JSON.stringify(fileConfigs);\n\n const watcher = chokidar.watch(configPaths, {\n usePolling: process.env.NODE_ENV === 'test',\n });\n watcher.on('change', async () => {\n try {\n const newConfigs = await loadConfigFiles();\n const newSerializedConfig = JSON.stringify(newConfigs);\n\n if (currentSerializedConfig === newSerializedConfig) {\n return;\n }\n currentSerializedConfig = newSerializedConfig;\n\n watch.onChange([...newConfigs, ...envConfigs]);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n });\n\n if (watch.stopSignal) {\n watch.stopSignal.then(() => {\n watcher.close();\n });\n }\n }\n\n return [...fileConfigs, ...envConfigs];\n}\n"],"names":["resolvePath","relativePath"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/lib/env.ts","../src/lib/transform/utils.ts","../src/lib/transform/apply.ts","../src/lib/transform/include.ts","../src/lib/transform/substitution.ts","../src/lib/schema/types.ts","../src/lib/schema/compile.ts","../src/lib/schema/collect.ts","../src/lib/schema/filtering.ts","../src/lib/schema/load.ts","../src/lib/urls.ts","../src/loader.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\nconst ENV_PREFIX = 'APP_CONFIG_';\n\n// Update the same pattern in config package if this is changed\nconst CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;\n\n/**\n * Read runtime configuration from the environment.\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n */\nexport function readEnvConfig(env: {\n [name: string]: string | undefined;\n}): AppConfig[] {\n let data: JsonObject | undefined = undefined;\n\n for (const [name, value] of Object.entries(env)) {\n if (!value) {\n continue;\n }\n if (name.startsWith(ENV_PREFIX)) {\n const key = name.replace(ENV_PREFIX, '');\n const keyParts = key.split('_');\n\n let obj = (data = data ?? {});\n for (const [index, part] of keyParts.entries()) {\n if (!CONFIG_KEY_PART_PATTERN.test(part)) {\n throw new TypeError(`Invalid env config key '${key}'`);\n }\n if (index < keyParts.length - 1) {\n obj = (obj[part] = obj[part] ?? {}) as JsonObject;\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n const subKey = keyParts.slice(0, index + 1).join('_');\n throw new TypeError(\n `Could not nest config for key '${key}' under existing value '${subKey}'`,\n );\n }\n } else {\n if (part in obj) {\n throw new TypeError(\n `Refusing to override existing config at key '${key}'`,\n );\n }\n try {\n const [, parsedValue] = safeJsonParse(value);\n if (parsedValue === null) {\n throw new Error('value may not be null');\n }\n obj[part] = parsedValue;\n } catch (error) {\n throw new TypeError(\n `Failed to parse JSON-serialized config value for key '${key}', ${error}`,\n );\n }\n }\n }\n }\n }\n\n return data ? [{ data, context: 'env' }] : [];\n}\n\nfunction safeJsonParse(str: string): [Error | null, any] {\n try {\n return [null, JSON.parse(str)];\n } catch (err) {\n assertError(err);\n return [err, str];\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue, JsonObject } from '@backstage/types';\n\nexport function isObject(obj: JsonValue | undefined): obj is JsonObject {\n if (typeof obj !== 'object') {\n return false;\n } else if (Array.isArray(obj)) {\n return false;\n }\n return obj !== null;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\nimport { TransformFunc } from './types';\nimport { isObject } from './utils';\n\n/**\n * Applies a set of transforms to raw configuration data.\n */\nexport async function applyConfigTransforms(\n initialDir: string,\n input: JsonValue,\n transforms: TransformFunc[],\n): Promise<JsonObject> {\n async function transform(\n inputObj: JsonValue,\n path: string,\n baseDir: string,\n ): Promise<JsonValue | undefined> {\n let obj = inputObj;\n let dir = baseDir;\n\n for (const tf of transforms) {\n try {\n const result = await tf(inputObj, baseDir);\n if (result.applied) {\n if (result.value === undefined) {\n return undefined;\n }\n obj = result.value;\n dir = result.newBaseDir ?? dir;\n break;\n }\n } catch (error) {\n assertError(error);\n throw new Error(`error at ${path}, ${error.message}`);\n }\n }\n\n if (typeof obj !== 'object') {\n return obj;\n } else if (obj === null) {\n return undefined;\n } else if (Array.isArray(obj)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of obj.entries()) {\n const out = await transform(value, `${path}[${index}]`, dir);\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n return arr;\n }\n\n const out: JsonObject = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // undefined covers optional fields\n if (value !== undefined) {\n const result = await transform(value, `${path}.${key}`, dir);\n if (result !== undefined) {\n out[key] = result;\n }\n }\n }\n\n return out;\n }\n\n const finalData = await transform(input, '', initialDir);\n if (!isObject(finalData)) {\n throw new TypeError('expected object at config root');\n }\n return finalData;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport yaml from 'yaml';\nimport { extname, dirname, resolve as resolvePath } from 'path';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { isObject } from './utils';\nimport { TransformFunc, EnvFunc, ReadFileFunc } from './types';\n\n// Parsers for each type of included file\nconst includeFileParser: {\n [ext in string]: (content: string) => Promise<JsonObject>;\n} = {\n '.json': async content => JSON.parse(content),\n '.yaml': async content => yaml.parse(content),\n '.yml': async content => yaml.parse(content),\n};\n\n/**\n * Transforms a include description into the actual included value.\n */\nexport function createIncludeTransform(\n env: EnvFunc,\n readFile: ReadFileFunc,\n substitute: TransformFunc,\n): TransformFunc {\n return async (input: JsonValue, baseDir: string) => {\n if (!isObject(input)) {\n return { applied: false };\n }\n // Check if there's any key that starts with a '$', in that case we treat\n // this entire object as an include description.\n const [includeKey] = Object.keys(input).filter(key => key.startsWith('$'));\n if (includeKey) {\n if (Object.keys(input).length !== 1) {\n throw new Error(\n `include key ${includeKey} should not have adjacent keys`,\n );\n }\n } else {\n return { applied: false };\n }\n\n const rawIncludedValue = input[includeKey];\n if (typeof rawIncludedValue !== 'string') {\n throw new Error(`${includeKey} include value is not a string`);\n }\n\n const substituteResults = await substitute(rawIncludedValue, baseDir);\n const includeValue = substituteResults.applied\n ? substituteResults.value\n : rawIncludedValue;\n\n // The second string check is needed for Typescript to know this is a string.\n if (includeValue === undefined || typeof includeValue !== 'string') {\n throw new Error(`${includeKey} substitution value was undefined`);\n }\n\n switch (includeKey) {\n case '$file':\n try {\n const value = await readFile(resolvePath(baseDir, includeValue));\n return { applied: true, value };\n } catch (error) {\n throw new Error(`failed to read file ${includeValue}, ${error}`);\n }\n case '$env':\n try {\n return { applied: true, value: await env(includeValue) };\n } catch (error) {\n throw new Error(`failed to read env ${includeValue}, ${error}`);\n }\n\n case '$include': {\n const [filePath, dataPath] = includeValue.split(/#(.*)/);\n\n const ext = extname(filePath);\n const parser = includeFileParser[ext];\n if (!parser) {\n throw new Error(\n `no configuration parser available for included file ${filePath}`,\n );\n }\n\n const path = resolvePath(baseDir, filePath);\n const content = await readFile(path);\n const newBaseDir = dirname(path);\n\n const parts = dataPath ? dataPath.split('.') : [];\n\n let value: JsonValue | undefined;\n try {\n value = await parser(content);\n } catch (error) {\n throw new Error(\n `failed to parse included file ${filePath}, ${error}`,\n );\n }\n\n // This bit handles selecting a subtree in the included file, if a path was provided after a #\n for (const [index, part] of parts.entries()) {\n if (!isObject(value)) {\n const errPath = parts.slice(0, index).join('.');\n throw new Error(\n `value at '${errPath}' in included file ${filePath} is not an object`,\n );\n }\n value = value[part];\n }\n\n return {\n applied: true,\n value,\n newBaseDir: newBaseDir !== baseDir ? newBaseDir : undefined,\n };\n }\n\n default:\n throw new Error(`unknown include ${includeKey}`);\n }\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue } from '@backstage/types';\nimport { TransformFunc, EnvFunc } from './types';\n\n/**\n * A environment variable substitution transform that transforms e.g. 'token ${MY_TOKEN}'\n * to 'token abc' if MY_TOKEN is 'abc'. If any of the substituted variables are undefined,\n * the entire expression ends up undefined.\n */\nexport function createSubstitutionTransform(env: EnvFunc): TransformFunc {\n return async (input: JsonValue) => {\n if (typeof input !== 'string') {\n return { applied: false };\n }\n\n const parts: (string | undefined)[] = input.split(/(\\$?\\$\\{[^{}]*\\})/);\n for (let i = 1; i < parts.length; i += 2) {\n const part = parts[i]!;\n if (part.startsWith('$$')) {\n parts[i] = part.slice(1);\n } else {\n parts[i] = await env(part.slice(2, -1).trim());\n }\n }\n\n if (parts.some(part => part === undefined)) {\n return { applied: true, value: undefined };\n }\n return { applied: true, value: parts.join('') };\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\n\n/**\n * An sub-set of configuration schema.\n */\nexport type ConfigSchemaPackageEntry = {\n /**\n * The configuration schema itself.\n */\n value: JsonObject;\n /**\n * The relative path that the configuration schema was discovered at.\n */\n path: string;\n};\n\n/**\n * A list of all possible configuration value visibilities.\n */\nexport const CONFIG_VISIBILITIES = ['frontend', 'backend', 'secret'] as const;\n\n/**\n * A type representing the possible configuration value visibilities\n *\n * @public\n */\nexport type ConfigVisibility = 'frontend' | 'backend' | 'secret';\n\n/**\n * The default configuration visibility if no other values is given.\n */\nexport const DEFAULT_CONFIG_VISIBILITY: ConfigVisibility = 'backend';\n\n/**\n * An explanation of a configuration validation error.\n */\nexport type ValidationError = {\n keyword: string;\n dataPath: string;\n schemaPath: string;\n params: Record<string, any>;\n propertyName?: string;\n message?: string;\n};\n\n/**\n * The result of validating configuration data using a schema.\n */\ntype ValidationResult = {\n /**\n * Errors that where emitted during validation, if any.\n */\n errors?: ValidationError[];\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/<key>/<sub-key>/<array-index>/<leaf-key>`\n */\n visibilityByDataPath: Map<string, ConfigVisibility>;\n\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/properties/<key>/items/additionalProperties/<leaf-key>`\n */\n visibilityBySchemaPath: Map<string, ConfigVisibility>;\n};\n\n/**\n * A function used validate configuration data.\n */\nexport type ValidationFunc = (configs: AppConfig[]) => ValidationResult;\n\n/**\n * A function used to transform primitive configuration values.\n *\n * @public\n */\nexport type TransformFunc<T extends number | string | boolean> = (\n value: T,\n context: { visibility: ConfigVisibility },\n) => T | undefined;\n\n/**\n * Options used to process configuration data with a schema.\n *\n * @public\n */\nexport type ConfigSchemaProcessingOptions = {\n /**\n * The visibilities that should be included in the output data.\n * If omitted, the data will not be filtered by visibility.\n */\n visibility?: ConfigVisibility[];\n\n /**\n * A transform function that can be used to transform primitive configuration values\n * during validation. The value returned from the transform function will be used\n * instead of the original value. If the transform returns `undefined`, the value\n * will be omitted.\n */\n valueTransform?: TransformFunc<any>;\n\n /**\n * Whether or not to include the `filteredKeys` property in the output `AppConfig`s.\n *\n * Default: `false`.\n */\n withFilteredKeys?: boolean;\n};\n\n/**\n * A loaded configuration schema that is ready to process configuration data.\n *\n * @public\n */\nexport type ConfigSchema = {\n process(\n appConfigs: AppConfig[],\n options?: ConfigSchemaProcessingOptions,\n ): AppConfig[];\n\n serialize(): JsonObject;\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Ajv from 'ajv';\nimport { JSONSchema7 as JSONSchema } from 'json-schema';\nimport mergeAllOf, { Resolvers } from 'json-schema-merge-allof';\nimport traverse from 'json-schema-traverse';\nimport { ConfigReader } from '@backstage/config';\nimport {\n ConfigSchemaPackageEntry,\n ValidationFunc,\n CONFIG_VISIBILITIES,\n ConfigVisibility,\n} from './types';\n\n/**\n * This takes a collection of Backstage configuration schemas from various\n * sources and compiles them down into a single schema validation function.\n *\n * It also handles the implementation of the custom \"visibility\" keyword used\n * to specify the scope of different config paths.\n */\nexport function compileConfigSchemas(\n schemas: ConfigSchemaPackageEntry[],\n): ValidationFunc {\n // The ajv instance below is stateful and doesn't really allow for additional\n // output during validation. We work around this by having this extra piece\n // of state that we reset before each validation.\n const visibilityByDataPath = new Map<string, ConfigVisibility>();\n\n const ajv = new Ajv({\n allErrors: true,\n allowUnionTypes: true,\n schemas: {\n 'https://backstage.io/schema/config-v1': true,\n },\n }).addKeyword({\n keyword: 'visibility',\n metaSchema: {\n type: 'string',\n enum: CONFIG_VISIBILITIES,\n },\n compile(visibility: ConfigVisibility) {\n return (_data, context) => {\n if (context?.dataPath === undefined) {\n return false;\n }\n if (visibility && visibility !== 'backend') {\n const normalizedPath = context.dataPath.replace(\n /\\['?(.*?)'?\\]/g,\n (_, segment) => `/${segment}`,\n );\n visibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n });\n\n for (const schema of schemas) {\n try {\n ajv.compile(schema.value);\n } catch (error) {\n throw new Error(`Schema at ${schema.path} is invalid, ${error}`);\n }\n }\n\n const merged = mergeConfigSchemas(schemas.map(_ => _.value));\n const validate = ajv.compile(merged);\n\n const visibilityBySchemaPath = new Map<string, ConfigVisibility>();\n traverse(merged, (schema, path) => {\n if (schema.visibility && schema.visibility !== 'backend') {\n visibilityBySchemaPath.set(path, schema.visibility);\n }\n });\n\n return configs => {\n const config = ConfigReader.fromConfigs(configs).get();\n\n visibilityByDataPath.clear();\n\n const valid = validate(config);\n if (!valid) {\n return {\n errors: validate.errors ?? [],\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n }\n\n return {\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n };\n}\n\n/**\n * Given a list of configuration schemas from packages, merge them\n * into a single json schema.\n *\n * @public\n */\nexport function mergeConfigSchemas(schemas: JSONSchema[]): JSONSchema {\n const merged = mergeAllOf(\n { allOf: schemas },\n {\n // JSONSchema is typically subtractive, as in it always reduces the set of allowed\n // inputs through constraints. This changes the object property merging to be additive\n // rather than subtractive.\n ignoreAdditionalProperties: true,\n resolvers: {\n // This ensures that the visibilities across different schemas are sound, and\n // selects the most specific visibility for each path.\n visibility(values: string[], path: string[]) {\n const hasFrontend = values.some(_ => _ === 'frontend');\n const hasSecret = values.some(_ => _ === 'secret');\n if (hasFrontend && hasSecret) {\n throw new Error(\n `Config schema visibility is both 'frontend' and 'secret' for ${path.join(\n '/',\n )}`,\n );\n } else if (hasFrontend) {\n return 'frontend';\n } else if (hasSecret) {\n return 'secret';\n }\n\n return 'backend';\n },\n } as Partial<Resolvers<JSONSchema>>,\n },\n );\n return merged;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport {\n resolve as resolvePath,\n relative as relativePath,\n dirname,\n sep,\n} from 'path';\nimport { ConfigSchemaPackageEntry } from './types';\nimport { getProgramFromFiles, generateSchema } from 'typescript-json-schema';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\ntype Item = {\n name?: string;\n parentPath?: string;\n packagePath?: string;\n};\n\nconst req =\n typeof __non_webpack_require__ === 'undefined'\n ? require\n : __non_webpack_require__;\n\n/**\n * This collects all known config schemas across all dependencies of the app.\n */\nexport async function collectConfigSchemas(\n packageNames: string[],\n packagePaths: string[],\n): Promise<ConfigSchemaPackageEntry[]> {\n const schemas = new Array<ConfigSchemaPackageEntry>();\n const tsSchemaPaths = new Array<string>();\n const visitedPackageVersions = new Map<string, Set<string>>(); // pkgName: [versions...]\n\n const currentDir = await fs.realpath(process.cwd());\n\n async function processItem(item: Item) {\n let pkgPath = item.packagePath;\n\n if (pkgPath) {\n const pkgExists = await fs.pathExists(pkgPath);\n if (!pkgExists) {\n return;\n }\n } else if (item.name) {\n const { name, parentPath } = item;\n\n try {\n pkgPath = req.resolve(\n `${name}/package.json`,\n parentPath && {\n paths: [parentPath],\n },\n );\n } catch {\n // We can somewhat safely ignore packages that don't export package.json,\n // as they are likely not part of the Backstage ecosystem anyway.\n }\n }\n if (!pkgPath) {\n return;\n }\n\n const pkg = await fs.readJson(pkgPath);\n\n // Ensures that we only process the same version of each package once.\n let versions = visitedPackageVersions.get(pkg.name);\n if (versions?.has(pkg.version)) {\n return;\n }\n if (!versions) {\n versions = new Set();\n visitedPackageVersions.set(pkg.name, versions);\n }\n versions.add(pkg.version);\n\n const depNames = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ...Object.keys(pkg.optionalDependencies ?? {}),\n ...Object.keys(pkg.peerDependencies ?? {}),\n ];\n\n // TODO(Rugvip): Trying this out to avoid having to traverse the full dependency graph,\n // since that's pretty slow. We probably need a better way to determine when\n // we've left the Backstage ecosystem, but this will do for now.\n const hasSchema = 'configSchema' in pkg;\n const hasBackstageDep = depNames.some(_ => _.startsWith('@backstage/'));\n if (!hasSchema && !hasBackstageDep) {\n return;\n }\n if (hasSchema) {\n if (typeof pkg.configSchema === 'string') {\n const isJson = pkg.configSchema.endsWith('.json');\n const isDts = pkg.configSchema.endsWith('.d.ts');\n if (!isJson && !isDts) {\n throw new Error(\n `Config schema files must be .json or .d.ts, got ${pkg.configSchema}`,\n );\n }\n if (isDts) {\n tsSchemaPaths.push(\n relativePath(\n currentDir,\n resolvePath(dirname(pkgPath), pkg.configSchema),\n ),\n );\n } else {\n const path = resolvePath(dirname(pkgPath), pkg.configSchema);\n const value = await fs.readJson(path);\n schemas.push({\n value,\n path: relativePath(currentDir, path),\n });\n }\n } else {\n schemas.push({\n value: pkg.configSchema,\n path: relativePath(currentDir, pkgPath),\n });\n }\n }\n\n await Promise.all(\n depNames.map(depName =>\n processItem({ name: depName, parentPath: pkgPath }),\n ),\n );\n }\n\n await Promise.all([\n ...packageNames.map(name => processItem({ name, parentPath: currentDir })),\n ...packagePaths.map(path => processItem({ name: path, packagePath: path })),\n ]);\n\n const tsSchemas = compileTsSchemas(tsSchemaPaths);\n\n return schemas.concat(tsSchemas);\n}\n\n// This handles the support of TypeScript .d.ts config schema declarations.\n// We collect all typescript schema definition and compile them all in one go.\n// This is much faster than compiling them separately.\nfunction compileTsSchemas(paths: string[]) {\n if (paths.length === 0) {\n return [];\n }\n\n const program = getProgramFromFiles(paths, {\n incremental: false,\n isolatedModules: true,\n lib: ['ES5'], // Skipping most libs speeds processing up a lot, we just need the primitive types anyway\n noEmit: true,\n noResolve: true,\n skipLibCheck: true, // Skipping lib checks speeds things up\n skipDefaultLibCheck: true,\n strict: true,\n typeRoots: [], // Do not include any additional types\n types: [],\n });\n\n const tsSchemas = paths.map(path => {\n let value;\n try {\n value = generateSchema(\n program,\n // All schemas should export a `Config` symbol\n 'Config',\n // This enables usage of @visibility is doc comments\n {\n required: true,\n validationKeywords: ['visibility'],\n },\n [path.split(sep).join('/')], // Unix paths are expected for all OSes here\n ) as JsonObject | null;\n } catch (error) {\n assertError(error);\n if (error.message !== 'type Config not found') {\n throw error;\n }\n }\n\n if (!value) {\n throw new Error(`Invalid schema in ${path}, missing Config export`);\n }\n return { path, value };\n });\n\n return tsSchemas;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport {\n ConfigVisibility,\n DEFAULT_CONFIG_VISIBILITY,\n TransformFunc,\n ValidationError,\n} from './types';\n\n/**\n * This filters data by visibility by discovering the visibility of each\n * value, and then only keeping the ones that are specified in `includeVisibilities`.\n */\nexport function filterByVisibility(\n data: JsonObject,\n includeVisibilities: ConfigVisibility[],\n visibilityByDataPath: Map<string, ConfigVisibility>,\n transformFunc?: TransformFunc<number | string | boolean>,\n withFilteredKeys?: boolean,\n): { data: JsonObject; filteredKeys?: string[] } {\n const filteredKeys = new Array<string>();\n\n function transform(\n jsonVal: JsonValue,\n visibilityPath: string, // Matches the format we get from ajv\n filterPath: string, // Matches the format of the ConfigReader\n ): JsonValue | undefined {\n const visibility =\n visibilityByDataPath.get(visibilityPath) ?? DEFAULT_CONFIG_VISIBILITY;\n const isVisible = includeVisibilities.includes(visibility);\n\n if (typeof jsonVal !== 'object') {\n if (isVisible) {\n if (transformFunc) {\n return transformFunc(jsonVal, { visibility });\n }\n return jsonVal;\n }\n if (withFilteredKeys) {\n filteredKeys.push(filterPath);\n }\n return undefined;\n } else if (jsonVal === null) {\n return undefined;\n } else if (Array.isArray(jsonVal)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of jsonVal.entries()) {\n const out = transform(\n value,\n `${visibilityPath}/${index}`,\n `${filterPath}[${index}]`,\n );\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n if (arr.length > 0 || isVisible) {\n return arr;\n }\n return undefined;\n }\n\n const outObj: JsonObject = {};\n let hasOutput = false;\n\n for (const [key, value] of Object.entries(jsonVal)) {\n if (value === undefined) {\n continue;\n }\n const out = transform(\n value,\n `${visibilityPath}/${key}`,\n filterPath ? `${filterPath}.${key}` : key,\n );\n if (out !== undefined) {\n outObj[key] = out;\n hasOutput = true;\n }\n }\n\n if (hasOutput || isVisible) {\n return outObj;\n }\n return undefined;\n }\n\n return {\n filteredKeys: withFilteredKeys ? filteredKeys : undefined,\n data: (transform(data, '', '') as JsonObject) ?? {},\n };\n}\n\nexport function filterErrorsByVisibility(\n errors: ValidationError[] | undefined,\n includeVisibilities: ConfigVisibility[] | undefined,\n visibilityByDataPath: Map<string, ConfigVisibility>,\n visibilityBySchemaPath: Map<string, ConfigVisibility>,\n): ValidationError[] {\n if (!errors) {\n return [];\n }\n if (!includeVisibilities) {\n return errors;\n }\n\n const visibleSchemaPaths = Array.from(visibilityBySchemaPath)\n .filter(([, v]) => includeVisibilities.includes(v))\n .map(([k]) => k);\n\n // If we're filtering by visibility we only care about the errors that happened\n // in a visible path.\n return errors.filter(error => {\n // We always include structural errors as we don't know whether there are\n // any visible paths within the structures.\n if (\n error.keyword === 'type' &&\n ['object', 'array'].includes(error.params.type)\n ) {\n return true;\n }\n\n // For fields that were required we use the schema path to determine whether\n // it was visible in addition to the data path. This is because the data path\n // visibilities are only populated for values that we reached, which we won't\n // if the value is missing.\n // We don't use this method for all the errors as the data path is more robust\n // and doesn't require us to properly trim the schema path.\n if (error.keyword === 'required') {\n const trimmedPath = error.schemaPath.slice(1, -'/required'.length);\n const fullPath = `${trimmedPath}/properties/${error.params.missingProperty}`;\n if (\n visibleSchemaPaths.some(visiblePath => visiblePath.startsWith(fullPath))\n ) {\n return true;\n }\n }\n\n const vis =\n visibilityByDataPath.get(error.dataPath) ?? DEFAULT_CONFIG_VISIBILITY;\n return vis && includeVisibilities.includes(vis);\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { compileConfigSchemas } from './compile';\nimport { collectConfigSchemas } from './collect';\nimport { filterByVisibility, filterErrorsByVisibility } from './filtering';\nimport {\n ValidationError,\n ConfigSchema,\n ConfigSchemaPackageEntry,\n CONFIG_VISIBILITIES,\n} from './types';\n\n/**\n * Options that control the loading of configuration schema files in the backend.\n *\n * @public\n */\nexport type LoadConfigSchemaOptions =\n | {\n dependencies: string[];\n packagePaths?: string[];\n }\n | {\n serialized: JsonObject;\n };\n\nfunction errorsToError(errors: ValidationError[]): Error {\n const messages = errors.map(({ dataPath, message, params }) => {\n const paramStr = Object.entries(params)\n .map(([name, value]) => `${name}=${value}`)\n .join(' ');\n return `Config ${message || ''} { ${paramStr} } at ${dataPath}`;\n });\n const error = new Error(`Config validation failed, ${messages.join('; ')}`);\n (error as any).messages = messages;\n return error;\n}\n\n/**\n * Loads config schema for a Backstage instance.\n *\n * @public\n */\nexport async function loadConfigSchema(\n options: LoadConfigSchemaOptions,\n): Promise<ConfigSchema> {\n let schemas: ConfigSchemaPackageEntry[];\n\n if ('dependencies' in options) {\n schemas = await collectConfigSchemas(\n options.dependencies,\n options.packagePaths ?? [],\n );\n } else {\n const { serialized } = options;\n if (serialized?.backstageConfigSchemaVersion !== 1) {\n throw new Error(\n 'Serialized configuration schema is invalid or has an invalid version number',\n );\n }\n schemas = serialized.schemas as ConfigSchemaPackageEntry[];\n }\n\n const validate = compileConfigSchemas(schemas);\n\n return {\n process(\n configs: AppConfig[],\n { visibility, valueTransform, withFilteredKeys } = {},\n ): AppConfig[] {\n const result = validate(configs);\n\n const visibleErrors = filterErrorsByVisibility(\n result.errors,\n visibility,\n result.visibilityByDataPath,\n result.visibilityBySchemaPath,\n );\n if (visibleErrors.length > 0) {\n throw errorsToError(visibleErrors);\n }\n\n let processedConfigs = configs;\n\n if (visibility) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n visibility,\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n } else if (valueTransform) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n Array.from(CONFIG_VISIBILITIES),\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n }\n\n return processedConfigs;\n },\n serialize(): JsonObject {\n return {\n schemas,\n backstageConfigSchemaVersion: 1,\n };\n },\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport function isValidUrl(url: string): boolean {\n try {\n // eslint-disable-next-line no-new\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport yaml from 'yaml';\nimport chokidar from 'chokidar';\nimport { basename, dirname, isAbsolute, resolve as resolvePath } from 'path';\nimport { AppConfig } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport {\n applyConfigTransforms,\n createIncludeTransform,\n createSubstitutionTransform,\n isValidUrl,\n readEnvConfig,\n} from './lib';\nimport fetch from 'node-fetch';\n\nexport type ConfigTarget = { path: string } | { url: string };\n\nexport type LoadConfigOptionsWatch = {\n /**\n * A listener that is called when a config file is changed.\n */\n onChange: (configs: AppConfig[]) => void;\n\n /**\n * An optional signal that stops the watcher once the promise resolves.\n */\n stopSignal?: Promise<void>;\n};\n\nexport type LoadConfigOptionsRemote = {\n /**\n * An optional remote config reloading period, in seconds\n */\n reloadIntervalSeconds: number;\n};\n\n/**\n * Options that control the loading of configuration files in the backend.\n *\n * @public\n */\nexport type LoadConfigOptions = {\n // The root directory of the config loading context. Used to find default configs.\n configRoot: string;\n\n /** Absolute paths to load config files from. Configs from earlier paths have lower priority.\n * @deprecated Use {@link configTargets} instead.\n */\n configPaths: string[];\n\n // Paths to load config files from. Configs from earlier paths have lower priority.\n configTargets: ConfigTarget[];\n\n /** @deprecated This option has been removed */\n env?: string;\n\n /**\n * Custom environment variable loading function\n *\n * @experimental This API is not stable and may change at any point\n */\n experimentalEnvFunc?: (name: string) => Promise<string | undefined>;\n\n /**\n * An optional remote config\n */\n remote?: LoadConfigOptionsRemote;\n\n /**\n * An optional configuration that enables watching of config files.\n */\n watch?: LoadConfigOptionsWatch;\n};\n\n/**\n * Load configuration data.\n *\n * @public\n */\nexport async function loadConfig(\n options: LoadConfigOptions,\n): Promise<AppConfig[]> {\n const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options;\n\n const configPaths: string[] = options.configTargets\n .slice()\n .filter((e): e is { path: string } => e.hasOwnProperty('path'))\n .map(configTarget => configTarget.path);\n\n // Append deprecated configPaths to the absolute config paths received via configTargets.\n options.configPaths.forEach(cp => {\n if (!configPaths.includes(cp)) {\n configPaths.push(cp);\n }\n });\n\n const configUrls: string[] = options.configTargets\n .slice()\n .filter((e): e is { url: string } => e.hasOwnProperty('url'))\n .map(configTarget => configTarget.url);\n\n if (remote === undefined && configUrls.length > 0) {\n throw new Error(`Remote config detected but this feature is turned off`);\n }\n\n // If no paths are provided, we default to reading\n // `app-config.yaml` and, if it exists, `app-config.local.yaml`\n if (configPaths.length === 0 && configUrls.length === 0) {\n configPaths.push(resolvePath(configRoot, 'app-config.yaml'));\n\n const localConfig = resolvePath(configRoot, 'app-config.local.yaml');\n if (await fs.pathExists(localConfig)) {\n configPaths.push(localConfig);\n }\n }\n\n const env = envFunc ?? (async (name: string) => process.env[name]);\n\n const loadConfigFiles = async () => {\n const configs = [];\n\n for (const configPath of configPaths) {\n if (!isAbsolute(configPath)) {\n throw new Error(`Config load path is not absolute: '${configPath}'`);\n }\n\n const dir = dirname(configPath);\n const readFile = (path: string) =>\n fs.readFile(resolvePath(dir, path), 'utf8');\n\n const input = yaml.parse(await readFile(configPath));\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(dir, input, [\n createIncludeTransform(env, readFile, substitutionTransform),\n substitutionTransform,\n ]);\n\n configs.push({ data, context: basename(configPath) });\n }\n\n return configs;\n };\n\n const loadRemoteConfigFiles = async () => {\n const configs: AppConfig[] = [];\n\n const readConfigFromUrl = async (url: string) => {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Could not read config file at ${url}`);\n }\n\n return await response.text();\n };\n\n for (let i = 0; i < configUrls.length; i++) {\n const configUrl = configUrls[i];\n if (!isValidUrl(configUrl)) {\n throw new Error(`Config load path is not valid: '${configUrl}'`);\n }\n\n const remoteConfigContent = await readConfigFromUrl(configUrl);\n if (!remoteConfigContent) {\n throw new Error(`Config is not valid`);\n }\n const configYaml = yaml.parse(remoteConfigContent);\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(configRoot, configYaml, [\n substitutionTransform,\n ]);\n\n configs.push({ data, context: configUrl });\n }\n\n return configs;\n };\n\n let fileConfigs: AppConfig[];\n try {\n fileConfigs = await loadConfigFiles();\n } catch (error) {\n throw new ForwardedError('Failed to read static configuration file', error);\n }\n\n let remoteConfigs: AppConfig[] = [];\n if (remote) {\n try {\n remoteConfigs = await loadRemoteConfigFiles();\n } catch (error) {\n throw new ForwardedError(\n `Failed to read remote configuration file`,\n error,\n );\n }\n }\n\n const envConfigs = await readEnvConfig(process.env);\n\n const watchConfigFile = (watchProp: LoadConfigOptionsWatch) => {\n const watcher = chokidar.watch(configPaths, {\n usePolling: process.env.NODE_ENV === 'test',\n });\n\n let currentSerializedConfig = JSON.stringify(fileConfigs);\n watcher.on('change', async () => {\n try {\n const newConfigs = await loadConfigFiles();\n const newSerializedConfig = JSON.stringify(newConfigs);\n\n if (currentSerializedConfig === newSerializedConfig) {\n return;\n }\n currentSerializedConfig = newSerializedConfig;\n\n watchProp.onChange([...remoteConfigs, ...newConfigs, ...envConfigs]);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n });\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n watcher.close();\n });\n }\n };\n\n const watchRemoteConfig = (\n watchProp: LoadConfigOptionsWatch,\n remoteProp: LoadConfigOptionsRemote,\n ) => {\n const hasConfigChanged = async (\n oldRemoteConfigs: AppConfig[],\n newRemoteConfigs: AppConfig[],\n ) => {\n return (\n JSON.stringify(oldRemoteConfigs) !== JSON.stringify(newRemoteConfigs)\n );\n };\n\n let handle: NodeJS.Timeout | undefined;\n try {\n handle = setInterval(async () => {\n console.info(`Checking for config update`);\n const newRemoteConfigs = await loadRemoteConfigFiles();\n if (await hasConfigChanged(remoteConfigs, newRemoteConfigs)) {\n remoteConfigs = newRemoteConfigs;\n console.info(`Remote config change, reloading config ...`);\n watchProp.onChange([...remoteConfigs, ...fileConfigs, ...envConfigs]);\n console.info(`Remote config reloaded`);\n }\n }, remoteProp.reloadIntervalSeconds * 1000);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n if (handle !== undefined) {\n console.info(`Stopping remote config watch`);\n clearInterval(handle);\n handle = undefined;\n }\n });\n }\n };\n\n // Set up config file watching if requested by the caller\n if (watch) {\n watchConfigFile(watch);\n }\n\n if (watch && remote) {\n watchRemoteConfig(watch, remote);\n }\n\n return remote\n ? [...remoteConfigs, ...fileConfigs, ...envConfigs]\n : [...fileConfigs, ...envConfigs];\n}\n"],"names":["resolvePath","relativePath"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/config-loader",
|
|
3
3
|
"description": "Config loading functionality used by Backstage backend, and CLI",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.2",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"json-schema": "^0.3.0",
|
|
42
42
|
"json-schema-merge-allof": "^0.8.1",
|
|
43
43
|
"json-schema-traverse": "^1.0.0",
|
|
44
|
+
"node-fetch": "2.6.5",
|
|
44
45
|
"typescript-json-schema": "^0.51.0",
|
|
45
46
|
"yaml": "^1.9.2",
|
|
46
47
|
"yup": "^0.32.9"
|
|
@@ -51,11 +52,12 @@
|
|
|
51
52
|
"@types/mock-fs": "^4.10.0",
|
|
52
53
|
"@types/node": "^14.14.32",
|
|
53
54
|
"@types/yup": "^0.29.13",
|
|
54
|
-
"mock-fs": "^5.1.0"
|
|
55
|
+
"mock-fs": "^5.1.0",
|
|
56
|
+
"msw": "^0.35.0"
|
|
55
57
|
},
|
|
56
58
|
"files": [
|
|
57
59
|
"dist"
|
|
58
60
|
],
|
|
59
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "5bdaccc40b4a814cf0b45d429f15a3afacc2f60b",
|
|
60
62
|
"module": "dist/index.esm.js"
|
|
61
63
|
}
|