@backstage/config-loader 0.7.2 → 0.9.1
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 +45 -0
- package/dist/index.cjs.js +56 -50
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +13 -9
- package/dist/index.esm.js +42 -36
- package/dist/index.esm.js.map +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# @backstage/config-loader
|
|
2
2
|
|
|
3
|
+
## 0.9.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 84663d59a3: Bump `typescript-json-schema` from `^0.51.0` to `^0.52.0`.
|
|
8
|
+
|
|
9
|
+
## 0.9.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- f6722d2458: Removed deprecated option `env` from `LoadConfigOptions` and associated tests
|
|
14
|
+
- 67d6cb3c7e: Removed deprecated option `configPaths` as it has been superseded by `configTargets`
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 1e7070443d: In case remote.reloadIntervalSeconds is passed, it must be a valid positive value
|
|
19
|
+
|
|
20
|
+
## 0.8.1
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- b055a6addc: Align on usage of `cross-fetch` vs `node-fetch` in frontend vs backend packages, and remove some unnecessary imports of either one of them
|
|
25
|
+
- 4bea7b81d3: Uses key visibility as fallback in non-object arrays
|
|
26
|
+
|
|
27
|
+
## 0.8.0
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- 1e99c73c75: Update `loadConfig` to return `LoadConfigResult` instead of an array of `AppConfig`.
|
|
32
|
+
|
|
33
|
+
This function is primarily used internally by other config loaders like `loadBackendConfig` which means no changes are required for most users.
|
|
34
|
+
|
|
35
|
+
If you use `loadConfig` directly you will need to update your usage from:
|
|
36
|
+
|
|
37
|
+
```diff
|
|
38
|
+
- const appConfigs = await loadConfig(options)
|
|
39
|
+
+ const { appConfigs } = await loadConfig(options)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- 8809b6c0dd: Update the json-schema dependency version.
|
|
45
|
+
- Updated dependencies
|
|
46
|
+
- @backstage/cli-common@0.1.6
|
|
47
|
+
|
|
3
48
|
## 0.7.2
|
|
4
49
|
|
|
5
50
|
### Patch Changes
|
package/dist/index.cjs.js
CHANGED
|
@@ -64,7 +64,7 @@ function readEnvConfig(env) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
return data ? [{data, context: "env"}] : [];
|
|
67
|
+
return data ? [{ data, context: "env" }] : [];
|
|
68
68
|
}
|
|
69
69
|
function safeJsonParse(str) {
|
|
70
70
|
try {
|
|
@@ -139,13 +139,13 @@ async function applyConfigTransforms(initialDir, input, transforms) {
|
|
|
139
139
|
|
|
140
140
|
const includeFileParser = {
|
|
141
141
|
".json": async (content) => JSON.parse(content),
|
|
142
|
-
".yaml": async (content) => yaml__default[
|
|
143
|
-
".yml": async (content) => yaml__default[
|
|
142
|
+
".yaml": async (content) => yaml__default["default"].parse(content),
|
|
143
|
+
".yml": async (content) => yaml__default["default"].parse(content)
|
|
144
144
|
};
|
|
145
145
|
function createIncludeTransform(env, readFile, substitute) {
|
|
146
146
|
return async (input, baseDir) => {
|
|
147
147
|
if (!isObject(input)) {
|
|
148
|
-
return {applied: false};
|
|
148
|
+
return { applied: false };
|
|
149
149
|
}
|
|
150
150
|
const [includeKey] = Object.keys(input).filter((key) => key.startsWith("$"));
|
|
151
151
|
if (includeKey) {
|
|
@@ -153,7 +153,7 @@ function createIncludeTransform(env, readFile, substitute) {
|
|
|
153
153
|
throw new Error(`include key ${includeKey} should not have adjacent keys`);
|
|
154
154
|
}
|
|
155
155
|
} else {
|
|
156
|
-
return {applied: false};
|
|
156
|
+
return { applied: false };
|
|
157
157
|
}
|
|
158
158
|
const rawIncludedValue = input[includeKey];
|
|
159
159
|
if (typeof rawIncludedValue !== "string") {
|
|
@@ -168,13 +168,13 @@ function createIncludeTransform(env, readFile, substitute) {
|
|
|
168
168
|
case "$file":
|
|
169
169
|
try {
|
|
170
170
|
const value = await readFile(path.resolve(baseDir, includeValue));
|
|
171
|
-
return {applied: true, value};
|
|
171
|
+
return { applied: true, value };
|
|
172
172
|
} catch (error) {
|
|
173
173
|
throw new Error(`failed to read file ${includeValue}, ${error}`);
|
|
174
174
|
}
|
|
175
175
|
case "$env":
|
|
176
176
|
try {
|
|
177
|
-
return {applied: true, value: await env(includeValue)};
|
|
177
|
+
return { applied: true, value: await env(includeValue) };
|
|
178
178
|
} catch (error) {
|
|
179
179
|
throw new Error(`failed to read env ${includeValue}, ${error}`);
|
|
180
180
|
}
|
|
@@ -217,7 +217,7 @@ function createIncludeTransform(env, readFile, substitute) {
|
|
|
217
217
|
function createSubstitutionTransform(env) {
|
|
218
218
|
return async (input) => {
|
|
219
219
|
if (typeof input !== "string") {
|
|
220
|
-
return {applied: false};
|
|
220
|
+
return { applied: false };
|
|
221
221
|
}
|
|
222
222
|
const parts = input.split(/(\$?\$\{[^{}]*\})/);
|
|
223
223
|
for (let i = 1; i < parts.length; i += 2) {
|
|
@@ -229,9 +229,9 @@ function createSubstitutionTransform(env) {
|
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
if (parts.some((part) => part === void 0)) {
|
|
232
|
-
return {applied: true, value: void 0};
|
|
232
|
+
return { applied: true, value: void 0 };
|
|
233
233
|
}
|
|
234
|
-
return {applied: true, value: parts.join("")};
|
|
234
|
+
return { applied: true, value: parts.join("") };
|
|
235
235
|
};
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -239,8 +239,8 @@ const CONFIG_VISIBILITIES = ["frontend", "backend", "secret"];
|
|
|
239
239
|
const DEFAULT_CONFIG_VISIBILITY = "backend";
|
|
240
240
|
|
|
241
241
|
function compileConfigSchemas(schemas) {
|
|
242
|
-
const visibilityByDataPath = new Map();
|
|
243
|
-
const ajv = new Ajv__default[
|
|
242
|
+
const visibilityByDataPath = /* @__PURE__ */ new Map();
|
|
243
|
+
const ajv = new Ajv__default["default"]({
|
|
244
244
|
allErrors: true,
|
|
245
245
|
allowUnionTypes: true,
|
|
246
246
|
schemas: {
|
|
@@ -274,8 +274,8 @@ function compileConfigSchemas(schemas) {
|
|
|
274
274
|
}
|
|
275
275
|
const merged = mergeConfigSchemas(schemas.map((_) => _.value));
|
|
276
276
|
const validate = ajv.compile(merged);
|
|
277
|
-
const visibilityBySchemaPath = new Map();
|
|
278
|
-
traverse__default[
|
|
277
|
+
const visibilityBySchemaPath = /* @__PURE__ */ new Map();
|
|
278
|
+
traverse__default["default"](merged, (schema, path) => {
|
|
279
279
|
if (schema.visibility && schema.visibility !== "backend") {
|
|
280
280
|
visibilityBySchemaPath.set(path, schema.visibility);
|
|
281
281
|
}
|
|
@@ -299,7 +299,7 @@ function compileConfigSchemas(schemas) {
|
|
|
299
299
|
};
|
|
300
300
|
}
|
|
301
301
|
function mergeConfigSchemas(schemas) {
|
|
302
|
-
const merged = mergeAllOf__default[
|
|
302
|
+
const merged = mergeAllOf__default["default"]({ allOf: schemas }, {
|
|
303
303
|
ignoreAdditionalProperties: true,
|
|
304
304
|
resolvers: {
|
|
305
305
|
visibility(values, path) {
|
|
@@ -323,18 +323,18 @@ const req = typeof __non_webpack_require__ === "undefined" ? require : __non_web
|
|
|
323
323
|
async function collectConfigSchemas(packageNames, packagePaths) {
|
|
324
324
|
const schemas = new Array();
|
|
325
325
|
const tsSchemaPaths = new Array();
|
|
326
|
-
const visitedPackageVersions = new Map();
|
|
327
|
-
const currentDir = await fs__default[
|
|
326
|
+
const visitedPackageVersions = /* @__PURE__ */ new Map();
|
|
327
|
+
const currentDir = await fs__default["default"].realpath(process.cwd());
|
|
328
328
|
async function processItem(item) {
|
|
329
329
|
var _a, _b, _c, _d;
|
|
330
330
|
let pkgPath = item.packagePath;
|
|
331
331
|
if (pkgPath) {
|
|
332
|
-
const pkgExists = await fs__default[
|
|
332
|
+
const pkgExists = await fs__default["default"].pathExists(pkgPath);
|
|
333
333
|
if (!pkgExists) {
|
|
334
334
|
return;
|
|
335
335
|
}
|
|
336
336
|
} else if (item.name) {
|
|
337
|
-
const {name, parentPath} = item;
|
|
337
|
+
const { name, parentPath } = item;
|
|
338
338
|
try {
|
|
339
339
|
pkgPath = req.resolve(`${name}/package.json`, parentPath && {
|
|
340
340
|
paths: [parentPath]
|
|
@@ -345,13 +345,13 @@ async function collectConfigSchemas(packageNames, packagePaths) {
|
|
|
345
345
|
if (!pkgPath) {
|
|
346
346
|
return;
|
|
347
347
|
}
|
|
348
|
-
const pkg = await fs__default[
|
|
348
|
+
const pkg = await fs__default["default"].readJson(pkgPath);
|
|
349
349
|
let versions = visitedPackageVersions.get(pkg.name);
|
|
350
350
|
if (versions == null ? void 0 : versions.has(pkg.version)) {
|
|
351
351
|
return;
|
|
352
352
|
}
|
|
353
353
|
if (!versions) {
|
|
354
|
-
versions = new Set();
|
|
354
|
+
versions = /* @__PURE__ */ new Set();
|
|
355
355
|
visitedPackageVersions.set(pkg.name, versions);
|
|
356
356
|
}
|
|
357
357
|
versions.add(pkg.version);
|
|
@@ -377,7 +377,7 @@ async function collectConfigSchemas(packageNames, packagePaths) {
|
|
|
377
377
|
tsSchemaPaths.push(path.relative(currentDir, path.resolve(path.dirname(pkgPath), pkg.configSchema)));
|
|
378
378
|
} else {
|
|
379
379
|
const path$1 = path.resolve(path.dirname(pkgPath), pkg.configSchema);
|
|
380
|
-
const value = await fs__default[
|
|
380
|
+
const value = await fs__default["default"].readJson(path$1);
|
|
381
381
|
schemas.push({
|
|
382
382
|
value,
|
|
383
383
|
path: path.relative(currentDir, path$1)
|
|
@@ -390,11 +390,11 @@ async function collectConfigSchemas(packageNames, packagePaths) {
|
|
|
390
390
|
});
|
|
391
391
|
}
|
|
392
392
|
}
|
|
393
|
-
await Promise.all(depNames.map((depName) => processItem({name: depName, parentPath: pkgPath})));
|
|
393
|
+
await Promise.all(depNames.map((depName) => processItem({ name: depName, parentPath: pkgPath })));
|
|
394
394
|
}
|
|
395
395
|
await Promise.all([
|
|
396
|
-
...packageNames.map((name) => processItem({name, parentPath: currentDir})),
|
|
397
|
-
...packagePaths.map((path) => processItem({name: path, packagePath: path}))
|
|
396
|
+
...packageNames.map((name) => processItem({ name, parentPath: currentDir })),
|
|
397
|
+
...packagePaths.map((path) => processItem({ name: path, packagePath: path }))
|
|
398
398
|
]);
|
|
399
399
|
const tsSchemas = compileTsSchemas(tsSchemaPaths);
|
|
400
400
|
return schemas.concat(tsSchemas);
|
|
@@ -431,7 +431,7 @@ function compileTsSchemas(paths) {
|
|
|
431
431
|
if (!value) {
|
|
432
432
|
throw new Error(`Invalid schema in ${path$1}, missing Config export`);
|
|
433
433
|
}
|
|
434
|
-
return {path: path$1, value};
|
|
434
|
+
return { path: path$1, value };
|
|
435
435
|
});
|
|
436
436
|
return tsSchemas;
|
|
437
437
|
}
|
|
@@ -446,7 +446,7 @@ function filterByVisibility(data, includeVisibilities, visibilityByDataPath, tra
|
|
|
446
446
|
if (typeof jsonVal !== "object") {
|
|
447
447
|
if (isVisible) {
|
|
448
448
|
if (transformFunc) {
|
|
449
|
-
return transformFunc(jsonVal, {visibility});
|
|
449
|
+
return transformFunc(jsonVal, { visibility });
|
|
450
450
|
}
|
|
451
451
|
return jsonVal;
|
|
452
452
|
}
|
|
@@ -459,7 +459,12 @@ function filterByVisibility(data, includeVisibilities, visibilityByDataPath, tra
|
|
|
459
459
|
} else if (Array.isArray(jsonVal)) {
|
|
460
460
|
const arr = new Array();
|
|
461
461
|
for (const [index, value] of jsonVal.entries()) {
|
|
462
|
-
|
|
462
|
+
let path = visibilityPath;
|
|
463
|
+
const hasVisibilityInIndex = visibilityByDataPath.get(`${visibilityPath}/${index}`);
|
|
464
|
+
if (hasVisibilityInIndex || typeof value === "object") {
|
|
465
|
+
path = `${visibilityPath}/${index}`;
|
|
466
|
+
}
|
|
467
|
+
const out = transform(value, path, `${filterPath}[${index}]`);
|
|
463
468
|
if (out !== void 0) {
|
|
464
469
|
arr.push(out);
|
|
465
470
|
}
|
|
@@ -517,7 +522,7 @@ function filterErrorsByVisibility(errors, includeVisibilities, visibilityByDataP
|
|
|
517
522
|
}
|
|
518
523
|
|
|
519
524
|
function errorsToError(errors) {
|
|
520
|
-
const messages = errors.map(({dataPath, message, params}) => {
|
|
525
|
+
const messages = errors.map(({ dataPath, message, params }) => {
|
|
521
526
|
const paramStr = Object.entries(params).map(([name, value]) => `${name}=${value}`).join(" ");
|
|
522
527
|
return `Config ${message || ""} { ${paramStr} } at ${dataPath}`;
|
|
523
528
|
});
|
|
@@ -531,7 +536,7 @@ async function loadConfigSchema(options) {
|
|
|
531
536
|
if ("dependencies" in options) {
|
|
532
537
|
schemas = await collectConfigSchemas(options.dependencies, (_a = options.packagePaths) != null ? _a : []);
|
|
533
538
|
} else {
|
|
534
|
-
const {serialized} = options;
|
|
539
|
+
const { serialized } = options;
|
|
535
540
|
if ((serialized == null ? void 0 : serialized.backstageConfigSchemaVersion) !== 1) {
|
|
536
541
|
throw new Error("Serialized configuration schema is invalid or has an invalid version number");
|
|
537
542
|
}
|
|
@@ -539,7 +544,7 @@ async function loadConfigSchema(options) {
|
|
|
539
544
|
}
|
|
540
545
|
const validate = compileConfigSchemas(schemas);
|
|
541
546
|
return {
|
|
542
|
-
process(configs, {visibility, valueTransform, withFilteredKeys} = {}) {
|
|
547
|
+
process(configs, { visibility, valueTransform, withFilteredKeys } = {}) {
|
|
543
548
|
const result = validate(configs);
|
|
544
549
|
const visibleErrors = filterErrorsByVisibility(result.errors, visibility, result.visibilityByDataPath, result.visibilityBySchemaPath);
|
|
545
550
|
if (visibleErrors.length > 0) {
|
|
@@ -547,12 +552,12 @@ async function loadConfigSchema(options) {
|
|
|
547
552
|
}
|
|
548
553
|
let processedConfigs = configs;
|
|
549
554
|
if (visibility) {
|
|
550
|
-
processedConfigs = processedConfigs.map(({data, context}) => ({
|
|
555
|
+
processedConfigs = processedConfigs.map(({ data, context }) => ({
|
|
551
556
|
context,
|
|
552
557
|
...filterByVisibility(data, visibility, result.visibilityByDataPath, valueTransform, withFilteredKeys)
|
|
553
558
|
}));
|
|
554
559
|
} else if (valueTransform) {
|
|
555
|
-
processedConfigs = processedConfigs.map(({data, context}) => ({
|
|
560
|
+
processedConfigs = processedConfigs.map(({ data, context }) => ({
|
|
556
561
|
context,
|
|
557
562
|
...filterByVisibility(data, Array.from(CONFIG_VISIBILITIES), result.visibilityByDataPath, valueTransform, withFilteredKeys)
|
|
558
563
|
}));
|
|
@@ -578,21 +583,20 @@ function isValidUrl(url) {
|
|
|
578
583
|
}
|
|
579
584
|
|
|
580
585
|
async function loadConfig(options) {
|
|
581
|
-
const {configRoot, experimentalEnvFunc: envFunc, watch, remote} = options;
|
|
586
|
+
const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options;
|
|
582
587
|
const configPaths = options.configTargets.slice().filter((e) => e.hasOwnProperty("path")).map((configTarget) => configTarget.path);
|
|
583
|
-
options.configPaths.forEach((cp) => {
|
|
584
|
-
if (!configPaths.includes(cp)) {
|
|
585
|
-
configPaths.push(cp);
|
|
586
|
-
}
|
|
587
|
-
});
|
|
588
588
|
const configUrls = options.configTargets.slice().filter((e) => e.hasOwnProperty("url")).map((configTarget) => configTarget.url);
|
|
589
|
-
if (remote === void 0
|
|
590
|
-
|
|
589
|
+
if (remote === void 0) {
|
|
590
|
+
if (configUrls.length > 0) {
|
|
591
|
+
throw new Error(`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.`);
|
|
592
|
+
}
|
|
593
|
+
} else if (remote.reloadIntervalSeconds <= 0) {
|
|
594
|
+
throw new Error(`Remote config must be contain a non zero reloadIntervalSeconds: <seconds> value`);
|
|
591
595
|
}
|
|
592
596
|
if (configPaths.length === 0 && configUrls.length === 0) {
|
|
593
597
|
configPaths.push(path.resolve(configRoot, "app-config.yaml"));
|
|
594
598
|
const localConfig = path.resolve(configRoot, "app-config.local.yaml");
|
|
595
|
-
if (await fs__default[
|
|
599
|
+
if (await fs__default["default"].pathExists(localConfig)) {
|
|
596
600
|
configPaths.push(localConfig);
|
|
597
601
|
}
|
|
598
602
|
}
|
|
@@ -604,21 +608,21 @@ async function loadConfig(options) {
|
|
|
604
608
|
throw new Error(`Config load path is not absolute: '${configPath}'`);
|
|
605
609
|
}
|
|
606
610
|
const dir = path.dirname(configPath);
|
|
607
|
-
const readFile = (path$1) => fs__default[
|
|
608
|
-
const input = yaml__default[
|
|
611
|
+
const readFile = (path$1) => fs__default["default"].readFile(path.resolve(dir, path$1), "utf8");
|
|
612
|
+
const input = yaml__default["default"].parse(await readFile(configPath));
|
|
609
613
|
const substitutionTransform = createSubstitutionTransform(env);
|
|
610
614
|
const data = await applyConfigTransforms(dir, input, [
|
|
611
615
|
createIncludeTransform(env, readFile, substitutionTransform),
|
|
612
616
|
substitutionTransform
|
|
613
617
|
]);
|
|
614
|
-
configs.push({data, context: path.basename(configPath)});
|
|
618
|
+
configs.push({ data, context: path.basename(configPath) });
|
|
615
619
|
}
|
|
616
620
|
return configs;
|
|
617
621
|
};
|
|
618
622
|
const loadRemoteConfigFiles = async () => {
|
|
619
623
|
const configs = [];
|
|
620
624
|
const readConfigFromUrl = async (url) => {
|
|
621
|
-
const response = await fetch__default[
|
|
625
|
+
const response = await fetch__default["default"](url);
|
|
622
626
|
if (!response.ok) {
|
|
623
627
|
throw new Error(`Could not read config file at ${url}`);
|
|
624
628
|
}
|
|
@@ -633,12 +637,12 @@ async function loadConfig(options) {
|
|
|
633
637
|
if (!remoteConfigContent) {
|
|
634
638
|
throw new Error(`Config is not valid`);
|
|
635
639
|
}
|
|
636
|
-
const configYaml = yaml__default[
|
|
640
|
+
const configYaml = yaml__default["default"].parse(remoteConfigContent);
|
|
637
641
|
const substitutionTransform = createSubstitutionTransform(env);
|
|
638
642
|
const data = await applyConfigTransforms(configRoot, configYaml, [
|
|
639
643
|
substitutionTransform
|
|
640
644
|
]);
|
|
641
|
-
configs.push({data, context: configUrl});
|
|
645
|
+
configs.push({ data, context: configUrl });
|
|
642
646
|
}
|
|
643
647
|
return configs;
|
|
644
648
|
};
|
|
@@ -658,7 +662,7 @@ async function loadConfig(options) {
|
|
|
658
662
|
}
|
|
659
663
|
const envConfigs = await readEnvConfig(process.env);
|
|
660
664
|
const watchConfigFile = (watchProp) => {
|
|
661
|
-
const watcher = chokidar__default[
|
|
665
|
+
const watcher = chokidar__default["default"].watch(configPaths, {
|
|
662
666
|
usePolling: process.env.NODE_ENV === "test"
|
|
663
667
|
});
|
|
664
668
|
let currentSerializedConfig = JSON.stringify(fileConfigs);
|
|
@@ -716,7 +720,9 @@ async function loadConfig(options) {
|
|
|
716
720
|
if (watch && remote) {
|
|
717
721
|
watchRemoteConfig(watch, remote);
|
|
718
722
|
}
|
|
719
|
-
return
|
|
723
|
+
return {
|
|
724
|
+
appConfigs: remote ? [...remoteConfigs, ...fileConfigs, ...envConfigs] : [...fileConfigs, ...envConfigs]
|
|
725
|
+
};
|
|
720
726
|
}
|
|
721
727
|
|
|
722
728
|
exports.loadConfig = loadConfig;
|