@backstage/config-loader 1.2.0 → 1.3.0
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 +100 -0
- package/dist/index.cjs.js +1174 -375
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +440 -31
- package/package.json +7 -3
package/dist/index.cjs.js
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var errors = require('@backstage/errors');
|
|
6
|
-
var yaml = require('yaml');
|
|
7
|
-
var path = require('path');
|
|
8
5
|
var Ajv = require('ajv');
|
|
9
6
|
var mergeAllOf = require('json-schema-merge-allof');
|
|
10
7
|
var traverse = require('json-schema-traverse');
|
|
11
8
|
var config = require('@backstage/config');
|
|
12
9
|
var fs = require('fs-extra');
|
|
10
|
+
var path = require('path');
|
|
11
|
+
var errors = require('@backstage/errors');
|
|
12
|
+
var parseArgs = require('minimist');
|
|
13
13
|
var chokidar = require('chokidar');
|
|
14
|
+
var yaml = require('yaml');
|
|
15
|
+
var isEqual = require('lodash/isEqual');
|
|
14
16
|
var fetch = require('node-fetch');
|
|
17
|
+
var cliCommon = require('@backstage/cli-common');
|
|
15
18
|
|
|
16
19
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
17
20
|
|
|
@@ -33,243 +36,20 @@ function _interopNamespace(e) {
|
|
|
33
36
|
return Object.freeze(n);
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
var yaml__default = /*#__PURE__*/_interopDefaultLegacy(yaml);
|
|
37
39
|
var Ajv__default = /*#__PURE__*/_interopDefaultLegacy(Ajv);
|
|
38
40
|
var mergeAllOf__default = /*#__PURE__*/_interopDefaultLegacy(mergeAllOf);
|
|
39
41
|
var traverse__default = /*#__PURE__*/_interopDefaultLegacy(traverse);
|
|
40
42
|
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
|
|
43
|
+
var parseArgs__default = /*#__PURE__*/_interopDefaultLegacy(parseArgs);
|
|
41
44
|
var chokidar__default = /*#__PURE__*/_interopDefaultLegacy(chokidar);
|
|
45
|
+
var yaml__default = /*#__PURE__*/_interopDefaultLegacy(yaml);
|
|
46
|
+
var isEqual__default = /*#__PURE__*/_interopDefaultLegacy(isEqual);
|
|
42
47
|
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
43
48
|
|
|
44
|
-
const ENV_PREFIX = "APP_CONFIG_";
|
|
45
|
-
const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;
|
|
46
|
-
function readEnvConfig(env) {
|
|
47
|
-
var _a;
|
|
48
|
-
let data = void 0;
|
|
49
|
-
for (const [name, value] of Object.entries(env)) {
|
|
50
|
-
if (!value) {
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
if (name.startsWith(ENV_PREFIX)) {
|
|
54
|
-
const key = name.replace(ENV_PREFIX, "");
|
|
55
|
-
const keyParts = key.split("_");
|
|
56
|
-
let obj = data = data != null ? data : {};
|
|
57
|
-
for (const [index, part] of keyParts.entries()) {
|
|
58
|
-
if (!CONFIG_KEY_PART_PATTERN.test(part)) {
|
|
59
|
-
throw new TypeError(`Invalid env config key '${key}'`);
|
|
60
|
-
}
|
|
61
|
-
if (index < keyParts.length - 1) {
|
|
62
|
-
obj = obj[part] = (_a = obj[part]) != null ? _a : {};
|
|
63
|
-
if (typeof obj !== "object" || Array.isArray(obj)) {
|
|
64
|
-
const subKey = keyParts.slice(0, index + 1).join("_");
|
|
65
|
-
throw new TypeError(
|
|
66
|
-
`Could not nest config for key '${key}' under existing value '${subKey}'`
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
if (part in obj) {
|
|
71
|
-
throw new TypeError(
|
|
72
|
-
`Refusing to override existing config at key '${key}'`
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
try {
|
|
76
|
-
const [, parsedValue] = safeJsonParse(value);
|
|
77
|
-
if (parsedValue === null) {
|
|
78
|
-
throw new Error("value may not be null");
|
|
79
|
-
}
|
|
80
|
-
obj[part] = parsedValue;
|
|
81
|
-
} catch (error) {
|
|
82
|
-
throw new TypeError(
|
|
83
|
-
`Failed to parse JSON-serialized config value for key '${key}', ${error}`
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return data ? [{ data, context: "env" }] : [];
|
|
91
|
-
}
|
|
92
|
-
function safeJsonParse(str) {
|
|
93
|
-
try {
|
|
94
|
-
return [null, JSON.parse(str)];
|
|
95
|
-
} catch (err) {
|
|
96
|
-
errors.assertError(err);
|
|
97
|
-
return [err, str];
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function isObject(obj) {
|
|
102
|
-
if (typeof obj !== "object") {
|
|
103
|
-
return false;
|
|
104
|
-
} else if (Array.isArray(obj)) {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
return obj !== null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function applyConfigTransforms(initialDir, input, transforms) {
|
|
111
|
-
async function transform(inputObj, path, baseDir) {
|
|
112
|
-
var _a;
|
|
113
|
-
let obj = inputObj;
|
|
114
|
-
let dir = baseDir;
|
|
115
|
-
for (const tf of transforms) {
|
|
116
|
-
try {
|
|
117
|
-
const result = await tf(inputObj, baseDir);
|
|
118
|
-
if (result.applied) {
|
|
119
|
-
if (result.value === void 0) {
|
|
120
|
-
return void 0;
|
|
121
|
-
}
|
|
122
|
-
obj = result.value;
|
|
123
|
-
dir = (_a = result.newBaseDir) != null ? _a : dir;
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
} catch (error) {
|
|
127
|
-
errors.assertError(error);
|
|
128
|
-
throw new Error(`error at ${path}, ${error.message}`);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (typeof obj !== "object") {
|
|
132
|
-
return obj;
|
|
133
|
-
} else if (obj === null) {
|
|
134
|
-
return void 0;
|
|
135
|
-
} else if (Array.isArray(obj)) {
|
|
136
|
-
const arr = new Array();
|
|
137
|
-
for (const [index, value] of obj.entries()) {
|
|
138
|
-
const out2 = await transform(value, `${path}[${index}]`, dir);
|
|
139
|
-
if (out2 !== void 0) {
|
|
140
|
-
arr.push(out2);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return arr;
|
|
144
|
-
}
|
|
145
|
-
const out = {};
|
|
146
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
147
|
-
if (value !== void 0) {
|
|
148
|
-
const result = await transform(value, `${path}.${key}`, dir);
|
|
149
|
-
if (result !== void 0) {
|
|
150
|
-
out[key] = result;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return out;
|
|
155
|
-
}
|
|
156
|
-
const finalData = await transform(input, "", initialDir);
|
|
157
|
-
if (!isObject(finalData)) {
|
|
158
|
-
throw new TypeError("expected object at config root");
|
|
159
|
-
}
|
|
160
|
-
return finalData;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const includeFileParser = {
|
|
164
|
-
".json": async (content) => JSON.parse(content),
|
|
165
|
-
".yaml": async (content) => yaml__default["default"].parse(content),
|
|
166
|
-
".yml": async (content) => yaml__default["default"].parse(content)
|
|
167
|
-
};
|
|
168
|
-
function createIncludeTransform(env, readFile, substitute) {
|
|
169
|
-
return async (input, baseDir) => {
|
|
170
|
-
if (!isObject(input)) {
|
|
171
|
-
return { applied: false };
|
|
172
|
-
}
|
|
173
|
-
const [includeKey] = Object.keys(input).filter((key) => key.startsWith("$"));
|
|
174
|
-
if (includeKey) {
|
|
175
|
-
if (Object.keys(input).length !== 1) {
|
|
176
|
-
throw new Error(
|
|
177
|
-
`include key ${includeKey} should not have adjacent keys`
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
} else {
|
|
181
|
-
return { applied: false };
|
|
182
|
-
}
|
|
183
|
-
const rawIncludedValue = input[includeKey];
|
|
184
|
-
if (typeof rawIncludedValue !== "string") {
|
|
185
|
-
throw new Error(`${includeKey} include value is not a string`);
|
|
186
|
-
}
|
|
187
|
-
const substituteResults = await substitute(rawIncludedValue, baseDir);
|
|
188
|
-
const includeValue = substituteResults.applied ? substituteResults.value : rawIncludedValue;
|
|
189
|
-
if (includeValue === void 0 || typeof includeValue !== "string") {
|
|
190
|
-
throw new Error(`${includeKey} substitution value was undefined`);
|
|
191
|
-
}
|
|
192
|
-
switch (includeKey) {
|
|
193
|
-
case "$file":
|
|
194
|
-
try {
|
|
195
|
-
const value = await readFile(path.resolve(baseDir, includeValue));
|
|
196
|
-
return { applied: true, value: value.trimEnd() };
|
|
197
|
-
} catch (error) {
|
|
198
|
-
throw new Error(`failed to read file ${includeValue}, ${error}`);
|
|
199
|
-
}
|
|
200
|
-
case "$env":
|
|
201
|
-
try {
|
|
202
|
-
return { applied: true, value: await env(includeValue) };
|
|
203
|
-
} catch (error) {
|
|
204
|
-
throw new Error(`failed to read env ${includeValue}, ${error}`);
|
|
205
|
-
}
|
|
206
|
-
case "$include": {
|
|
207
|
-
const [filePath, dataPath] = includeValue.split(/#(.*)/);
|
|
208
|
-
const ext = path.extname(filePath);
|
|
209
|
-
const parser = includeFileParser[ext];
|
|
210
|
-
if (!parser) {
|
|
211
|
-
throw new Error(
|
|
212
|
-
`no configuration parser available for included file ${filePath}`
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
const path$1 = path.resolve(baseDir, filePath);
|
|
216
|
-
const content = await readFile(path$1);
|
|
217
|
-
const newBaseDir = path.dirname(path$1);
|
|
218
|
-
const parts = dataPath ? dataPath.split(".") : [];
|
|
219
|
-
let value;
|
|
220
|
-
try {
|
|
221
|
-
value = await parser(content);
|
|
222
|
-
} catch (error) {
|
|
223
|
-
throw new Error(
|
|
224
|
-
`failed to parse included file ${filePath}, ${error}`
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
for (const [index, part] of parts.entries()) {
|
|
228
|
-
if (!isObject(value)) {
|
|
229
|
-
const errPath = parts.slice(0, index).join(".");
|
|
230
|
-
throw new Error(
|
|
231
|
-
`value at '${errPath}' in included file ${filePath} is not an object`
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
value = value[part];
|
|
235
|
-
}
|
|
236
|
-
return {
|
|
237
|
-
applied: true,
|
|
238
|
-
value,
|
|
239
|
-
newBaseDir: newBaseDir !== baseDir ? newBaseDir : void 0
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
default:
|
|
243
|
-
throw new Error(`unknown include ${includeKey}`);
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function createSubstitutionTransform(env) {
|
|
249
|
-
return async (input) => {
|
|
250
|
-
if (typeof input !== "string") {
|
|
251
|
-
return { applied: false };
|
|
252
|
-
}
|
|
253
|
-
const parts = input.split(/(\$?\$\{[^{}]*\})/);
|
|
254
|
-
for (let i = 1; i < parts.length; i += 2) {
|
|
255
|
-
const part = parts[i];
|
|
256
|
-
if (part.startsWith("$$")) {
|
|
257
|
-
parts[i] = part.slice(1);
|
|
258
|
-
} else {
|
|
259
|
-
parts[i] = await env(part.slice(2, -1).trim());
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
if (parts.some((part) => part === void 0)) {
|
|
263
|
-
return { applied: true, value: void 0 };
|
|
264
|
-
}
|
|
265
|
-
return { applied: true, value: parts.join("") };
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
49
|
const CONFIG_VISIBILITIES = ["frontend", "backend", "secret"];
|
|
270
50
|
const DEFAULT_CONFIG_VISIBILITY = "backend";
|
|
271
51
|
|
|
272
|
-
function compileConfigSchemas(schemas) {
|
|
52
|
+
function compileConfigSchemas(schemas, options) {
|
|
273
53
|
const visibilityByDataPath = /* @__PURE__ */ new Map();
|
|
274
54
|
const deprecationByDataPath = /* @__PURE__ */ new Map();
|
|
275
55
|
const ajv = new Ajv__default["default"]({
|
|
@@ -325,6 +105,13 @@ function compileConfigSchemas(schemas) {
|
|
|
325
105
|
}
|
|
326
106
|
}
|
|
327
107
|
const merged = mergeConfigSchemas(schemas.map((_) => _.value));
|
|
108
|
+
if (options == null ? void 0 : options.noUndeclaredProperties) {
|
|
109
|
+
traverse__default["default"](merged, (schema) => {
|
|
110
|
+
if ((schema == null ? void 0 : schema.type) === "object") {
|
|
111
|
+
schema.additionalProperties || (schema.additionalProperties = false);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
328
115
|
const validate = ajv.compile(merged);
|
|
329
116
|
const visibilityBySchemaPath = /* @__PURE__ */ new Map();
|
|
330
117
|
traverse__default["default"](merged, (schema, path) => {
|
|
@@ -651,7 +438,9 @@ async function loadConfigSchema(options) {
|
|
|
651
438
|
}
|
|
652
439
|
schemas = serialized.schemas;
|
|
653
440
|
}
|
|
654
|
-
const validate = compileConfigSchemas(schemas
|
|
441
|
+
const validate = compileConfigSchemas(schemas, {
|
|
442
|
+
noUndeclaredProperties: options.noUndeclaredProperties
|
|
443
|
+
});
|
|
655
444
|
return {
|
|
656
445
|
process(configs, {
|
|
657
446
|
visibility,
|
|
@@ -711,173 +500,1183 @@ async function loadConfigSchema(options) {
|
|
|
711
500
|
};
|
|
712
501
|
}
|
|
713
502
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
return true;
|
|
718
|
-
} catch {
|
|
719
|
-
return false;
|
|
503
|
+
class EnvConfigSource {
|
|
504
|
+
constructor(env) {
|
|
505
|
+
this.env = env;
|
|
720
506
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
`Please make sure you are passing the remote option when loading remote configurations. See https://backstage.io/docs/conf/writing#configuration-files for detailed info.`
|
|
731
|
-
);
|
|
732
|
-
}
|
|
733
|
-
} else if (remote.reloadIntervalSeconds <= 0) {
|
|
734
|
-
throw new Error(
|
|
735
|
-
`Remote config must be contain a non zero reloadIntervalSeconds: <seconds> value`
|
|
736
|
-
);
|
|
507
|
+
/**
|
|
508
|
+
* Creates a new config source that reads from the environment.
|
|
509
|
+
*
|
|
510
|
+
* @param options - Options for the config source.
|
|
511
|
+
* @returns A new config source that reads from the environment.
|
|
512
|
+
*/
|
|
513
|
+
static create(options) {
|
|
514
|
+
var _a;
|
|
515
|
+
return new EnvConfigSource((_a = options == null ? void 0 : options.env) != null ? _a : process.env);
|
|
737
516
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
configPaths.push(localConfig);
|
|
743
|
-
}
|
|
517
|
+
async *readConfigData() {
|
|
518
|
+
const configs = readEnvConfig(this.env);
|
|
519
|
+
yield { configs };
|
|
520
|
+
return;
|
|
744
521
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (!path.isAbsolute(configPath)) {
|
|
751
|
-
throw new Error(`Config load path is not absolute: '${configPath}'`);
|
|
752
|
-
}
|
|
753
|
-
const dir = path.dirname(configPath);
|
|
754
|
-
const readFile = (path$1) => {
|
|
755
|
-
const fullPath = path.resolve(dir, path$1);
|
|
756
|
-
loadedPaths2.add(fullPath);
|
|
757
|
-
return fs__default["default"].readFile(fullPath, "utf8");
|
|
758
|
-
};
|
|
759
|
-
const input = yaml__default["default"].parse(await readFile(configPath));
|
|
760
|
-
if (input !== null) {
|
|
761
|
-
const substitutionTransform = createSubstitutionTransform(env);
|
|
762
|
-
const data = await applyConfigTransforms(dir, input, [
|
|
763
|
-
createIncludeTransform(env, readFile, substitutionTransform),
|
|
764
|
-
substitutionTransform
|
|
765
|
-
]);
|
|
766
|
-
fileConfigs2.push({ data, context: path.basename(configPath) });
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
return { fileConfigs: fileConfigs2, loadedPaths: loadedPaths2 };
|
|
770
|
-
};
|
|
771
|
-
const loadRemoteConfigFiles = async () => {
|
|
772
|
-
const configs = [];
|
|
773
|
-
const readConfigFromUrl = async (url) => {
|
|
774
|
-
const response = await fetch__default["default"](url);
|
|
775
|
-
if (!response.ok) {
|
|
776
|
-
throw new Error(`Could not read config file at ${url}`);
|
|
777
|
-
}
|
|
778
|
-
return await response.text();
|
|
779
|
-
};
|
|
780
|
-
for (let i = 0; i < configUrls.length; i++) {
|
|
781
|
-
const configUrl = configUrls[i];
|
|
782
|
-
if (!isValidUrl(configUrl)) {
|
|
783
|
-
throw new Error(`Config load path is not valid: '${configUrl}'`);
|
|
784
|
-
}
|
|
785
|
-
const remoteConfigContent = await readConfigFromUrl(configUrl);
|
|
786
|
-
if (!remoteConfigContent) {
|
|
787
|
-
throw new Error(`Config is not valid`);
|
|
788
|
-
}
|
|
789
|
-
const configYaml = yaml__default["default"].parse(remoteConfigContent);
|
|
790
|
-
const substitutionTransform = createSubstitutionTransform(env);
|
|
791
|
-
const data = await applyConfigTransforms(configRoot, configYaml, [
|
|
792
|
-
substitutionTransform
|
|
793
|
-
]);
|
|
794
|
-
configs.push({ data, context: configUrl });
|
|
795
|
-
}
|
|
796
|
-
return configs;
|
|
797
|
-
};
|
|
798
|
-
let fileConfigs;
|
|
799
|
-
let loadedPaths;
|
|
800
|
-
try {
|
|
801
|
-
({ fileConfigs, loadedPaths } = await loadConfigFiles());
|
|
802
|
-
} catch (error) {
|
|
803
|
-
throw new errors.ForwardedError("Failed to read static configuration file", error);
|
|
522
|
+
toString() {
|
|
523
|
+
const keys = Object.keys(this.env).filter(
|
|
524
|
+
(key) => key.startsWith("APP_CONFIG_")
|
|
525
|
+
);
|
|
526
|
+
return `EnvConfigSource{count=${keys.length}}`;
|
|
804
527
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
528
|
+
}
|
|
529
|
+
const ENV_PREFIX = "APP_CONFIG_";
|
|
530
|
+
const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;
|
|
531
|
+
function readEnvConfig(env) {
|
|
532
|
+
var _a;
|
|
533
|
+
let data = void 0;
|
|
534
|
+
for (const [name, value] of Object.entries(env)) {
|
|
535
|
+
if (!value) {
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
if (name.startsWith(ENV_PREFIX)) {
|
|
539
|
+
const key = name.replace(ENV_PREFIX, "");
|
|
540
|
+
const keyParts = key.split("_");
|
|
541
|
+
let obj = data = data != null ? data : {};
|
|
542
|
+
for (const [index, part] of keyParts.entries()) {
|
|
543
|
+
if (!CONFIG_KEY_PART_PATTERN.test(part)) {
|
|
544
|
+
throw new TypeError(`Invalid env config key '${key}'`);
|
|
545
|
+
}
|
|
546
|
+
if (index < keyParts.length - 1) {
|
|
547
|
+
obj = obj[part] = (_a = obj[part]) != null ? _a : {};
|
|
548
|
+
if (typeof obj !== "object" || Array.isArray(obj)) {
|
|
549
|
+
const subKey = keyParts.slice(0, index + 1).join("_");
|
|
550
|
+
throw new TypeError(
|
|
551
|
+
`Could not nest config for key '${key}' under existing value '${subKey}'`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
} else {
|
|
555
|
+
if (part in obj) {
|
|
556
|
+
throw new TypeError(
|
|
557
|
+
`Refusing to override existing config at key '${key}'`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
const [, parsedValue] = safeJsonParse(value);
|
|
562
|
+
if (parsedValue === null) {
|
|
563
|
+
throw new Error("value may not be null");
|
|
564
|
+
}
|
|
565
|
+
obj[part] = parsedValue;
|
|
566
|
+
} catch (error) {
|
|
567
|
+
throw new TypeError(
|
|
568
|
+
`Failed to parse JSON-serialized config value for key '${key}', ${error}`
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return data ? [{ data, context: "env" }] : [];
|
|
576
|
+
}
|
|
577
|
+
function safeJsonParse(str) {
|
|
578
|
+
try {
|
|
579
|
+
return [null, JSON.parse(str)];
|
|
580
|
+
} catch (err) {
|
|
581
|
+
errors.assertError(err);
|
|
582
|
+
return [err, str];
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function isObject(obj) {
|
|
587
|
+
if (typeof obj !== "object") {
|
|
588
|
+
return false;
|
|
589
|
+
} else if (Array.isArray(obj)) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
return obj !== null;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function createSubstitutionTransform(env) {
|
|
596
|
+
return async (input) => {
|
|
597
|
+
if (typeof input !== "string") {
|
|
598
|
+
return { applied: false };
|
|
599
|
+
}
|
|
600
|
+
const parts = input.split(/(\$?\$\{[^{}]*\})/);
|
|
601
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
602
|
+
const part = parts[i];
|
|
603
|
+
if (part.startsWith("$$")) {
|
|
604
|
+
parts[i] = part.slice(1);
|
|
605
|
+
} else {
|
|
606
|
+
parts[i] = await env(part.slice(2, -1).trim());
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (parts.some((part) => part === void 0)) {
|
|
610
|
+
return { applied: true, value: void 0 };
|
|
611
|
+
}
|
|
612
|
+
return { applied: true, value: parts.join("") };
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const includeFileParser = {
|
|
617
|
+
".json": async (content) => JSON.parse(content),
|
|
618
|
+
".yaml": async (content) => yaml__default["default"].parse(content),
|
|
619
|
+
".yml": async (content) => yaml__default["default"].parse(content)
|
|
620
|
+
};
|
|
621
|
+
function createIncludeTransform(env, readFile, substitute) {
|
|
622
|
+
return async (input, context) => {
|
|
623
|
+
const { dir } = context;
|
|
624
|
+
if (!dir) {
|
|
625
|
+
throw new Error("Include transform requires a base directory");
|
|
626
|
+
}
|
|
627
|
+
if (!isObject(input)) {
|
|
628
|
+
return { applied: false };
|
|
629
|
+
}
|
|
630
|
+
const [includeKey] = Object.keys(input).filter((key) => key.startsWith("$"));
|
|
631
|
+
if (includeKey) {
|
|
632
|
+
if (Object.keys(input).length !== 1) {
|
|
633
|
+
throw new Error(
|
|
634
|
+
`include key ${includeKey} should not have adjacent keys`
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
} else {
|
|
638
|
+
return { applied: false };
|
|
639
|
+
}
|
|
640
|
+
const rawIncludedValue = input[includeKey];
|
|
641
|
+
if (typeof rawIncludedValue !== "string") {
|
|
642
|
+
throw new Error(`${includeKey} include value is not a string`);
|
|
643
|
+
}
|
|
644
|
+
const substituteResults = await substitute(rawIncludedValue, { dir });
|
|
645
|
+
const includeValue = substituteResults.applied ? substituteResults.value : rawIncludedValue;
|
|
646
|
+
if (includeValue === void 0 || typeof includeValue !== "string") {
|
|
647
|
+
throw new Error(`${includeKey} substitution value was undefined`);
|
|
648
|
+
}
|
|
649
|
+
switch (includeKey) {
|
|
650
|
+
case "$file":
|
|
651
|
+
try {
|
|
652
|
+
const value = await readFile(path.resolve(dir, includeValue));
|
|
653
|
+
return { applied: true, value: value.trimEnd() };
|
|
654
|
+
} catch (error) {
|
|
655
|
+
throw new Error(`failed to read file ${includeValue}, ${error}`);
|
|
656
|
+
}
|
|
657
|
+
case "$env":
|
|
658
|
+
try {
|
|
659
|
+
return { applied: true, value: await env(includeValue) };
|
|
660
|
+
} catch (error) {
|
|
661
|
+
throw new Error(`failed to read env ${includeValue}, ${error}`);
|
|
662
|
+
}
|
|
663
|
+
case "$include": {
|
|
664
|
+
const [filePath, dataPath] = includeValue.split(/#(.*)/);
|
|
665
|
+
const ext = path.extname(filePath);
|
|
666
|
+
const parser = includeFileParser[ext];
|
|
667
|
+
if (!parser) {
|
|
668
|
+
throw new Error(
|
|
669
|
+
`no configuration parser available for included file ${filePath}`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const path$1 = path.resolve(dir, filePath);
|
|
673
|
+
const content = await readFile(path$1);
|
|
674
|
+
const newDir = path.dirname(path$1);
|
|
675
|
+
const parts = dataPath ? dataPath.split(".") : [];
|
|
676
|
+
let value;
|
|
677
|
+
try {
|
|
678
|
+
value = await parser(content);
|
|
679
|
+
} catch (error) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
`failed to parse included file ${filePath}, ${error}`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
for (const [index, part] of parts.entries()) {
|
|
685
|
+
if (!isObject(value)) {
|
|
686
|
+
const errPath = parts.slice(0, index).join(".");
|
|
687
|
+
throw new Error(
|
|
688
|
+
`value at '${errPath}' in included file ${filePath} is not an object`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
value = value[part];
|
|
692
|
+
}
|
|
693
|
+
if (typeof value === "string") {
|
|
694
|
+
const substituted = await substitute(value, { dir: newDir });
|
|
695
|
+
if (substituted.applied) {
|
|
696
|
+
value = substituted.value;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return {
|
|
700
|
+
applied: true,
|
|
701
|
+
value,
|
|
702
|
+
newDir: newDir !== dir ? newDir : void 0
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
default:
|
|
706
|
+
throw new Error(`unknown include ${includeKey}`);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
async function applyConfigTransforms(input, context, transforms) {
|
|
712
|
+
async function transform(inputObj, path, baseDir) {
|
|
713
|
+
var _a;
|
|
714
|
+
let obj = inputObj;
|
|
715
|
+
let dir = baseDir;
|
|
716
|
+
for (const tf of transforms) {
|
|
717
|
+
try {
|
|
718
|
+
const result = await tf(inputObj, { dir });
|
|
719
|
+
if (result.applied) {
|
|
720
|
+
if (result.value === void 0) {
|
|
721
|
+
return void 0;
|
|
722
|
+
}
|
|
723
|
+
obj = result.value;
|
|
724
|
+
dir = (_a = result == null ? void 0 : result.newDir) != null ? _a : dir;
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
} catch (error) {
|
|
728
|
+
errors.assertError(error);
|
|
729
|
+
throw new Error(`error at ${path}, ${error.message}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (typeof obj !== "object") {
|
|
733
|
+
return obj;
|
|
734
|
+
} else if (obj === null) {
|
|
735
|
+
return void 0;
|
|
736
|
+
} else if (Array.isArray(obj)) {
|
|
737
|
+
const arr = new Array();
|
|
738
|
+
for (const [index, value] of obj.entries()) {
|
|
739
|
+
const out2 = await transform(value, `${path}[${index}]`, dir);
|
|
740
|
+
if (out2 !== void 0) {
|
|
741
|
+
arr.push(out2);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return arr;
|
|
745
|
+
}
|
|
746
|
+
const out = {};
|
|
747
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
748
|
+
if (value !== void 0) {
|
|
749
|
+
const result = await transform(value, `${path}.${key}`, dir);
|
|
750
|
+
if (result !== void 0) {
|
|
751
|
+
out[key] = result;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
return out;
|
|
756
|
+
}
|
|
757
|
+
const finalData = await transform(input, "", context == null ? void 0 : context.dir);
|
|
758
|
+
if (!isObject(finalData)) {
|
|
759
|
+
throw new TypeError("expected object at config root");
|
|
815
760
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
761
|
+
return finalData;
|
|
762
|
+
}
|
|
763
|
+
function createConfigTransformer(options) {
|
|
764
|
+
const { substitutionFunc = async (name) => process.env[name], readFile } = options;
|
|
765
|
+
const substitutionTransform = createSubstitutionTransform(substitutionFunc);
|
|
766
|
+
const transforms = [substitutionTransform];
|
|
767
|
+
if (readFile) {
|
|
768
|
+
const includeTransform = createIncludeTransform(
|
|
769
|
+
substitutionFunc,
|
|
770
|
+
readFile,
|
|
771
|
+
substitutionTransform
|
|
772
|
+
);
|
|
773
|
+
transforms.push(includeTransform);
|
|
774
|
+
}
|
|
775
|
+
return async (input, ctx) => applyConfigTransforms(input, ctx != null ? ctx : {}, transforms);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
var __accessCheck$3 = (obj, member, msg) => {
|
|
779
|
+
if (!member.has(obj))
|
|
780
|
+
throw TypeError("Cannot " + msg);
|
|
781
|
+
};
|
|
782
|
+
var __privateGet$2 = (obj, member, getter) => {
|
|
783
|
+
__accessCheck$3(obj, member, "read from private field");
|
|
784
|
+
return getter ? getter.call(obj) : member.get(obj);
|
|
785
|
+
};
|
|
786
|
+
var __privateAdd$3 = (obj, member, value) => {
|
|
787
|
+
if (member.has(obj))
|
|
788
|
+
throw TypeError("Cannot add the same private member more than once");
|
|
789
|
+
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
790
|
+
};
|
|
791
|
+
var __privateSet$2 = (obj, member, value, setter) => {
|
|
792
|
+
__accessCheck$3(obj, member, "write to private field");
|
|
793
|
+
setter ? setter.call(obj, value) : member.set(obj, value);
|
|
794
|
+
return value;
|
|
795
|
+
};
|
|
796
|
+
var __privateMethod$2 = (obj, member, method) => {
|
|
797
|
+
__accessCheck$3(obj, member, "access private method");
|
|
798
|
+
return method;
|
|
799
|
+
};
|
|
800
|
+
var _path, _substitutionFunc, _waitForEvent, waitForEvent_fn;
|
|
801
|
+
async function readFile(path) {
|
|
802
|
+
try {
|
|
803
|
+
return await fs__default["default"].readFile(path, "utf8");
|
|
804
|
+
} catch (error) {
|
|
805
|
+
if (error.code === "ENOENT") {
|
|
806
|
+
return void 0;
|
|
807
|
+
}
|
|
808
|
+
throw error;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const _FileConfigSource = class {
|
|
812
|
+
constructor(options) {
|
|
813
|
+
__privateAdd$3(this, _waitForEvent);
|
|
814
|
+
__privateAdd$3(this, _path, void 0);
|
|
815
|
+
__privateAdd$3(this, _substitutionFunc, void 0);
|
|
816
|
+
__privateSet$2(this, _path, options.path);
|
|
817
|
+
__privateSet$2(this, _substitutionFunc, options.substitutionFunc);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Creates a new config source that loads configuration from the given path.
|
|
821
|
+
*
|
|
822
|
+
* @remarks
|
|
823
|
+
*
|
|
824
|
+
* The source will watch the file for changes, as well as any referenced files.
|
|
825
|
+
*
|
|
826
|
+
* @param options - Options for the config source.
|
|
827
|
+
* @returns A new config source that loads from the given path.
|
|
828
|
+
*/
|
|
829
|
+
static create(options) {
|
|
830
|
+
if (!path.isAbsolute(options.path)) {
|
|
831
|
+
throw new Error(`Config load path is not absolute: "${options.path}"`);
|
|
832
|
+
}
|
|
833
|
+
return new _FileConfigSource(options);
|
|
834
|
+
}
|
|
835
|
+
// Work is duplicated across each read, in practice that should not
|
|
836
|
+
// have any impact since there won't be multiple consumers. If that
|
|
837
|
+
// changes it might be worth refactoring this to avoid duplicate work.
|
|
838
|
+
async *readConfigData(options) {
|
|
839
|
+
const signal = options == null ? void 0 : options.signal;
|
|
840
|
+
const configFileName = path.basename(__privateGet$2(this, _path));
|
|
841
|
+
const watchedPaths = new Array();
|
|
842
|
+
const watcher = chokidar__default["default"].watch(__privateGet$2(this, _path), {
|
|
820
843
|
usePolling: process.env.NODE_ENV === "test"
|
|
821
844
|
});
|
|
822
|
-
|
|
823
|
-
|
|
845
|
+
const dir = path.dirname(__privateGet$2(this, _path));
|
|
846
|
+
const transformer = createConfigTransformer({
|
|
847
|
+
substitutionFunc: __privateGet$2(this, _substitutionFunc),
|
|
848
|
+
readFile: async (path$1) => {
|
|
849
|
+
const fullPath = path.resolve(dir, path$1);
|
|
850
|
+
watcher.add(fullPath);
|
|
851
|
+
watchedPaths.push(fullPath);
|
|
852
|
+
const data = await readFile(fullPath);
|
|
853
|
+
if (data === void 0) {
|
|
854
|
+
throw new errors.NotFoundError(
|
|
855
|
+
`failed to include "${fullPath}", file does not exist`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
return data;
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
const readConfigFile = async () => {
|
|
862
|
+
watcher.unwatch(watchedPaths);
|
|
863
|
+
watchedPaths.length = 0;
|
|
864
|
+
watcher.add(__privateGet$2(this, _path));
|
|
865
|
+
watchedPaths.push(__privateGet$2(this, _path));
|
|
866
|
+
const content = await readFile(__privateGet$2(this, _path));
|
|
867
|
+
if (content === void 0) {
|
|
868
|
+
throw new errors.NotFoundError(`Config file "${__privateGet$2(this, _path)}" does not exist`);
|
|
869
|
+
}
|
|
870
|
+
const parsed = yaml__default["default"].parse(content);
|
|
871
|
+
if (parsed === null) {
|
|
872
|
+
return [];
|
|
873
|
+
}
|
|
824
874
|
try {
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
875
|
+
const data = await transformer(parsed, { dir });
|
|
876
|
+
return [{ data, context: configFileName, path: __privateGet$2(this, _path) }];
|
|
877
|
+
} catch (error) {
|
|
878
|
+
throw new Error(
|
|
879
|
+
`Failed to read config file at "${__privateGet$2(this, _path)}", ${error.message}`
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
const onAbort = () => {
|
|
884
|
+
signal == null ? void 0 : signal.removeEventListener("abort", onAbort);
|
|
885
|
+
watcher.close();
|
|
886
|
+
};
|
|
887
|
+
signal == null ? void 0 : signal.addEventListener("abort", onAbort);
|
|
888
|
+
yield { configs: await readConfigFile() };
|
|
889
|
+
for (; ; ) {
|
|
890
|
+
const event = await __privateMethod$2(this, _waitForEvent, waitForEvent_fn).call(this, watcher, signal);
|
|
891
|
+
if (event === "abort") {
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
yield { configs: await readConfigFile() };
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
toString() {
|
|
898
|
+
return `FileConfigSource{path="${__privateGet$2(this, _path)}"}`;
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
let FileConfigSource = _FileConfigSource;
|
|
902
|
+
_path = new WeakMap();
|
|
903
|
+
_substitutionFunc = new WeakMap();
|
|
904
|
+
_waitForEvent = new WeakSet();
|
|
905
|
+
waitForEvent_fn = function(watcher, signal) {
|
|
906
|
+
return new Promise((resolve) => {
|
|
907
|
+
function onChange() {
|
|
908
|
+
resolve("change");
|
|
909
|
+
onDone();
|
|
910
|
+
}
|
|
911
|
+
function onAbort() {
|
|
912
|
+
resolve("abort");
|
|
913
|
+
onDone();
|
|
914
|
+
}
|
|
915
|
+
function onDone() {
|
|
916
|
+
watcher.removeListener("change", onChange);
|
|
917
|
+
signal == null ? void 0 : signal.removeEventListener("abort", onAbort);
|
|
918
|
+
}
|
|
919
|
+
watcher.addListener("change", onChange);
|
|
920
|
+
signal == null ? void 0 : signal.addEventListener("abort", onAbort);
|
|
921
|
+
});
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
var __accessCheck$2 = (obj, member, msg) => {
|
|
925
|
+
if (!member.has(obj))
|
|
926
|
+
throw TypeError("Cannot " + msg);
|
|
927
|
+
};
|
|
928
|
+
var __privateAdd$2 = (obj, member, value) => {
|
|
929
|
+
if (member.has(obj))
|
|
930
|
+
throw TypeError("Cannot add the same private member more than once");
|
|
931
|
+
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
932
|
+
};
|
|
933
|
+
var __privateMethod$1 = (obj, member, method) => {
|
|
934
|
+
__accessCheck$2(obj, member, "access private method");
|
|
935
|
+
return method;
|
|
936
|
+
};
|
|
937
|
+
var _flattenSources, flattenSources_fn;
|
|
938
|
+
const sourcesSymbol = Symbol.for(
|
|
939
|
+
"@backstage/config-loader#MergedConfigSource.sources"
|
|
940
|
+
);
|
|
941
|
+
const _MergedConfigSource = class {
|
|
942
|
+
constructor(sources) {
|
|
943
|
+
this.sources = sources;
|
|
944
|
+
this[sourcesSymbol] = this.sources;
|
|
945
|
+
}
|
|
946
|
+
static from(sources) {
|
|
947
|
+
return new _MergedConfigSource(__privateMethod$1(this, _flattenSources, flattenSources_fn).call(this, sources));
|
|
948
|
+
}
|
|
949
|
+
async *readConfigData(options) {
|
|
950
|
+
const its = this.sources.map((source) => source.readConfigData(options));
|
|
951
|
+
const initialResults = await Promise.all(its.map((it) => it.next()));
|
|
952
|
+
const configs = initialResults.map((result, i) => {
|
|
953
|
+
if (result.done) {
|
|
954
|
+
throw new Error(
|
|
955
|
+
`Config source ${String(this.sources[i])} returned no data`
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
return result.value.configs;
|
|
959
|
+
});
|
|
960
|
+
yield { configs: configs.flat(1) };
|
|
961
|
+
const results = its.map((it, i) => nextWithIndex(it, i));
|
|
962
|
+
while (results.some(Boolean)) {
|
|
963
|
+
try {
|
|
964
|
+
const [i, result] = await Promise.race(results.filter(Boolean));
|
|
965
|
+
if (result.done) {
|
|
966
|
+
results[i] = void 0;
|
|
967
|
+
} else {
|
|
968
|
+
results[i] = nextWithIndex(its[i], i);
|
|
969
|
+
configs[i] = result.value.configs;
|
|
970
|
+
yield { configs: configs.flat(1) };
|
|
971
|
+
}
|
|
972
|
+
} catch (error) {
|
|
973
|
+
const source = this.sources[error.index];
|
|
974
|
+
if (source) {
|
|
975
|
+
throw new Error(`Config source ${String(source)} failed: ${error}`);
|
|
976
|
+
}
|
|
977
|
+
throw error;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
toString() {
|
|
982
|
+
return `MergedConfigSource{${this.sources.map(String).join(", ")}}`;
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
let MergedConfigSource = _MergedConfigSource;
|
|
986
|
+
_flattenSources = new WeakSet();
|
|
987
|
+
flattenSources_fn = function(sources) {
|
|
988
|
+
return sources.flatMap((source) => {
|
|
989
|
+
if (sourcesSymbol in source && Array.isArray(source[sourcesSymbol])) {
|
|
990
|
+
return __privateMethod$1(this, _flattenSources, flattenSources_fn).call(this, source[sourcesSymbol]);
|
|
991
|
+
}
|
|
992
|
+
return source;
|
|
993
|
+
});
|
|
994
|
+
};
|
|
995
|
+
// An optimization to flatten nested merged sources to avid unnecessary microtasks
|
|
996
|
+
__privateAdd$2(MergedConfigSource, _flattenSources);
|
|
997
|
+
function nextWithIndex(iterator, index) {
|
|
998
|
+
return iterator.next().then(
|
|
999
|
+
(r) => [index, r],
|
|
1000
|
+
(e) => {
|
|
1001
|
+
throw Object.assign(e, { index });
|
|
1002
|
+
}
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
var __accessCheck$1 = (obj, member, msg) => {
|
|
1007
|
+
if (!member.has(obj))
|
|
1008
|
+
throw TypeError("Cannot " + msg);
|
|
1009
|
+
};
|
|
1010
|
+
var __privateGet$1 = (obj, member, getter) => {
|
|
1011
|
+
__accessCheck$1(obj, member, "read from private field");
|
|
1012
|
+
return getter ? getter.call(obj) : member.get(obj);
|
|
1013
|
+
};
|
|
1014
|
+
var __privateAdd$1 = (obj, member, value) => {
|
|
1015
|
+
if (member.has(obj))
|
|
1016
|
+
throw TypeError("Cannot add the same private member more than once");
|
|
1017
|
+
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
1018
|
+
};
|
|
1019
|
+
var __privateSet$1 = (obj, member, value, setter) => {
|
|
1020
|
+
__accessCheck$1(obj, member, "write to private field");
|
|
1021
|
+
setter ? setter.call(obj, value) : member.set(obj, value);
|
|
1022
|
+
return value;
|
|
1023
|
+
};
|
|
1024
|
+
var __privateMethod = (obj, member, method) => {
|
|
1025
|
+
__accessCheck$1(obj, member, "access private method");
|
|
1026
|
+
return method;
|
|
1027
|
+
};
|
|
1028
|
+
var _url, _reloadIntervalMs, _transformer, _load, load_fn, _wait, wait_fn;
|
|
1029
|
+
const DEFAULT_RELOAD_INTERVAL = { seconds: 60 };
|
|
1030
|
+
function durationToMs(duration) {
|
|
1031
|
+
const {
|
|
1032
|
+
years = 0,
|
|
1033
|
+
months = 0,
|
|
1034
|
+
weeks = 0,
|
|
1035
|
+
days = 0,
|
|
1036
|
+
hours = 0,
|
|
1037
|
+
minutes = 0,
|
|
1038
|
+
seconds = 0,
|
|
1039
|
+
milliseconds = 0
|
|
1040
|
+
} = duration;
|
|
1041
|
+
const totalDays = years * 365 + months * 30 + weeks * 7 + days;
|
|
1042
|
+
const totalHours = totalDays * 24 + hours;
|
|
1043
|
+
const totalMinutes = totalHours * 60 + minutes;
|
|
1044
|
+
const totalSeconds = totalMinutes * 60 + seconds;
|
|
1045
|
+
const totalMilliseconds = totalSeconds * 1e3 + milliseconds;
|
|
1046
|
+
return totalMilliseconds;
|
|
1047
|
+
}
|
|
1048
|
+
const _RemoteConfigSource = class {
|
|
1049
|
+
constructor(options) {
|
|
1050
|
+
__privateAdd$1(this, _load);
|
|
1051
|
+
__privateAdd$1(this, _wait);
|
|
1052
|
+
__privateAdd$1(this, _url, void 0);
|
|
1053
|
+
__privateAdd$1(this, _reloadIntervalMs, void 0);
|
|
1054
|
+
__privateAdd$1(this, _transformer, void 0);
|
|
1055
|
+
var _a;
|
|
1056
|
+
__privateSet$1(this, _url, options.url);
|
|
1057
|
+
__privateSet$1(this, _reloadIntervalMs, durationToMs(
|
|
1058
|
+
(_a = options.reloadInterval) != null ? _a : DEFAULT_RELOAD_INTERVAL
|
|
1059
|
+
));
|
|
1060
|
+
__privateSet$1(this, _transformer, createConfigTransformer({
|
|
1061
|
+
substitutionFunc: options.substitutionFunc
|
|
1062
|
+
}));
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Creates a new {@link RemoteConfigSource}.
|
|
1066
|
+
*
|
|
1067
|
+
* @param options - Options for the source.
|
|
1068
|
+
* @returns A new remote config source.
|
|
1069
|
+
*/
|
|
1070
|
+
static create(options) {
|
|
1071
|
+
try {
|
|
1072
|
+
new URL(options.url);
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
throw new Error(
|
|
1075
|
+
`Invalid URL provided to remote config source, '${options.url}', ${error}`
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
return new _RemoteConfigSource(options);
|
|
1079
|
+
}
|
|
1080
|
+
async *readConfigData(options) {
|
|
1081
|
+
var _a;
|
|
1082
|
+
let data = await __privateMethod(this, _load, load_fn).call(this);
|
|
1083
|
+
yield { configs: [{ data, context: __privateGet$1(this, _url) }] };
|
|
1084
|
+
for (; ; ) {
|
|
1085
|
+
await __privateMethod(this, _wait, wait_fn).call(this, options == null ? void 0 : options.signal);
|
|
1086
|
+
if ((_a = options == null ? void 0 : options.signal) == null ? void 0 : _a.aborted) {
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
try {
|
|
1090
|
+
const newData = await __privateMethod(this, _load, load_fn).call(this, options == null ? void 0 : options.signal);
|
|
1091
|
+
if (newData && !isEqual__default["default"](data, newData)) {
|
|
1092
|
+
data = newData;
|
|
1093
|
+
yield { configs: [{ data, context: __privateGet$1(this, _url) }] };
|
|
832
1094
|
}
|
|
833
|
-
currentSerializedConfig = newSerializedConfig;
|
|
834
|
-
watchProp.onChange([...remoteConfigs, ...newConfigs, ...envConfigs]);
|
|
835
1095
|
} catch (error) {
|
|
836
|
-
|
|
1096
|
+
if (error.name !== "AbortError") {
|
|
1097
|
+
console.error(`Failed to read config from ${__privateGet$1(this, _url)}, ${error}`);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
toString() {
|
|
1103
|
+
return `RemoteConfigSource{path="${__privateGet$1(this, _url)}"}`;
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
let RemoteConfigSource = _RemoteConfigSource;
|
|
1107
|
+
_url = new WeakMap();
|
|
1108
|
+
_reloadIntervalMs = new WeakMap();
|
|
1109
|
+
_transformer = new WeakMap();
|
|
1110
|
+
_load = new WeakSet();
|
|
1111
|
+
load_fn = async function(signal) {
|
|
1112
|
+
const res = await fetch__default["default"](__privateGet$1(this, _url), {
|
|
1113
|
+
signal
|
|
1114
|
+
});
|
|
1115
|
+
if (!res.ok) {
|
|
1116
|
+
throw await errors.ResponseError.fromResponse(res);
|
|
1117
|
+
}
|
|
1118
|
+
const content = await res.text();
|
|
1119
|
+
const data = await __privateGet$1(this, _transformer).call(this, yaml__default["default"].parse(content));
|
|
1120
|
+
if (data === null) {
|
|
1121
|
+
throw new Error("configuration data is null");
|
|
1122
|
+
} else if (typeof data !== "object") {
|
|
1123
|
+
throw new Error("configuration data is not an object");
|
|
1124
|
+
} else if (Array.isArray(data)) {
|
|
1125
|
+
throw new Error(
|
|
1126
|
+
"configuration data is an array, expected an object instead"
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
return data;
|
|
1130
|
+
};
|
|
1131
|
+
_wait = new WeakSet();
|
|
1132
|
+
wait_fn = async function(signal) {
|
|
1133
|
+
return new Promise((resolve) => {
|
|
1134
|
+
const timeoutId = setTimeout(onDone, __privateGet$1(this, _reloadIntervalMs));
|
|
1135
|
+
signal == null ? void 0 : signal.addEventListener("abort", onDone);
|
|
1136
|
+
function onDone() {
|
|
1137
|
+
clearTimeout(timeoutId);
|
|
1138
|
+
signal == null ? void 0 : signal.removeEventListener("abort", onDone);
|
|
1139
|
+
resolve();
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
};
|
|
1143
|
+
|
|
1144
|
+
class ObservableConfigProxy {
|
|
1145
|
+
constructor(parent, parentKey, abortController) {
|
|
1146
|
+
this.parent = parent;
|
|
1147
|
+
this.parentKey = parentKey;
|
|
1148
|
+
this.abortController = abortController;
|
|
1149
|
+
this.config = new config.ConfigReader({});
|
|
1150
|
+
this.subscribers = [];
|
|
1151
|
+
if (parent && !parentKey) {
|
|
1152
|
+
throw new Error("parentKey is required if parent is set");
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
static create(abortController) {
|
|
1156
|
+
return new ObservableConfigProxy(void 0, void 0, abortController);
|
|
1157
|
+
}
|
|
1158
|
+
setConfig(config) {
|
|
1159
|
+
if (this.parent) {
|
|
1160
|
+
throw new Error("immutable");
|
|
1161
|
+
}
|
|
1162
|
+
this.config = config;
|
|
1163
|
+
for (const subscriber of this.subscribers) {
|
|
1164
|
+
try {
|
|
1165
|
+
subscriber();
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
console.error(`Config subscriber threw error, ${error}`);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
close() {
|
|
1172
|
+
if (!this.abortController) {
|
|
1173
|
+
throw new Error("Only the root config can be closed");
|
|
1174
|
+
}
|
|
1175
|
+
this.abortController.abort();
|
|
1176
|
+
}
|
|
1177
|
+
subscribe(onChange) {
|
|
1178
|
+
if (this.parent) {
|
|
1179
|
+
return this.parent.subscribe(onChange);
|
|
1180
|
+
}
|
|
1181
|
+
this.subscribers.push(onChange);
|
|
1182
|
+
return {
|
|
1183
|
+
unsubscribe: () => {
|
|
1184
|
+
const index = this.subscribers.indexOf(onChange);
|
|
1185
|
+
if (index >= 0) {
|
|
1186
|
+
this.subscribers.splice(index, 1);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
select(required) {
|
|
1192
|
+
var _a;
|
|
1193
|
+
if (this.parent && this.parentKey) {
|
|
1194
|
+
if (required) {
|
|
1195
|
+
return this.parent.select(true).getConfig(this.parentKey);
|
|
1196
|
+
}
|
|
1197
|
+
return (_a = this.parent.select(false)) == null ? void 0 : _a.getOptionalConfig(this.parentKey);
|
|
1198
|
+
}
|
|
1199
|
+
return this.config;
|
|
1200
|
+
}
|
|
1201
|
+
has(key) {
|
|
1202
|
+
var _a, _b;
|
|
1203
|
+
return (_b = (_a = this.select(false)) == null ? void 0 : _a.has(key)) != null ? _b : false;
|
|
1204
|
+
}
|
|
1205
|
+
keys() {
|
|
1206
|
+
var _a, _b;
|
|
1207
|
+
return (_b = (_a = this.select(false)) == null ? void 0 : _a.keys()) != null ? _b : [];
|
|
1208
|
+
}
|
|
1209
|
+
get(key) {
|
|
1210
|
+
return this.select(true).get(key);
|
|
1211
|
+
}
|
|
1212
|
+
getOptional(key) {
|
|
1213
|
+
var _a;
|
|
1214
|
+
return (_a = this.select(false)) == null ? void 0 : _a.getOptional(key);
|
|
1215
|
+
}
|
|
1216
|
+
getConfig(key) {
|
|
1217
|
+
return new ObservableConfigProxy(this, key);
|
|
1218
|
+
}
|
|
1219
|
+
getOptionalConfig(key) {
|
|
1220
|
+
var _a;
|
|
1221
|
+
if ((_a = this.select(false)) == null ? void 0 : _a.has(key)) {
|
|
1222
|
+
return new ObservableConfigProxy(this, key);
|
|
1223
|
+
}
|
|
1224
|
+
return void 0;
|
|
1225
|
+
}
|
|
1226
|
+
getConfigArray(key) {
|
|
1227
|
+
return this.select(true).getConfigArray(key);
|
|
1228
|
+
}
|
|
1229
|
+
getOptionalConfigArray(key) {
|
|
1230
|
+
var _a;
|
|
1231
|
+
return (_a = this.select(false)) == null ? void 0 : _a.getOptionalConfigArray(key);
|
|
1232
|
+
}
|
|
1233
|
+
getNumber(key) {
|
|
1234
|
+
return this.select(true).getNumber(key);
|
|
1235
|
+
}
|
|
1236
|
+
getOptionalNumber(key) {
|
|
1237
|
+
var _a;
|
|
1238
|
+
return (_a = this.select(false)) == null ? void 0 : _a.getOptionalNumber(key);
|
|
1239
|
+
}
|
|
1240
|
+
getBoolean(key) {
|
|
1241
|
+
return this.select(true).getBoolean(key);
|
|
1242
|
+
}
|
|
1243
|
+
getOptionalBoolean(key) {
|
|
1244
|
+
var _a;
|
|
1245
|
+
return (_a = this.select(false)) == null ? void 0 : _a.getOptionalBoolean(key);
|
|
1246
|
+
}
|
|
1247
|
+
getString(key) {
|
|
1248
|
+
return this.select(true).getString(key);
|
|
1249
|
+
}
|
|
1250
|
+
getOptionalString(key) {
|
|
1251
|
+
var _a;
|
|
1252
|
+
return (_a = this.select(false)) == null ? void 0 : _a.getOptionalString(key);
|
|
1253
|
+
}
|
|
1254
|
+
getStringArray(key) {
|
|
1255
|
+
return this.select(true).getStringArray(key);
|
|
1256
|
+
}
|
|
1257
|
+
getOptionalStringArray(key) {
|
|
1258
|
+
var _a;
|
|
1259
|
+
return (_a = this.select(false)) == null ? void 0 : _a.getOptionalStringArray(key);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
class ConfigSources {
|
|
1264
|
+
/**
|
|
1265
|
+
* Parses command line arguments and returns the config targets.
|
|
1266
|
+
*
|
|
1267
|
+
* @param argv - The command line arguments to parse. Defaults to `process.argv`
|
|
1268
|
+
* @returns A list of config targets
|
|
1269
|
+
*/
|
|
1270
|
+
static parseArgs(argv = process.argv) {
|
|
1271
|
+
const args = [parseArgs__default["default"](argv).config].flat().filter(Boolean);
|
|
1272
|
+
return args.map((target) => {
|
|
1273
|
+
try {
|
|
1274
|
+
const url = new URL(target);
|
|
1275
|
+
if (!url.host) {
|
|
1276
|
+
return { type: "path", target };
|
|
1277
|
+
}
|
|
1278
|
+
return { type: "url", target };
|
|
1279
|
+
} catch {
|
|
1280
|
+
return { type: "path", target };
|
|
837
1281
|
}
|
|
838
1282
|
});
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Creates the default config sources for the provided targets.
|
|
1286
|
+
*
|
|
1287
|
+
* @remarks
|
|
1288
|
+
*
|
|
1289
|
+
* This will create {@link FileConfigSource}s and {@link RemoteConfigSource}s
|
|
1290
|
+
* for the provided targets, and merge them together to a single source.
|
|
1291
|
+
* If no targets are provided it will fall back to `app-config.yaml` and
|
|
1292
|
+
* `app-config.local.yaml`.
|
|
1293
|
+
*
|
|
1294
|
+
* URL targets are only supported if the `remote` option is provided.
|
|
1295
|
+
*
|
|
1296
|
+
* @param options - Options
|
|
1297
|
+
* @returns A config source for the provided targets
|
|
1298
|
+
*/
|
|
1299
|
+
static defaultForTargets(options) {
|
|
1300
|
+
var _a;
|
|
1301
|
+
const rootDir = (_a = options.rootDir) != null ? _a : cliCommon.findPaths(process.cwd()).targetRoot;
|
|
1302
|
+
const argSources = options.targets.map((arg) => {
|
|
1303
|
+
if (arg.type === "url") {
|
|
1304
|
+
if (!options.remote) {
|
|
1305
|
+
throw new Error(
|
|
1306
|
+
`Config argument "${arg.target}" looks like a URL but remote configuration is not enabled. Enable it by passing the \`remote\` option`
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
return RemoteConfigSource.create({
|
|
1310
|
+
url: arg.target,
|
|
1311
|
+
substitutionFunc: options.substitutionFunc,
|
|
1312
|
+
reloadInterval: options.remote.reloadInterval
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
return FileConfigSource.create({
|
|
1316
|
+
path: arg.target,
|
|
1317
|
+
substitutionFunc: options.substitutionFunc
|
|
842
1318
|
});
|
|
1319
|
+
});
|
|
1320
|
+
if (argSources.length === 0) {
|
|
1321
|
+
const defaultPath = path.resolve(rootDir, "app-config.yaml");
|
|
1322
|
+
const localPath = path.resolve(rootDir, "app-config.local.yaml");
|
|
1323
|
+
argSources.push(
|
|
1324
|
+
FileConfigSource.create({
|
|
1325
|
+
path: defaultPath,
|
|
1326
|
+
substitutionFunc: options.substitutionFunc
|
|
1327
|
+
})
|
|
1328
|
+
);
|
|
1329
|
+
if (fs__default["default"].pathExistsSync(localPath)) {
|
|
1330
|
+
argSources.push(
|
|
1331
|
+
FileConfigSource.create({
|
|
1332
|
+
path: localPath,
|
|
1333
|
+
substitutionFunc: options.substitutionFunc
|
|
1334
|
+
})
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
843
1337
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
1338
|
+
return this.merge(argSources);
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Creates the default config source for Backstage.
|
|
1342
|
+
*
|
|
1343
|
+
* @remarks
|
|
1344
|
+
*
|
|
1345
|
+
* This will read from `app-config.yaml` and `app-config.local.yaml` by
|
|
1346
|
+
* default, as well as environment variables prefixed with `APP_CONFIG_`.
|
|
1347
|
+
* If `--config <path|url>` command line arguments are passed, these will
|
|
1348
|
+
* override the default configuration file paths. URLs are only supported
|
|
1349
|
+
* if the `remote` option is provided.
|
|
1350
|
+
*
|
|
1351
|
+
* @param options - Options
|
|
1352
|
+
* @returns The default Backstage config source
|
|
1353
|
+
*/
|
|
1354
|
+
static default(options) {
|
|
1355
|
+
const argSource = this.defaultForTargets({
|
|
1356
|
+
...options,
|
|
1357
|
+
targets: this.parseArgs(options.argv)
|
|
1358
|
+
});
|
|
1359
|
+
const envSource = EnvConfigSource.create({
|
|
1360
|
+
env: options.env
|
|
1361
|
+
});
|
|
1362
|
+
return this.merge([argSource, envSource]);
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Merges multiple config sources into a single source that reads from all
|
|
1366
|
+
* sources and concatenates the result.
|
|
1367
|
+
*
|
|
1368
|
+
* @param sources - The config sources to merge
|
|
1369
|
+
* @returns A single config source that concatenates the data from the given sources
|
|
1370
|
+
*/
|
|
1371
|
+
static merge(sources) {
|
|
1372
|
+
return MergedConfigSource.from(sources);
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Creates an observable {@link @backstage/config#Config} implementation from a {@link ConfigSource}.
|
|
1376
|
+
*
|
|
1377
|
+
* @remarks
|
|
1378
|
+
*
|
|
1379
|
+
* If you only want to read the config once you can close the returned config immediately.
|
|
1380
|
+
*
|
|
1381
|
+
* @example
|
|
1382
|
+
*
|
|
1383
|
+
* ```ts
|
|
1384
|
+
* const sources = ConfigSources.default(...)
|
|
1385
|
+
* const config = await ConfigSources.toConfig(source)
|
|
1386
|
+
* config.close()
|
|
1387
|
+
* const example = config.getString(...)
|
|
1388
|
+
* ```
|
|
1389
|
+
*
|
|
1390
|
+
* @param source - The config source to read from
|
|
1391
|
+
* @returns A promise that resolves to a closable config
|
|
1392
|
+
*/
|
|
1393
|
+
static toConfig(source) {
|
|
1394
|
+
return new Promise(async (resolve, reject) => {
|
|
1395
|
+
let config$1 = void 0;
|
|
1396
|
+
try {
|
|
1397
|
+
const abortController = new AbortController();
|
|
1398
|
+
for await (const { configs } of source.readConfigData({
|
|
1399
|
+
signal: abortController.signal
|
|
1400
|
+
})) {
|
|
1401
|
+
if (config$1) {
|
|
1402
|
+
config$1.setConfig(config.ConfigReader.fromConfigs(configs));
|
|
1403
|
+
} else {
|
|
1404
|
+
config$1 = ObservableConfigProxy.create(abortController);
|
|
1405
|
+
config$1.setConfig(config.ConfigReader.fromConfigs(configs));
|
|
1406
|
+
resolve(config$1);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
reject(error);
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
function simpleDefer() {
|
|
1417
|
+
let resolve;
|
|
1418
|
+
const promise = new Promise((_resolve) => {
|
|
1419
|
+
resolve = _resolve;
|
|
1420
|
+
});
|
|
1421
|
+
return { promise, resolve };
|
|
1422
|
+
}
|
|
1423
|
+
async function waitOrAbort(promise, signal) {
|
|
1424
|
+
const signals = [signal].flat().filter((x) => !!x);
|
|
1425
|
+
return new Promise((resolve, reject) => {
|
|
1426
|
+
if (signals.some((s) => s.aborted)) {
|
|
1427
|
+
resolve([false]);
|
|
1428
|
+
}
|
|
1429
|
+
const onAbort = () => {
|
|
1430
|
+
resolve([false]);
|
|
848
1431
|
};
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1432
|
+
promise.then(
|
|
1433
|
+
(value) => {
|
|
1434
|
+
resolve([true, value]);
|
|
1435
|
+
signals.forEach((s) => s.removeEventListener("abort", onAbort));
|
|
1436
|
+
},
|
|
1437
|
+
(error) => {
|
|
1438
|
+
reject(error);
|
|
1439
|
+
signals.forEach((s) => s.removeEventListener("abort", onAbort));
|
|
1440
|
+
}
|
|
1441
|
+
);
|
|
1442
|
+
signals.forEach((s) => s.addEventListener("abort", onAbort));
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
var __accessCheck = (obj, member, msg) => {
|
|
1447
|
+
if (!member.has(obj))
|
|
1448
|
+
throw TypeError("Cannot " + msg);
|
|
1449
|
+
};
|
|
1450
|
+
var __privateGet = (obj, member, getter) => {
|
|
1451
|
+
__accessCheck(obj, member, "read from private field");
|
|
1452
|
+
return getter ? getter.call(obj) : member.get(obj);
|
|
1453
|
+
};
|
|
1454
|
+
var __privateAdd = (obj, member, value) => {
|
|
1455
|
+
if (member.has(obj))
|
|
1456
|
+
throw TypeError("Cannot add the same private member more than once");
|
|
1457
|
+
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
1458
|
+
};
|
|
1459
|
+
var __privateSet = (obj, member, value, setter) => {
|
|
1460
|
+
__accessCheck(obj, member, "write to private field");
|
|
1461
|
+
setter ? setter.call(obj, value) : member.set(obj, value);
|
|
1462
|
+
return value;
|
|
1463
|
+
};
|
|
1464
|
+
var _currentData, _deferred, _context, _abortController;
|
|
1465
|
+
const _MutableConfigSource = class {
|
|
1466
|
+
constructor(context, initialData) {
|
|
1467
|
+
__privateAdd(this, _currentData, void 0);
|
|
1468
|
+
__privateAdd(this, _deferred, void 0);
|
|
1469
|
+
__privateAdd(this, _context, void 0);
|
|
1470
|
+
__privateAdd(this, _abortController, new AbortController());
|
|
1471
|
+
__privateSet(this, _currentData, initialData);
|
|
1472
|
+
__privateSet(this, _context, context);
|
|
1473
|
+
__privateSet(this, _deferred, simpleDefer());
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Creates a new mutable config source.
|
|
1477
|
+
*
|
|
1478
|
+
* @param options - Options for the config source.
|
|
1479
|
+
* @returns A new mutable config source.
|
|
1480
|
+
*/
|
|
1481
|
+
static create(options) {
|
|
1482
|
+
var _a;
|
|
1483
|
+
return new _MutableConfigSource(
|
|
1484
|
+
(_a = options == null ? void 0 : options.context) != null ? _a : "mutable-config",
|
|
1485
|
+
options == null ? void 0 : options.data
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
async *readConfigData(options) {
|
|
1489
|
+
let deferredPromise = __privateGet(this, _deferred).promise;
|
|
1490
|
+
if (__privateGet(this, _currentData) !== void 0) {
|
|
1491
|
+
yield { configs: [{ data: __privateGet(this, _currentData), context: __privateGet(this, _context) }] };
|
|
1492
|
+
}
|
|
1493
|
+
for (; ; ) {
|
|
1494
|
+
const [ok] = await waitOrAbort(deferredPromise, [
|
|
1495
|
+
options == null ? void 0 : options.signal,
|
|
1496
|
+
__privateGet(this, _abortController).signal
|
|
1497
|
+
]);
|
|
1498
|
+
if (!ok) {
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
deferredPromise = __privateGet(this, _deferred).promise;
|
|
1502
|
+
if (__privateGet(this, _currentData) !== void 0) {
|
|
1503
|
+
yield {
|
|
1504
|
+
configs: [{ data: __privateGet(this, _currentData), context: __privateGet(this, _context) }]
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Set the data of the config source.
|
|
1511
|
+
*
|
|
1512
|
+
* @param data - The new data to set
|
|
1513
|
+
*/
|
|
1514
|
+
setData(data) {
|
|
1515
|
+
if (!__privateGet(this, _abortController).signal.aborted) {
|
|
1516
|
+
__privateSet(this, _currentData, data);
|
|
1517
|
+
const oldDeferred = __privateGet(this, _deferred);
|
|
1518
|
+
__privateSet(this, _deferred, simpleDefer());
|
|
1519
|
+
oldDeferred.resolve();
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
/**
|
|
1523
|
+
* Close the config source, preventing any further updates.
|
|
1524
|
+
*/
|
|
1525
|
+
close() {
|
|
1526
|
+
__privateSet(this, _currentData, void 0);
|
|
1527
|
+
__privateGet(this, _abortController).abort();
|
|
1528
|
+
}
|
|
1529
|
+
toString() {
|
|
1530
|
+
return `MutableConfigSource{}`;
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
let MutableConfigSource = _MutableConfigSource;
|
|
1534
|
+
_currentData = new WeakMap();
|
|
1535
|
+
_deferred = new WeakMap();
|
|
1536
|
+
_context = new WeakMap();
|
|
1537
|
+
_abortController = new WeakMap();
|
|
1538
|
+
|
|
1539
|
+
class StaticObservableConfigSource {
|
|
1540
|
+
constructor(data, context) {
|
|
1541
|
+
this.data = data;
|
|
1542
|
+
this.context = context;
|
|
1543
|
+
}
|
|
1544
|
+
async *readConfigData(options) {
|
|
1545
|
+
const queue = new Array();
|
|
1546
|
+
let deferred = simpleDefer();
|
|
1547
|
+
const sub = this.data.subscribe({
|
|
1548
|
+
next(value) {
|
|
1549
|
+
queue.push(value);
|
|
1550
|
+
deferred.resolve();
|
|
1551
|
+
deferred = simpleDefer();
|
|
1552
|
+
},
|
|
1553
|
+
complete() {
|
|
1554
|
+
deferred.resolve();
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
const signal = options == null ? void 0 : options.signal;
|
|
1558
|
+
if (signal) {
|
|
1559
|
+
const onAbort = () => {
|
|
1560
|
+
sub.unsubscribe();
|
|
1561
|
+
queue.length = 0;
|
|
1562
|
+
deferred.resolve();
|
|
1563
|
+
signal.removeEventListener("abort", onAbort);
|
|
1564
|
+
};
|
|
1565
|
+
signal.addEventListener("abort", onAbort);
|
|
1566
|
+
}
|
|
1567
|
+
for (; ; ) {
|
|
1568
|
+
await deferred.promise;
|
|
1569
|
+
if (queue.length === 0) {
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
while (queue.length > 0) {
|
|
1573
|
+
yield { configs: [{ data: queue.shift(), context: this.context }] };
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
function isObservable(value) {
|
|
1579
|
+
return "subscribe" in value && typeof value.subscribe === "function";
|
|
1580
|
+
}
|
|
1581
|
+
function isAsyncIterable(value) {
|
|
1582
|
+
return Symbol.asyncIterator in value;
|
|
1583
|
+
}
|
|
1584
|
+
class StaticConfigSource {
|
|
1585
|
+
constructor(promise, context) {
|
|
1586
|
+
this.promise = promise;
|
|
1587
|
+
this.context = context;
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Creates a new {@link StaticConfigSource}.
|
|
1591
|
+
*
|
|
1592
|
+
* @param options - Options for the config source
|
|
1593
|
+
* @returns A new static config source
|
|
1594
|
+
*/
|
|
1595
|
+
static create(options) {
|
|
1596
|
+
const { data, context = "static-config" } = options;
|
|
1597
|
+
if (!data) {
|
|
1598
|
+
return {
|
|
1599
|
+
async *readConfigData() {
|
|
1600
|
+
yield { configs: [] };
|
|
1601
|
+
return;
|
|
856
1602
|
}
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
if (isObservable(data)) {
|
|
1606
|
+
return new StaticObservableConfigSource(data, context);
|
|
860
1607
|
}
|
|
861
|
-
if (
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1608
|
+
if (isAsyncIterable(data)) {
|
|
1609
|
+
return {
|
|
1610
|
+
async *readConfigData() {
|
|
1611
|
+
for await (const value of data) {
|
|
1612
|
+
yield { configs: [{ data: value, context }] };
|
|
1613
|
+
}
|
|
866
1614
|
}
|
|
867
|
-
}
|
|
1615
|
+
};
|
|
868
1616
|
}
|
|
869
|
-
|
|
870
|
-
if (watch) {
|
|
871
|
-
watchConfigFile(watch);
|
|
1617
|
+
return new StaticConfigSource(data, context);
|
|
872
1618
|
}
|
|
873
|
-
|
|
874
|
-
|
|
1619
|
+
async *readConfigData() {
|
|
1620
|
+
yield { configs: [{ data: await this.promise, context: this.context }] };
|
|
1621
|
+
return;
|
|
875
1622
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
}
|
|
1623
|
+
toString() {
|
|
1624
|
+
return `StaticConfigSource{}`;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
async function loadConfig(options) {
|
|
1629
|
+
const source = ConfigSources.default({
|
|
1630
|
+
substitutionFunc: options.experimentalEnvFunc,
|
|
1631
|
+
remote: options.remote && {
|
|
1632
|
+
reloadInterval: { seconds: options.remote.reloadIntervalSeconds }
|
|
1633
|
+
},
|
|
1634
|
+
rootDir: options.configRoot,
|
|
1635
|
+
argv: options.configTargets.flatMap((t) => [
|
|
1636
|
+
"--config",
|
|
1637
|
+
"url" in t ? t.url : t.path
|
|
1638
|
+
])
|
|
1639
|
+
});
|
|
1640
|
+
return new Promise((resolve, reject) => {
|
|
1641
|
+
async function loadConfigReaderLoop() {
|
|
1642
|
+
var _a, _b, _c, _d;
|
|
1643
|
+
let loaded = false;
|
|
1644
|
+
try {
|
|
1645
|
+
const abortController = new AbortController();
|
|
1646
|
+
(_b = (_a = options.watch) == null ? void 0 : _a.stopSignal) == null ? void 0 : _b.then(() => abortController.abort());
|
|
1647
|
+
for await (const { configs } of source.readConfigData({
|
|
1648
|
+
signal: abortController.signal
|
|
1649
|
+
})) {
|
|
1650
|
+
if (loaded) {
|
|
1651
|
+
(_c = options.watch) == null ? void 0 : _c.onChange(configs);
|
|
1652
|
+
} else {
|
|
1653
|
+
resolve({ appConfigs: configs });
|
|
1654
|
+
loaded = true;
|
|
1655
|
+
if (options.watch) {
|
|
1656
|
+
(_d = options.watch.stopSignal) == null ? void 0 : _d.then(() => abortController.abort());
|
|
1657
|
+
} else {
|
|
1658
|
+
abortController.abort();
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
} catch (error) {
|
|
1663
|
+
if (loaded) {
|
|
1664
|
+
console.error(`Failed to reload configuration, ${error}`);
|
|
1665
|
+
} else {
|
|
1666
|
+
reject(error);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
loadConfigReaderLoop();
|
|
1671
|
+
});
|
|
879
1672
|
}
|
|
880
1673
|
|
|
1674
|
+
exports.ConfigSources = ConfigSources;
|
|
1675
|
+
exports.EnvConfigSource = EnvConfigSource;
|
|
1676
|
+
exports.FileConfigSource = FileConfigSource;
|
|
1677
|
+
exports.MutableConfigSource = MutableConfigSource;
|
|
1678
|
+
exports.RemoteConfigSource = RemoteConfigSource;
|
|
1679
|
+
exports.StaticConfigSource = StaticConfigSource;
|
|
881
1680
|
exports.loadConfig = loadConfig;
|
|
882
1681
|
exports.loadConfigSchema = loadConfigSchema;
|
|
883
1682
|
exports.mergeConfigSchemas = mergeConfigSchemas;
|