@backstage/config-loader 1.9.1 → 1.9.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 +21 -0
- package/dist/index.cjs.js +22 -1650
- package/dist/index.cjs.js.map +1 -1
- package/dist/loader.cjs.js +60 -0
- package/dist/loader.cjs.js.map +1 -0
- package/dist/schema/collect.cjs.js +169 -0
- package/dist/schema/collect.cjs.js.map +1 -0
- package/dist/schema/compile.cjs.js +185 -0
- package/dist/schema/compile.cjs.js.map +1 -0
- package/dist/schema/filtering.cjs.js +112 -0
- package/dist/schema/filtering.cjs.js.map +1 -0
- package/dist/schema/load.cjs.js +101 -0
- package/dist/schema/load.cjs.js.map +1 -0
- package/dist/schema/types.cjs.js +8 -0
- package/dist/schema/types.cjs.js.map +1 -0
- package/dist/schema/utils.cjs.js +8 -0
- package/dist/schema/utils.cjs.js.map +1 -0
- package/dist/sources/ConfigSources.cjs.js +178 -0
- package/dist/sources/ConfigSources.cjs.js.map +1 -0
- package/dist/sources/EnvConfigSource.cjs.js +88 -0
- package/dist/sources/EnvConfigSource.cjs.js.map +1 -0
- package/dist/sources/FileConfigSource.cjs.js +153 -0
- package/dist/sources/FileConfigSource.cjs.js.map +1 -0
- package/dist/sources/MergedConfigSource.cjs.js +72 -0
- package/dist/sources/MergedConfigSource.cjs.js.map +1 -0
- package/dist/sources/MutableConfigSource.cjs.js +75 -0
- package/dist/sources/MutableConfigSource.cjs.js.map +1 -0
- package/dist/sources/ObservableConfigProxy.cjs.js +123 -0
- package/dist/sources/ObservableConfigProxy.cjs.js.map +1 -0
- package/dist/sources/RemoteConfigSource.cjs.js +107 -0
- package/dist/sources/RemoteConfigSource.cjs.js.map +1 -0
- package/dist/sources/StaticConfigSource.cjs.js +95 -0
- package/dist/sources/StaticConfigSource.cjs.js.map +1 -0
- package/dist/sources/transform/apply.cjs.js +79 -0
- package/dist/sources/transform/apply.cjs.js.map +1 -0
- package/dist/sources/transform/include.cjs.js +107 -0
- package/dist/sources/transform/include.cjs.js.map +1 -0
- package/dist/sources/transform/substitution.cjs.js +34 -0
- package/dist/sources/transform/substitution.cjs.js.map +1 -0
- package/dist/sources/transform/utils.cjs.js +13 -0
- package/dist/sources/transform/utils.cjs.js.map +1 -0
- package/dist/sources/utils.cjs.js +38 -0
- package/dist/sources/utils.cjs.js.map +1 -0
- package/package.json +14 -7
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var path = require('path');
|
|
4
|
+
var fs = require('fs-extra');
|
|
5
|
+
var config = require('@backstage/config');
|
|
6
|
+
var parseArgs = require('minimist');
|
|
7
|
+
var EnvConfigSource = require('./EnvConfigSource.cjs.js');
|
|
8
|
+
var FileConfigSource = require('./FileConfigSource.cjs.js');
|
|
9
|
+
var MergedConfigSource = require('./MergedConfigSource.cjs.js');
|
|
10
|
+
var RemoteConfigSource = require('./RemoteConfigSource.cjs.js');
|
|
11
|
+
var ObservableConfigProxy = require('./ObservableConfigProxy.cjs.js');
|
|
12
|
+
var cliCommon = require('@backstage/cli-common');
|
|
13
|
+
|
|
14
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
15
|
+
|
|
16
|
+
var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
17
|
+
var parseArgs__default = /*#__PURE__*/_interopDefaultCompat(parseArgs);
|
|
18
|
+
|
|
19
|
+
class ConfigSources {
|
|
20
|
+
/**
|
|
21
|
+
* Parses command line arguments and returns the config targets.
|
|
22
|
+
*
|
|
23
|
+
* @param argv - The command line arguments to parse. Defaults to `process.argv`
|
|
24
|
+
* @returns A list of config targets
|
|
25
|
+
*/
|
|
26
|
+
static parseArgs(argv = process.argv) {
|
|
27
|
+
const args = [parseArgs__default.default(argv).config].flat().filter(Boolean);
|
|
28
|
+
return args.map((target) => {
|
|
29
|
+
try {
|
|
30
|
+
const url = new URL(target);
|
|
31
|
+
if (!url.host) {
|
|
32
|
+
return { type: "path", target };
|
|
33
|
+
}
|
|
34
|
+
return { type: "url", target };
|
|
35
|
+
} catch {
|
|
36
|
+
return { type: "path", target };
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Creates the default config sources for the provided targets.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
*
|
|
45
|
+
* This will create {@link FileConfigSource}s and {@link RemoteConfigSource}s
|
|
46
|
+
* for the provided targets, and merge them together to a single source.
|
|
47
|
+
* If no targets are provided it will fall back to `app-config.yaml` and
|
|
48
|
+
* `app-config.local.yaml`.
|
|
49
|
+
*
|
|
50
|
+
* URL targets are only supported if the `remote` option is provided.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Options
|
|
53
|
+
* @returns A config source for the provided targets
|
|
54
|
+
*/
|
|
55
|
+
static defaultForTargets(options) {
|
|
56
|
+
const rootDir = options.rootDir ?? cliCommon.findPaths(process.cwd()).targetRoot;
|
|
57
|
+
const argSources = options.targets.map((arg) => {
|
|
58
|
+
if (arg.type === "url") {
|
|
59
|
+
if (!options.remote) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Config argument "${arg.target}" looks like a URL but remote configuration is not enabled. Enable it by passing the \`remote\` option`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return RemoteConfigSource.RemoteConfigSource.create({
|
|
65
|
+
url: arg.target,
|
|
66
|
+
substitutionFunc: options.substitutionFunc,
|
|
67
|
+
reloadInterval: options.remote.reloadInterval
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return FileConfigSource.FileConfigSource.create({
|
|
71
|
+
watch: options.watch,
|
|
72
|
+
path: path.resolve(arg.target),
|
|
73
|
+
substitutionFunc: options.substitutionFunc
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
if (argSources.length === 0) {
|
|
77
|
+
const defaultPath = path.resolve(rootDir, "app-config.yaml");
|
|
78
|
+
const localPath = path.resolve(rootDir, "app-config.local.yaml");
|
|
79
|
+
const alwaysIncludeDefaultConfigSource = !options.allowMissingDefaultConfig;
|
|
80
|
+
if (alwaysIncludeDefaultConfigSource || fs__default.default.pathExistsSync(defaultPath)) {
|
|
81
|
+
argSources.push(
|
|
82
|
+
FileConfigSource.FileConfigSource.create({
|
|
83
|
+
watch: options.watch,
|
|
84
|
+
path: defaultPath,
|
|
85
|
+
substitutionFunc: options.substitutionFunc
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (fs__default.default.pathExistsSync(localPath)) {
|
|
90
|
+
argSources.push(
|
|
91
|
+
FileConfigSource.FileConfigSource.create({
|
|
92
|
+
watch: options.watch,
|
|
93
|
+
path: localPath,
|
|
94
|
+
substitutionFunc: options.substitutionFunc
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return this.merge(argSources);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Creates the default config source for Backstage.
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
*
|
|
106
|
+
* This will read from `app-config.yaml` and `app-config.local.yaml` by
|
|
107
|
+
* default, as well as environment variables prefixed with `APP_CONFIG_`.
|
|
108
|
+
* If `--config <path|url>` command line arguments are passed, these will
|
|
109
|
+
* override the default configuration file paths. URLs are only supported
|
|
110
|
+
* if the `remote` option is provided.
|
|
111
|
+
*
|
|
112
|
+
* @param options - Options
|
|
113
|
+
* @returns The default Backstage config source
|
|
114
|
+
*/
|
|
115
|
+
static default(options) {
|
|
116
|
+
const argSource = this.defaultForTargets({
|
|
117
|
+
...options,
|
|
118
|
+
targets: this.parseArgs(options.argv)
|
|
119
|
+
});
|
|
120
|
+
const envSource = EnvConfigSource.EnvConfigSource.create({
|
|
121
|
+
env: options.env
|
|
122
|
+
});
|
|
123
|
+
return this.merge([argSource, envSource]);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Merges multiple config sources into a single source that reads from all
|
|
127
|
+
* sources and concatenates the result.
|
|
128
|
+
*
|
|
129
|
+
* @param sources - The config sources to merge
|
|
130
|
+
* @returns A single config source that concatenates the data from the given sources
|
|
131
|
+
*/
|
|
132
|
+
static merge(sources) {
|
|
133
|
+
return MergedConfigSource.MergedConfigSource.from(sources);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Creates an observable {@link @backstage/config#Config} implementation from a {@link ConfigSource}.
|
|
137
|
+
*
|
|
138
|
+
* @remarks
|
|
139
|
+
*
|
|
140
|
+
* If you only want to read the config once you can close the returned config immediately.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
*
|
|
144
|
+
* ```ts
|
|
145
|
+
* const sources = ConfigSources.default(...)
|
|
146
|
+
* const config = await ConfigSources.toConfig(source)
|
|
147
|
+
* config.close()
|
|
148
|
+
* const example = config.getString(...)
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* @param source - The config source to read from
|
|
152
|
+
* @returns A promise that resolves to a closable config
|
|
153
|
+
*/
|
|
154
|
+
static toConfig(source) {
|
|
155
|
+
return new Promise(async (resolve, reject) => {
|
|
156
|
+
let config$1 = void 0;
|
|
157
|
+
try {
|
|
158
|
+
const abortController = new AbortController();
|
|
159
|
+
for await (const { configs } of source.readConfigData({
|
|
160
|
+
signal: abortController.signal
|
|
161
|
+
})) {
|
|
162
|
+
if (config$1) {
|
|
163
|
+
config$1.setConfig(config.ConfigReader.fromConfigs(configs));
|
|
164
|
+
} else {
|
|
165
|
+
config$1 = ObservableConfigProxy.ObservableConfigProxy.create(abortController);
|
|
166
|
+
config$1.setConfig(config.ConfigReader.fromConfigs(configs));
|
|
167
|
+
resolve(config$1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
reject(error);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
exports.ConfigSources = ConfigSources;
|
|
178
|
+
//# sourceMappingURL=ConfigSources.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigSources.cjs.js","sources":["../../src/sources/ConfigSources.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { resolve as resolvePath } from 'path';\nimport fs from 'fs-extra';\nimport { Config, ConfigReader } from '@backstage/config';\nimport parseArgs from 'minimist';\nimport { EnvConfigSource } from './EnvConfigSource';\nimport { FileConfigSource } from './FileConfigSource';\nimport { MergedConfigSource } from './MergedConfigSource';\nimport {\n RemoteConfigSource,\n RemoteConfigSourceOptions,\n} from './RemoteConfigSource';\nimport { ConfigSource, SubstitutionFunc } from './types';\nimport { ObservableConfigProxy } from './ObservableConfigProxy';\nimport { findPaths } from '@backstage/cli-common';\n\n/**\n * A target to read configuration from.\n *\n * @public\n */\nexport type ConfigSourceTarget =\n | {\n type: 'path';\n target: string;\n }\n | {\n type: 'url';\n target: string;\n };\n\n/**\n * A config implementation that can be closed.\n *\n * @remarks\n *\n * Closing the configuration instance will stop the reading from the underlying source.\n *\n * @public\n */\nexport interface ClosableConfig extends Config {\n /**\n * Closes the configuration instance.\n *\n * @remarks\n *\n * The configuration instance will still be usable after closing, but it will\n * no longer be updated with new values from the underlying source.\n */\n close(): void;\n}\n\n/**\n * Common options for the default Backstage configuration sources.\n *\n * @public\n */\nexport interface BaseConfigSourcesOptions {\n watch?: boolean;\n rootDir?: string;\n remote?: Pick<RemoteConfigSourceOptions, 'reloadInterval'>;\n /**\n * Allow the default app-config.yaml to be missing, in which case the source\n * will not be created.\n */\n allowMissingDefaultConfig?: boolean;\n\n /**\n * A custom substitution function that overrides the default one.\n *\n * @remarks\n * The substitution function handles syntax like `${MY_ENV_VAR}` in configuration values.\n * The default substitution will read the value from the environment and trim whitespace.\n */\n substitutionFunc?: SubstitutionFunc;\n}\n\n/**\n * Options for {@link ConfigSources.defaultForTargets}.\n *\n * @public\n */\nexport interface ConfigSourcesDefaultForTargetsOptions\n extends BaseConfigSourcesOptions {\n targets: ConfigSourceTarget[];\n}\n\n/**\n * Options for {@link ConfigSources.default}.\n *\n * @public\n */\nexport interface ConfigSourcesDefaultOptions extends BaseConfigSourcesOptions {\n argv?: string[];\n env?: Record<string, string | undefined>;\n}\n\n/**\n * A collection of utilities for working with and creating {@link ConfigSource}s.\n *\n * @public\n */\nexport class ConfigSources {\n /**\n * Parses command line arguments and returns the config targets.\n *\n * @param argv - The command line arguments to parse. Defaults to `process.argv`\n * @returns A list of config targets\n */\n static parseArgs(argv: string[] = process.argv): ConfigSourceTarget[] {\n const args: string[] = [parseArgs(argv).config].flat().filter(Boolean);\n return args.map(target => {\n try {\n const url = new URL(target);\n\n // Some file paths are valid relative URLs, so check if the host is empty too\n if (!url.host) {\n return { type: 'path', target };\n }\n return { type: 'url', target };\n } catch {\n return { type: 'path', target };\n }\n });\n }\n\n /**\n * Creates the default config sources for the provided targets.\n *\n * @remarks\n *\n * This will create {@link FileConfigSource}s and {@link RemoteConfigSource}s\n * for the provided targets, and merge them together to a single source.\n * If no targets are provided it will fall back to `app-config.yaml` and\n * `app-config.local.yaml`.\n *\n * URL targets are only supported if the `remote` option is provided.\n *\n * @param options - Options\n * @returns A config source for the provided targets\n */\n static defaultForTargets(\n options: ConfigSourcesDefaultForTargetsOptions,\n ): ConfigSource {\n const rootDir = options.rootDir ?? findPaths(process.cwd()).targetRoot;\n\n const argSources = options.targets.map(arg => {\n if (arg.type === 'url') {\n if (!options.remote) {\n throw new Error(\n `Config argument \"${arg.target}\" looks like a URL but remote configuration is not enabled. Enable it by passing the \\`remote\\` option`,\n );\n }\n return RemoteConfigSource.create({\n url: arg.target,\n substitutionFunc: options.substitutionFunc,\n reloadInterval: options.remote.reloadInterval,\n });\n }\n return FileConfigSource.create({\n watch: options.watch,\n path: resolvePath(arg.target),\n substitutionFunc: options.substitutionFunc,\n });\n });\n\n if (argSources.length === 0) {\n const defaultPath = resolvePath(rootDir, 'app-config.yaml');\n const localPath = resolvePath(rootDir, 'app-config.local.yaml');\n const alwaysIncludeDefaultConfigSource =\n !options.allowMissingDefaultConfig;\n\n if (alwaysIncludeDefaultConfigSource || fs.pathExistsSync(defaultPath)) {\n argSources.push(\n FileConfigSource.create({\n watch: options.watch,\n path: defaultPath,\n substitutionFunc: options.substitutionFunc,\n }),\n );\n }\n\n if (fs.pathExistsSync(localPath)) {\n argSources.push(\n FileConfigSource.create({\n watch: options.watch,\n path: localPath,\n substitutionFunc: options.substitutionFunc,\n }),\n );\n }\n }\n\n return this.merge(argSources);\n }\n\n /**\n * Creates the default config source for Backstage.\n *\n * @remarks\n *\n * This will read from `app-config.yaml` and `app-config.local.yaml` by\n * default, as well as environment variables prefixed with `APP_CONFIG_`.\n * If `--config <path|url>` command line arguments are passed, these will\n * override the default configuration file paths. URLs are only supported\n * if the `remote` option is provided.\n *\n * @param options - Options\n * @returns The default Backstage config source\n */\n static default(options: ConfigSourcesDefaultOptions): ConfigSource {\n const argSource = this.defaultForTargets({\n ...options,\n targets: this.parseArgs(options.argv),\n });\n\n const envSource = EnvConfigSource.create({\n env: options.env,\n });\n\n return this.merge([argSource, envSource]);\n }\n\n /**\n * Merges multiple config sources into a single source that reads from all\n * sources and concatenates the result.\n *\n * @param sources - The config sources to merge\n * @returns A single config source that concatenates the data from the given sources\n */\n static merge(sources: ConfigSource[]): ConfigSource {\n return MergedConfigSource.from(sources);\n }\n\n /**\n * Creates an observable {@link @backstage/config#Config} implementation from a {@link ConfigSource}.\n *\n * @remarks\n *\n * If you only want to read the config once you can close the returned config immediately.\n *\n * @example\n *\n * ```ts\n * const sources = ConfigSources.default(...)\n * const config = await ConfigSources.toConfig(source)\n * config.close()\n * const example = config.getString(...)\n * ```\n *\n * @param source - The config source to read from\n * @returns A promise that resolves to a closable config\n */\n static toConfig(source: ConfigSource): Promise<ClosableConfig> {\n return new Promise(async (resolve, reject) => {\n let config: ObservableConfigProxy | undefined = undefined;\n try {\n const abortController = new AbortController();\n for await (const { configs } of source.readConfigData({\n signal: abortController.signal,\n })) {\n if (config) {\n config.setConfig(ConfigReader.fromConfigs(configs));\n } else {\n config = ObservableConfigProxy.create(abortController);\n config!.setConfig(ConfigReader.fromConfigs(configs));\n resolve(config);\n }\n }\n } catch (error) {\n reject(error);\n }\n });\n }\n}\n"],"names":["parseArgs","findPaths","RemoteConfigSource","FileConfigSource","resolvePath","fs","EnvConfigSource","MergedConfigSource","config","ConfigReader","ObservableConfigProxy"],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
|
|
5
|
+
class EnvConfigSource {
|
|
6
|
+
constructor(env) {
|
|
7
|
+
this.env = env;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new config source that reads from the environment.
|
|
11
|
+
*
|
|
12
|
+
* @param options - Options for the config source.
|
|
13
|
+
* @returns A new config source that reads from the environment.
|
|
14
|
+
*/
|
|
15
|
+
static create(options) {
|
|
16
|
+
return new EnvConfigSource(options?.env ?? process.env);
|
|
17
|
+
}
|
|
18
|
+
async *readConfigData() {
|
|
19
|
+
const configs = readEnvConfig(this.env);
|
|
20
|
+
yield { configs };
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
toString() {
|
|
24
|
+
const keys = Object.keys(this.env).filter(
|
|
25
|
+
(key) => key.startsWith("APP_CONFIG_")
|
|
26
|
+
);
|
|
27
|
+
return `EnvConfigSource{count=${keys.length}}`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const ENV_PREFIX = "APP_CONFIG_";
|
|
31
|
+
const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;
|
|
32
|
+
function readEnvConfig(env) {
|
|
33
|
+
let data = void 0;
|
|
34
|
+
for (const [name, value] of Object.entries(env)) {
|
|
35
|
+
if (!value) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (name.startsWith(ENV_PREFIX)) {
|
|
39
|
+
const key = name.replace(ENV_PREFIX, "");
|
|
40
|
+
const keyParts = key.split("_");
|
|
41
|
+
let obj = data = data ?? {};
|
|
42
|
+
for (const [index, part] of keyParts.entries()) {
|
|
43
|
+
if (!CONFIG_KEY_PART_PATTERN.test(part)) {
|
|
44
|
+
throw new TypeError(`Invalid env config key '${key}'`);
|
|
45
|
+
}
|
|
46
|
+
if (index < keyParts.length - 1) {
|
|
47
|
+
obj = obj[part] = obj[part] ?? {};
|
|
48
|
+
if (typeof obj !== "object" || Array.isArray(obj)) {
|
|
49
|
+
const subKey = keyParts.slice(0, index + 1).join("_");
|
|
50
|
+
throw new TypeError(
|
|
51
|
+
`Could not nest config for key '${key}' under existing value '${subKey}'`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
if (part in obj) {
|
|
56
|
+
throw new TypeError(
|
|
57
|
+
`Refusing to override existing config at key '${key}'`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const [, parsedValue] = safeJsonParse(value);
|
|
62
|
+
if (parsedValue === null) {
|
|
63
|
+
throw new Error("value may not be null");
|
|
64
|
+
}
|
|
65
|
+
obj[part] = parsedValue;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw new TypeError(
|
|
68
|
+
`Failed to parse JSON-serialized config value for key '${key}', ${error}`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return data ? [{ data, context: "env" }] : [];
|
|
76
|
+
}
|
|
77
|
+
function safeJsonParse(str) {
|
|
78
|
+
try {
|
|
79
|
+
return [null, JSON.parse(str)];
|
|
80
|
+
} catch (err) {
|
|
81
|
+
errors.assertError(err);
|
|
82
|
+
return [err, str];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
exports.EnvConfigSource = EnvConfigSource;
|
|
87
|
+
exports.readEnvConfig = readEnvConfig;
|
|
88
|
+
//# sourceMappingURL=EnvConfigSource.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EnvConfigSource.cjs.js","sources":["../../src/sources/EnvConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { assertError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { AsyncConfigSourceGenerator, ConfigSource } from './types';\n\n/**\n * Options for {@link EnvConfigSource.create}.\n *\n * @public\n */\nexport interface EnvConfigSourceOptions {\n /**\n * The environment variables to use, defaults to `process.env`.\n */\n env?: Record<string, string | undefined>;\n}\n\n/**\n * A config source that reads configuration from the environment.\n *\n * @remarks\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 class EnvConfigSource implements ConfigSource {\n /**\n * Creates a new config source that reads from the environment.\n *\n * @param options - Options for the config source.\n * @returns A new config source that reads from the environment.\n */\n static create(options: EnvConfigSourceOptions): ConfigSource {\n return new EnvConfigSource(options?.env ?? process.env);\n }\n\n private constructor(\n private readonly env: { [name: string]: string | undefined },\n ) {}\n\n async *readConfigData(): AsyncConfigSourceGenerator {\n const configs = readEnvConfig(this.env);\n yield { configs };\n return;\n }\n\n toString() {\n const keys = Object.keys(this.env).filter(key =>\n key.startsWith('APP_CONFIG_'),\n );\n return `EnvConfigSource{count=${keys.length}}`;\n }\n}\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 * @remarks\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 * @deprecated Use {@link EnvConfigSource} instead\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"],"names":["assertError"],"mappings":"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"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chokidar = require('chokidar');
|
|
4
|
+
var fs = require('fs-extra');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var apply = require('./transform/apply.cjs.js');
|
|
7
|
+
var errors = require('@backstage/errors');
|
|
8
|
+
var utils = require('./utils.cjs.js');
|
|
9
|
+
|
|
10
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
11
|
+
|
|
12
|
+
var chokidar__default = /*#__PURE__*/_interopDefaultCompat(chokidar);
|
|
13
|
+
var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
14
|
+
|
|
15
|
+
async function readFile(path) {
|
|
16
|
+
try {
|
|
17
|
+
const content = await fs__default.default.readFile(path, "utf8");
|
|
18
|
+
if (content === "") {
|
|
19
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
20
|
+
return await fs__default.default.readFile(path, "utf8");
|
|
21
|
+
}
|
|
22
|
+
return content;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error.code === "ENOENT") {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
class FileConfigSource {
|
|
31
|
+
/**
|
|
32
|
+
* Creates a new config source that loads configuration from the given path.
|
|
33
|
+
*
|
|
34
|
+
* @remarks
|
|
35
|
+
*
|
|
36
|
+
* The source will watch the file for changes, as well as any referenced files.
|
|
37
|
+
*
|
|
38
|
+
* @param options - Options for the config source.
|
|
39
|
+
* @returns A new config source that loads from the given path.
|
|
40
|
+
*/
|
|
41
|
+
static create(options) {
|
|
42
|
+
if (!path.isAbsolute(options.path)) {
|
|
43
|
+
throw new Error(`Config load path is not absolute: "${options.path}"`);
|
|
44
|
+
}
|
|
45
|
+
return new FileConfigSource(options);
|
|
46
|
+
}
|
|
47
|
+
#path;
|
|
48
|
+
#substitutionFunc;
|
|
49
|
+
#watch;
|
|
50
|
+
#parser;
|
|
51
|
+
constructor(options) {
|
|
52
|
+
this.#path = options.path;
|
|
53
|
+
this.#substitutionFunc = options.substitutionFunc;
|
|
54
|
+
this.#watch = options.watch ?? true;
|
|
55
|
+
this.#parser = options.parser ?? utils.parseYamlContent;
|
|
56
|
+
}
|
|
57
|
+
// Work is duplicated across each read, in practice that should not
|
|
58
|
+
// have any impact since there won't be multiple consumers. If that
|
|
59
|
+
// changes it might be worth refactoring this to avoid duplicate work.
|
|
60
|
+
async *readConfigData(options) {
|
|
61
|
+
const signal = options?.signal;
|
|
62
|
+
const configFileName = path.basename(this.#path);
|
|
63
|
+
let watchedPaths = null;
|
|
64
|
+
let watcher = null;
|
|
65
|
+
if (this.#watch) {
|
|
66
|
+
watchedPaths = new Array();
|
|
67
|
+
watcher = chokidar__default.default.watch(this.#path, {
|
|
68
|
+
usePolling: process.env.NODE_ENV === "test"
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const dir = path.dirname(this.#path);
|
|
72
|
+
const transformer = apply.createConfigTransformer({
|
|
73
|
+
substitutionFunc: this.#substitutionFunc,
|
|
74
|
+
readFile: async (path$1) => {
|
|
75
|
+
const fullPath = path.resolve(dir, path$1);
|
|
76
|
+
if (watcher && watchedPaths) {
|
|
77
|
+
watcher.add(fullPath);
|
|
78
|
+
watchedPaths.push(fullPath);
|
|
79
|
+
}
|
|
80
|
+
const data = await readFile(fullPath);
|
|
81
|
+
if (data === void 0) {
|
|
82
|
+
throw new errors.NotFoundError(
|
|
83
|
+
`failed to include "${fullPath}", file does not exist`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return data;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const readConfigFile = async () => {
|
|
90
|
+
if (watcher && watchedPaths) {
|
|
91
|
+
watcher.unwatch(watchedPaths);
|
|
92
|
+
watchedPaths.length = 0;
|
|
93
|
+
watcher.add(this.#path);
|
|
94
|
+
watchedPaths.push(this.#path);
|
|
95
|
+
}
|
|
96
|
+
const contents = await readFile(this.#path);
|
|
97
|
+
if (contents === void 0) {
|
|
98
|
+
throw new errors.NotFoundError(`Config file "${this.#path}" does not exist`);
|
|
99
|
+
}
|
|
100
|
+
const { result: parsed } = await this.#parser({ contents });
|
|
101
|
+
if (parsed === void 0) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const data = await transformer(parsed, { dir });
|
|
106
|
+
return [{ data, context: configFileName, path: this.#path }];
|
|
107
|
+
} catch (error) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Failed to read config file at "${this.#path}", ${error.message}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const onAbort = () => {
|
|
114
|
+
signal?.removeEventListener("abort", onAbort);
|
|
115
|
+
if (watcher) watcher.close();
|
|
116
|
+
};
|
|
117
|
+
signal?.addEventListener("abort", onAbort);
|
|
118
|
+
yield { configs: await readConfigFile() };
|
|
119
|
+
if (watcher) {
|
|
120
|
+
for (; ; ) {
|
|
121
|
+
const event = await this.#waitForEvent(watcher, signal);
|
|
122
|
+
if (event === "abort") {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
yield { configs: await readConfigFile() };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
toString() {
|
|
130
|
+
return `FileConfigSource{path="${this.#path}"}`;
|
|
131
|
+
}
|
|
132
|
+
#waitForEvent(watcher, signal) {
|
|
133
|
+
return new Promise((resolve) => {
|
|
134
|
+
function onChange() {
|
|
135
|
+
resolve("change");
|
|
136
|
+
onDone();
|
|
137
|
+
}
|
|
138
|
+
function onAbort() {
|
|
139
|
+
resolve("abort");
|
|
140
|
+
onDone();
|
|
141
|
+
}
|
|
142
|
+
function onDone() {
|
|
143
|
+
watcher.removeListener("change", onChange);
|
|
144
|
+
signal?.removeEventListener("abort", onAbort);
|
|
145
|
+
}
|
|
146
|
+
watcher.addListener("change", onChange);
|
|
147
|
+
signal?.addEventListener("abort", onAbort);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
exports.FileConfigSource = FileConfigSource;
|
|
153
|
+
//# sourceMappingURL=FileConfigSource.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileConfigSource.cjs.js","sources":["../../src/sources/FileConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 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 chokidar, { FSWatcher } from 'chokidar';\nimport fs from 'fs-extra';\nimport { basename, dirname, isAbsolute, resolve as resolvePath } from 'path';\nimport {\n AsyncConfigSourceGenerator,\n ConfigSource,\n ConfigSourceData,\n SubstitutionFunc,\n Parser,\n ReadConfigDataOptions,\n} from './types';\nimport { createConfigTransformer } from './transform';\nimport { NotFoundError } from '@backstage/errors';\nimport { parseYamlContent } from './utils';\n\n/**\n * Options for {@link FileConfigSource.create}.\n *\n * @public\n */\nexport interface FileConfigSourceOptions {\n /**\n * The path to the config file that should be loaded.\n */\n path: string;\n\n /**\n * Set to `false` to disable file watching, defaults to `true`.\n */\n watch?: boolean;\n\n /**\n * A substitution function to use instead of the default environment substitution.\n */\n substitutionFunc?: SubstitutionFunc;\n\n /**\n * A content parsing function to transform string content to configuration values.\n */\n parser?: Parser;\n}\n\nasync function readFile(path: string): Promise<string | undefined> {\n try {\n const content = await fs.readFile(path, 'utf8');\n // During watching we may sometimes read files too early before the file content has been written.\n // We never expect the writing to take a long time, but if we encounter an empty file then check\n // again after a short delay for safety.\n if (content === '') {\n await new Promise(resolve => setTimeout(resolve, 10));\n return await fs.readFile(path, 'utf8');\n }\n return content;\n } catch (error) {\n if (error.code === 'ENOENT') {\n return undefined;\n }\n throw error;\n }\n}\n\n/**\n * A config source that loads configuration from a local file.\n *\n * @public\n */\nexport class FileConfigSource implements ConfigSource {\n /**\n * Creates a new config source that loads configuration from the given path.\n *\n * @remarks\n *\n * The source will watch the file for changes, as well as any referenced files.\n *\n * @param options - Options for the config source.\n * @returns A new config source that loads from the given path.\n */\n static create(options: FileConfigSourceOptions): ConfigSource {\n if (!isAbsolute(options.path)) {\n throw new Error(`Config load path is not absolute: \"${options.path}\"`);\n }\n return new FileConfigSource(options);\n }\n\n readonly #path: string;\n readonly #substitutionFunc?: SubstitutionFunc;\n readonly #watch?: boolean;\n readonly #parser: Parser;\n\n private constructor(options: FileConfigSourceOptions) {\n this.#path = options.path;\n this.#substitutionFunc = options.substitutionFunc;\n this.#watch = options.watch ?? true;\n this.#parser = options.parser ?? parseYamlContent;\n }\n\n // Work is duplicated across each read, in practice that should not\n // have any impact since there won't be multiple consumers. If that\n // changes it might be worth refactoring this to avoid duplicate work.\n async *readConfigData(\n options?: ReadConfigDataOptions,\n ): AsyncConfigSourceGenerator {\n const signal = options?.signal;\n const configFileName = basename(this.#path);\n\n let watchedPaths: Array<string> | null = null;\n let watcher: FSWatcher | null = null;\n\n if (this.#watch) {\n // Keep track of watched paths, since this is simpler than resetting the watcher\n watchedPaths = new Array<string>();\n watcher = chokidar.watch(this.#path, {\n usePolling: process.env.NODE_ENV === 'test',\n });\n }\n\n const dir = dirname(this.#path);\n const transformer = createConfigTransformer({\n substitutionFunc: this.#substitutionFunc,\n readFile: async path => {\n const fullPath = resolvePath(dir, path);\n if (watcher && watchedPaths) {\n // Any files discovered while reading this config should be watched too\n watcher.add(fullPath);\n watchedPaths.push(fullPath);\n }\n\n const data = await readFile(fullPath);\n if (data === undefined) {\n throw new NotFoundError(\n `failed to include \"${fullPath}\", file does not exist`,\n );\n }\n return data;\n },\n });\n\n // This is the entry point for reading the file, called initially and on change\n const readConfigFile = async (): Promise<ConfigSourceData[]> => {\n if (watcher && watchedPaths) {\n // We clear the watched files every time we initiate a new read\n watcher.unwatch(watchedPaths);\n watchedPaths.length = 0;\n\n watcher.add(this.#path);\n watchedPaths.push(this.#path);\n }\n\n const contents = await readFile(this.#path);\n if (contents === undefined) {\n throw new NotFoundError(`Config file \"${this.#path}\" does not exist`);\n }\n const { result: parsed } = await this.#parser({ contents });\n if (parsed === undefined) {\n return [];\n }\n try {\n const data = await transformer(parsed, { dir });\n return [{ data, context: configFileName, path: this.#path }];\n } catch (error) {\n throw new Error(\n `Failed to read config file at \"${this.#path}\", ${error.message}`,\n );\n }\n };\n\n const onAbort = () => {\n signal?.removeEventListener('abort', onAbort);\n if (watcher) watcher.close();\n };\n signal?.addEventListener('abort', onAbort);\n\n yield { configs: await readConfigFile() };\n\n if (watcher) {\n for (;;) {\n const event = await this.#waitForEvent(watcher, signal);\n if (event === 'abort') {\n return;\n }\n yield { configs: await readConfigFile() };\n }\n }\n }\n\n toString() {\n return `FileConfigSource{path=\"${this.#path}\"}`;\n }\n\n #waitForEvent(\n watcher: FSWatcher,\n signal?: AbortSignal,\n ): Promise<'change' | 'abort'> {\n return new Promise(resolve => {\n function onChange() {\n resolve('change');\n onDone();\n }\n function onAbort() {\n resolve('abort');\n onDone();\n }\n function onDone() {\n watcher.removeListener('change', onChange);\n signal?.removeEventListener('abort', onAbort);\n }\n watcher.addListener('change', onChange);\n signal?.addEventListener('abort', onAbort);\n });\n }\n}\n"],"names":["fs","isAbsolute","parseYamlContent","basename","chokidar","dirname","createConfigTransformer","path","resolvePath","NotFoundError"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const sourcesSymbol = Symbol.for(
|
|
4
|
+
"@backstage/config-loader#MergedConfigSource.sources"
|
|
5
|
+
);
|
|
6
|
+
class MergedConfigSource {
|
|
7
|
+
constructor(sources) {
|
|
8
|
+
this.sources = sources;
|
|
9
|
+
this[sourcesSymbol] = this.sources;
|
|
10
|
+
}
|
|
11
|
+
// An optimization to flatten nested merged sources to avid unnecessary microtasks
|
|
12
|
+
static #flattenSources(sources) {
|
|
13
|
+
return sources.flatMap((source) => {
|
|
14
|
+
if (sourcesSymbol in source && Array.isArray(source[sourcesSymbol])) {
|
|
15
|
+
return this.#flattenSources(
|
|
16
|
+
source[sourcesSymbol]
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return source;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
static from(sources) {
|
|
23
|
+
return new MergedConfigSource(this.#flattenSources(sources));
|
|
24
|
+
}
|
|
25
|
+
[sourcesSymbol];
|
|
26
|
+
async *readConfigData(options) {
|
|
27
|
+
const its = this.sources.map((source) => source.readConfigData(options));
|
|
28
|
+
const initialResults = await Promise.all(its.map((it) => it.next()));
|
|
29
|
+
const configs = initialResults.map((result, i) => {
|
|
30
|
+
if (result.done) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Config source ${String(this.sources[i])} returned no data`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return result.value.configs;
|
|
36
|
+
});
|
|
37
|
+
yield { configs: configs.flat(1) };
|
|
38
|
+
const results = its.map((it, i) => nextWithIndex(it, i));
|
|
39
|
+
while (results.some(Boolean)) {
|
|
40
|
+
try {
|
|
41
|
+
const [i, result] = await Promise.race(results.filter(Boolean));
|
|
42
|
+
if (result.done) {
|
|
43
|
+
results[i] = void 0;
|
|
44
|
+
} else {
|
|
45
|
+
results[i] = nextWithIndex(its[i], i);
|
|
46
|
+
configs[i] = result.value.configs;
|
|
47
|
+
yield { configs: configs.flat(1) };
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
const source = this.sources[error.index];
|
|
51
|
+
if (source) {
|
|
52
|
+
throw new Error(`Config source ${String(source)} failed: ${error}`);
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
toString() {
|
|
59
|
+
return `MergedConfigSource{${this.sources.map(String).join(", ")}}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function nextWithIndex(iterator, index) {
|
|
63
|
+
return iterator.next().then(
|
|
64
|
+
(r) => [index, r],
|
|
65
|
+
(e) => {
|
|
66
|
+
throw Object.assign(e, { index });
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
exports.MergedConfigSource = MergedConfigSource;
|
|
72
|
+
//# sourceMappingURL=MergedConfigSource.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MergedConfigSource.cjs.js","sources":["../../src/sources/MergedConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 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 {\n AsyncConfigSourceGenerator,\n ConfigSource,\n ConfigSourceData,\n ReadConfigDataOptions,\n} from './types';\n\nconst sourcesSymbol = Symbol.for(\n '@backstage/config-loader#MergedConfigSource.sources',\n);\n\n/** @internal */\nexport class MergedConfigSource implements ConfigSource {\n // An optimization to flatten nested merged sources to avid unnecessary microtasks\n static #flattenSources(sources: ConfigSource[]): ConfigSource[] {\n return sources.flatMap(source => {\n if (\n sourcesSymbol in source &&\n Array.isArray((source as any)[sourcesSymbol])\n ) {\n return this.#flattenSources(\n (source as any)[sourcesSymbol] as ConfigSource[],\n );\n }\n return source;\n });\n }\n\n static from(sources: ConfigSource[]): ConfigSource {\n return new MergedConfigSource(this.#flattenSources(sources));\n }\n\n [sourcesSymbol]: ConfigSource[];\n\n private constructor(private readonly sources: ConfigSource[]) {\n this[sourcesSymbol] = this.sources;\n }\n\n async *readConfigData(\n options?: ReadConfigDataOptions,\n ): AsyncConfigSourceGenerator {\n const its = this.sources.map(source => source.readConfigData(options));\n const initialResults = await Promise.all(its.map(it => it.next()));\n const configs = initialResults.map((result, i) => {\n if (result.done) {\n throw new Error(\n `Config source ${String(this.sources[i])} returned no data`,\n );\n }\n return result.value.configs;\n });\n\n yield { configs: configs.flat(1) };\n\n const results: Array<\n | Promise<\n readonly [\n number,\n IteratorResult<{ configs: ConfigSourceData[] }, void>,\n ]\n >\n | undefined\n > = its.map((it, i) => nextWithIndex(it, i));\n\n while (results.some(Boolean)) {\n try {\n const [i, result] = (await Promise.race(results.filter(Boolean)))!;\n if (result.done) {\n results[i] = undefined;\n } else {\n results[i] = nextWithIndex(its[i], i);\n configs[i] = result.value.configs;\n yield { configs: configs.flat(1) };\n }\n } catch (error) {\n const source = this.sources[error.index];\n if (source) {\n throw new Error(`Config source ${String(source)} failed: ${error}`);\n }\n throw error;\n }\n }\n }\n\n toString() {\n return `MergedConfigSource{${this.sources.map(String).join(', ')}}`;\n }\n}\n\n// Helper to wait for the next value of the iterator, while decorating the value\n// or error with the index of the iterator.\nfunction nextWithIndex<T>(\n iterator: AsyncIterator<T, void, void>,\n index: number,\n): Promise<readonly [index: number, result: IteratorResult<T, void>]> {\n return iterator.next().then(\n r => [index, r] as const,\n e => {\n throw Object.assign(e, { index });\n },\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var types = require('@backstage/types');
|
|
4
|
+
var utils = require('./utils.cjs.js');
|
|
5
|
+
|
|
6
|
+
class MutableConfigSource {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new mutable config source.
|
|
9
|
+
*
|
|
10
|
+
* @param options - Options for the config source.
|
|
11
|
+
* @returns A new mutable config source.
|
|
12
|
+
*/
|
|
13
|
+
static create(options) {
|
|
14
|
+
return new MutableConfigSource(
|
|
15
|
+
options?.context ?? "mutable-config",
|
|
16
|
+
options?.data
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
#currentData;
|
|
20
|
+
#deferred;
|
|
21
|
+
#context;
|
|
22
|
+
#abortController = new AbortController();
|
|
23
|
+
constructor(context, initialData) {
|
|
24
|
+
this.#currentData = initialData;
|
|
25
|
+
this.#context = context;
|
|
26
|
+
this.#deferred = types.createDeferred();
|
|
27
|
+
}
|
|
28
|
+
async *readConfigData(options) {
|
|
29
|
+
let deferredPromise = this.#deferred;
|
|
30
|
+
if (this.#currentData !== void 0) {
|
|
31
|
+
yield { configs: [{ data: this.#currentData, context: this.#context }] };
|
|
32
|
+
}
|
|
33
|
+
for (; ; ) {
|
|
34
|
+
const [ok] = await utils.waitOrAbort(deferredPromise, [
|
|
35
|
+
options?.signal,
|
|
36
|
+
this.#abortController.signal
|
|
37
|
+
]);
|
|
38
|
+
if (!ok) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
deferredPromise = this.#deferred;
|
|
42
|
+
if (this.#currentData !== void 0) {
|
|
43
|
+
yield {
|
|
44
|
+
configs: [{ data: this.#currentData, context: this.#context }]
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Set the data of the config source.
|
|
51
|
+
*
|
|
52
|
+
* @param data - The new data to set
|
|
53
|
+
*/
|
|
54
|
+
setData(data) {
|
|
55
|
+
if (!this.#abortController.signal.aborted) {
|
|
56
|
+
this.#currentData = data;
|
|
57
|
+
const oldDeferred = this.#deferred;
|
|
58
|
+
this.#deferred = types.createDeferred();
|
|
59
|
+
oldDeferred.resolve();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Close the config source, preventing any further updates.
|
|
64
|
+
*/
|
|
65
|
+
close() {
|
|
66
|
+
this.#currentData = void 0;
|
|
67
|
+
this.#abortController.abort();
|
|
68
|
+
}
|
|
69
|
+
toString() {
|
|
70
|
+
return `MutableConfigSource{}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
exports.MutableConfigSource = MutableConfigSource;
|
|
75
|
+
//# sourceMappingURL=MutableConfigSource.cjs.js.map
|