@backstage/config-loader 0.8.1 → 0.9.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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # @backstage/config-loader
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f6722d2458: Removed deprecated option `env` from `LoadConfigOptions` and associated tests
8
+ - 67d6cb3c7e: Removed deprecated option `configPaths` as it has been superseded by `configTargets`
9
+
10
+ ### Patch Changes
11
+
12
+ - 1e7070443d: In case remote.reloadIntervalSeconds is passed, it must be a valid positive value
13
+
3
14
  ## 0.8.1
4
15
 
5
16
  ### 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['default'].parse(content),
143
- ".yml": async (content) => yaml__default['default'].parse(content)
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['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['default'](merged, (schema, path) => {
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['default']({allOf: schemas}, {
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['default'].realpath(process.cwd());
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['default'].pathExists(pkgPath);
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['default'].readJson(pkgPath);
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['default'].readJson(path$1);
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
  }
@@ -522,7 +522,7 @@ function filterErrorsByVisibility(errors, includeVisibilities, visibilityByDataP
522
522
  }
523
523
 
524
524
  function errorsToError(errors) {
525
- const messages = errors.map(({dataPath, message, params}) => {
525
+ const messages = errors.map(({ dataPath, message, params }) => {
526
526
  const paramStr = Object.entries(params).map(([name, value]) => `${name}=${value}`).join(" ");
527
527
  return `Config ${message || ""} { ${paramStr} } at ${dataPath}`;
528
528
  });
@@ -536,7 +536,7 @@ async function loadConfigSchema(options) {
536
536
  if ("dependencies" in options) {
537
537
  schemas = await collectConfigSchemas(options.dependencies, (_a = options.packagePaths) != null ? _a : []);
538
538
  } else {
539
- const {serialized} = options;
539
+ const { serialized } = options;
540
540
  if ((serialized == null ? void 0 : serialized.backstageConfigSchemaVersion) !== 1) {
541
541
  throw new Error("Serialized configuration schema is invalid or has an invalid version number");
542
542
  }
@@ -544,7 +544,7 @@ async function loadConfigSchema(options) {
544
544
  }
545
545
  const validate = compileConfigSchemas(schemas);
546
546
  return {
547
- process(configs, {visibility, valueTransform, withFilteredKeys} = {}) {
547
+ process(configs, { visibility, valueTransform, withFilteredKeys } = {}) {
548
548
  const result = validate(configs);
549
549
  const visibleErrors = filterErrorsByVisibility(result.errors, visibility, result.visibilityByDataPath, result.visibilityBySchemaPath);
550
550
  if (visibleErrors.length > 0) {
@@ -552,12 +552,12 @@ async function loadConfigSchema(options) {
552
552
  }
553
553
  let processedConfigs = configs;
554
554
  if (visibility) {
555
- processedConfigs = processedConfigs.map(({data, context}) => ({
555
+ processedConfigs = processedConfigs.map(({ data, context }) => ({
556
556
  context,
557
557
  ...filterByVisibility(data, visibility, result.visibilityByDataPath, valueTransform, withFilteredKeys)
558
558
  }));
559
559
  } else if (valueTransform) {
560
- processedConfigs = processedConfigs.map(({data, context}) => ({
560
+ processedConfigs = processedConfigs.map(({ data, context }) => ({
561
561
  context,
562
562
  ...filterByVisibility(data, Array.from(CONFIG_VISIBILITIES), result.visibilityByDataPath, valueTransform, withFilteredKeys)
563
563
  }));
@@ -583,21 +583,20 @@ function isValidUrl(url) {
583
583
  }
584
584
 
585
585
  async function loadConfig(options) {
586
- const {configRoot, experimentalEnvFunc: envFunc, watch, remote} = options;
586
+ const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options;
587
587
  const configPaths = options.configTargets.slice().filter((e) => e.hasOwnProperty("path")).map((configTarget) => configTarget.path);
588
- options.configPaths.forEach((cp) => {
589
- if (!configPaths.includes(cp)) {
590
- configPaths.push(cp);
591
- }
592
- });
593
588
  const configUrls = options.configTargets.slice().filter((e) => e.hasOwnProperty("url")).map((configTarget) => configTarget.url);
594
- if (remote === void 0 && configUrls.length > 0) {
595
- throw new Error(`Remote config detected but this feature is turned off`);
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`);
596
595
  }
597
596
  if (configPaths.length === 0 && configUrls.length === 0) {
598
597
  configPaths.push(path.resolve(configRoot, "app-config.yaml"));
599
598
  const localConfig = path.resolve(configRoot, "app-config.local.yaml");
600
- if (await fs__default['default'].pathExists(localConfig)) {
599
+ if (await fs__default["default"].pathExists(localConfig)) {
601
600
  configPaths.push(localConfig);
602
601
  }
603
602
  }
@@ -609,21 +608,21 @@ async function loadConfig(options) {
609
608
  throw new Error(`Config load path is not absolute: '${configPath}'`);
610
609
  }
611
610
  const dir = path.dirname(configPath);
612
- const readFile = (path$1) => fs__default['default'].readFile(path.resolve(dir, path$1), "utf8");
613
- const input = yaml__default['default'].parse(await readFile(configPath));
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));
614
613
  const substitutionTransform = createSubstitutionTransform(env);
615
614
  const data = await applyConfigTransforms(dir, input, [
616
615
  createIncludeTransform(env, readFile, substitutionTransform),
617
616
  substitutionTransform
618
617
  ]);
619
- configs.push({data, context: path.basename(configPath)});
618
+ configs.push({ data, context: path.basename(configPath) });
620
619
  }
621
620
  return configs;
622
621
  };
623
622
  const loadRemoteConfigFiles = async () => {
624
623
  const configs = [];
625
624
  const readConfigFromUrl = async (url) => {
626
- const response = await fetch__default['default'](url);
625
+ const response = await fetch__default["default"](url);
627
626
  if (!response.ok) {
628
627
  throw new Error(`Could not read config file at ${url}`);
629
628
  }
@@ -638,12 +637,12 @@ async function loadConfig(options) {
638
637
  if (!remoteConfigContent) {
639
638
  throw new Error(`Config is not valid`);
640
639
  }
641
- const configYaml = yaml__default['default'].parse(remoteConfigContent);
640
+ const configYaml = yaml__default["default"].parse(remoteConfigContent);
642
641
  const substitutionTransform = createSubstitutionTransform(env);
643
642
  const data = await applyConfigTransforms(configRoot, configYaml, [
644
643
  substitutionTransform
645
644
  ]);
646
- configs.push({data, context: configUrl});
645
+ configs.push({ data, context: configUrl });
647
646
  }
648
647
  return configs;
649
648
  };
@@ -663,7 +662,7 @@ async function loadConfig(options) {
663
662
  }
664
663
  const envConfigs = await readEnvConfig(process.env);
665
664
  const watchConfigFile = (watchProp) => {
666
- const watcher = chokidar__default['default'].watch(configPaths, {
665
+ const watcher = chokidar__default["default"].watch(configPaths, {
667
666
  usePolling: process.env.NODE_ENV === "test"
668
667
  });
669
668
  let currentSerializedConfig = JSON.stringify(fileConfigs);
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/lib/env.ts","../src/lib/transform/utils.ts","../src/lib/transform/apply.ts","../src/lib/transform/include.ts","../src/lib/transform/substitution.ts","../src/lib/schema/types.ts","../src/lib/schema/compile.ts","../src/lib/schema/collect.ts","../src/lib/schema/filtering.ts","../src/lib/schema/load.ts","../src/lib/urls.ts","../src/loader.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\nconst ENV_PREFIX = 'APP_CONFIG_';\n\n// Update the same pattern in config package if this is changed\nconst CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;\n\n/**\n * Read runtime configuration from the environment.\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n */\nexport function readEnvConfig(env: {\n [name: string]: string | undefined;\n}): AppConfig[] {\n let data: JsonObject | undefined = undefined;\n\n for (const [name, value] of Object.entries(env)) {\n if (!value) {\n continue;\n }\n if (name.startsWith(ENV_PREFIX)) {\n const key = name.replace(ENV_PREFIX, '');\n const keyParts = key.split('_');\n\n let obj = (data = data ?? {});\n for (const [index, part] of keyParts.entries()) {\n if (!CONFIG_KEY_PART_PATTERN.test(part)) {\n throw new TypeError(`Invalid env config key '${key}'`);\n }\n if (index < keyParts.length - 1) {\n obj = (obj[part] = obj[part] ?? {}) as JsonObject;\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n const subKey = keyParts.slice(0, index + 1).join('_');\n throw new TypeError(\n `Could not nest config for key '${key}' under existing value '${subKey}'`,\n );\n }\n } else {\n if (part in obj) {\n throw new TypeError(\n `Refusing to override existing config at key '${key}'`,\n );\n }\n try {\n const [, parsedValue] = safeJsonParse(value);\n if (parsedValue === null) {\n throw new Error('value may not be null');\n }\n obj[part] = parsedValue;\n } catch (error) {\n throw new TypeError(\n `Failed to parse JSON-serialized config value for key '${key}', ${error}`,\n );\n }\n }\n }\n }\n }\n\n return data ? [{ data, context: 'env' }] : [];\n}\n\nfunction safeJsonParse(str: string): [Error | null, any] {\n try {\n return [null, JSON.parse(str)];\n } catch (err) {\n assertError(err);\n return [err, str];\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue, JsonObject } from '@backstage/types';\n\nexport function isObject(obj: JsonValue | undefined): obj is JsonObject {\n if (typeof obj !== 'object') {\n return false;\n } else if (Array.isArray(obj)) {\n return false;\n }\n return obj !== null;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\nimport { TransformFunc } from './types';\nimport { isObject } from './utils';\n\n/**\n * Applies a set of transforms to raw configuration data.\n */\nexport async function applyConfigTransforms(\n initialDir: string,\n input: JsonValue,\n transforms: TransformFunc[],\n): Promise<JsonObject> {\n async function transform(\n inputObj: JsonValue,\n path: string,\n baseDir: string,\n ): Promise<JsonValue | undefined> {\n let obj = inputObj;\n let dir = baseDir;\n\n for (const tf of transforms) {\n try {\n const result = await tf(inputObj, baseDir);\n if (result.applied) {\n if (result.value === undefined) {\n return undefined;\n }\n obj = result.value;\n dir = result.newBaseDir ?? dir;\n break;\n }\n } catch (error) {\n assertError(error);\n throw new Error(`error at ${path}, ${error.message}`);\n }\n }\n\n if (typeof obj !== 'object') {\n return obj;\n } else if (obj === null) {\n return undefined;\n } else if (Array.isArray(obj)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of obj.entries()) {\n const out = await transform(value, `${path}[${index}]`, dir);\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n return arr;\n }\n\n const out: JsonObject = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // undefined covers optional fields\n if (value !== undefined) {\n const result = await transform(value, `${path}.${key}`, dir);\n if (result !== undefined) {\n out[key] = result;\n }\n }\n }\n\n return out;\n }\n\n const finalData = await transform(input, '', initialDir);\n if (!isObject(finalData)) {\n throw new TypeError('expected object at config root');\n }\n return finalData;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport yaml from 'yaml';\nimport { extname, dirname, resolve as resolvePath } from 'path';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { isObject } from './utils';\nimport { TransformFunc, EnvFunc, ReadFileFunc } from './types';\n\n// Parsers for each type of included file\nconst includeFileParser: {\n [ext in string]: (content: string) => Promise<JsonObject>;\n} = {\n '.json': async content => JSON.parse(content),\n '.yaml': async content => yaml.parse(content),\n '.yml': async content => yaml.parse(content),\n};\n\n/**\n * Transforms a include description into the actual included value.\n */\nexport function createIncludeTransform(\n env: EnvFunc,\n readFile: ReadFileFunc,\n substitute: TransformFunc,\n): TransformFunc {\n return async (input: JsonValue, baseDir: string) => {\n if (!isObject(input)) {\n return { applied: false };\n }\n // Check if there's any key that starts with a '$', in that case we treat\n // this entire object as an include description.\n const [includeKey] = Object.keys(input).filter(key => key.startsWith('$'));\n if (includeKey) {\n if (Object.keys(input).length !== 1) {\n throw new Error(\n `include key ${includeKey} should not have adjacent keys`,\n );\n }\n } else {\n return { applied: false };\n }\n\n const rawIncludedValue = input[includeKey];\n if (typeof rawIncludedValue !== 'string') {\n throw new Error(`${includeKey} include value is not a string`);\n }\n\n const substituteResults = await substitute(rawIncludedValue, baseDir);\n const includeValue = substituteResults.applied\n ? substituteResults.value\n : rawIncludedValue;\n\n // The second string check is needed for Typescript to know this is a string.\n if (includeValue === undefined || typeof includeValue !== 'string') {\n throw new Error(`${includeKey} substitution value was undefined`);\n }\n\n switch (includeKey) {\n case '$file':\n try {\n const value = await readFile(resolvePath(baseDir, includeValue));\n return { applied: true, value };\n } catch (error) {\n throw new Error(`failed to read file ${includeValue}, ${error}`);\n }\n case '$env':\n try {\n return { applied: true, value: await env(includeValue) };\n } catch (error) {\n throw new Error(`failed to read env ${includeValue}, ${error}`);\n }\n\n case '$include': {\n const [filePath, dataPath] = includeValue.split(/#(.*)/);\n\n const ext = extname(filePath);\n const parser = includeFileParser[ext];\n if (!parser) {\n throw new Error(\n `no configuration parser available for included file ${filePath}`,\n );\n }\n\n const path = resolvePath(baseDir, filePath);\n const content = await readFile(path);\n const newBaseDir = dirname(path);\n\n const parts = dataPath ? dataPath.split('.') : [];\n\n let value: JsonValue | undefined;\n try {\n value = await parser(content);\n } catch (error) {\n throw new Error(\n `failed to parse included file ${filePath}, ${error}`,\n );\n }\n\n // This bit handles selecting a subtree in the included file, if a path was provided after a #\n for (const [index, part] of parts.entries()) {\n if (!isObject(value)) {\n const errPath = parts.slice(0, index).join('.');\n throw new Error(\n `value at '${errPath}' in included file ${filePath} is not an object`,\n );\n }\n value = value[part];\n }\n\n return {\n applied: true,\n value,\n newBaseDir: newBaseDir !== baseDir ? newBaseDir : undefined,\n };\n }\n\n default:\n throw new Error(`unknown include ${includeKey}`);\n }\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue } from '@backstage/types';\nimport { TransformFunc, EnvFunc } from './types';\n\n/**\n * A environment variable substitution transform that transforms e.g. 'token ${MY_TOKEN}'\n * to 'token abc' if MY_TOKEN is 'abc'. If any of the substituted variables are undefined,\n * the entire expression ends up undefined.\n */\nexport function createSubstitutionTransform(env: EnvFunc): TransformFunc {\n return async (input: JsonValue) => {\n if (typeof input !== 'string') {\n return { applied: false };\n }\n\n const parts: (string | undefined)[] = input.split(/(\\$?\\$\\{[^{}]*\\})/);\n for (let i = 1; i < parts.length; i += 2) {\n const part = parts[i]!;\n if (part.startsWith('$$')) {\n parts[i] = part.slice(1);\n } else {\n parts[i] = await env(part.slice(2, -1).trim());\n }\n }\n\n if (parts.some(part => part === undefined)) {\n return { applied: true, value: undefined };\n }\n return { applied: true, value: parts.join('') };\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\n\n/**\n * An sub-set of configuration schema.\n */\nexport type ConfigSchemaPackageEntry = {\n /**\n * The configuration schema itself.\n */\n value: JsonObject;\n /**\n * The relative path that the configuration schema was discovered at.\n */\n path: string;\n};\n\n/**\n * A list of all possible configuration value visibilities.\n */\nexport const CONFIG_VISIBILITIES = ['frontend', 'backend', 'secret'] as const;\n\n/**\n * A type representing the possible configuration value visibilities\n *\n * @public\n */\nexport type ConfigVisibility = 'frontend' | 'backend' | 'secret';\n\n/**\n * The default configuration visibility if no other values is given.\n */\nexport const DEFAULT_CONFIG_VISIBILITY: ConfigVisibility = 'backend';\n\n/**\n * An explanation of a configuration validation error.\n */\nexport type ValidationError = {\n keyword: string;\n dataPath: string;\n schemaPath: string;\n params: Record<string, any>;\n propertyName?: string;\n message?: string;\n};\n\n/**\n * The result of validating configuration data using a schema.\n */\ntype ValidationResult = {\n /**\n * Errors that where emitted during validation, if any.\n */\n errors?: ValidationError[];\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/<key>/<sub-key>/<array-index>/<leaf-key>`\n */\n visibilityByDataPath: Map<string, ConfigVisibility>;\n\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/properties/<key>/items/additionalProperties/<leaf-key>`\n */\n visibilityBySchemaPath: Map<string, ConfigVisibility>;\n};\n\n/**\n * A function used validate configuration data.\n */\nexport type ValidationFunc = (configs: AppConfig[]) => ValidationResult;\n\n/**\n * A function used to transform primitive configuration values.\n *\n * @public\n */\nexport type TransformFunc<T extends number | string | boolean> = (\n value: T,\n context: { visibility: ConfigVisibility },\n) => T | undefined;\n\n/**\n * Options used to process configuration data with a schema.\n *\n * @public\n */\nexport type ConfigSchemaProcessingOptions = {\n /**\n * The visibilities that should be included in the output data.\n * If omitted, the data will not be filtered by visibility.\n */\n visibility?: ConfigVisibility[];\n\n /**\n * A transform function that can be used to transform primitive configuration values\n * during validation. The value returned from the transform function will be used\n * instead of the original value. If the transform returns `undefined`, the value\n * will be omitted.\n */\n valueTransform?: TransformFunc<any>;\n\n /**\n * Whether or not to include the `filteredKeys` property in the output `AppConfig`s.\n *\n * Default: `false`.\n */\n withFilteredKeys?: boolean;\n};\n\n/**\n * A loaded configuration schema that is ready to process configuration data.\n *\n * @public\n */\nexport type ConfigSchema = {\n process(\n appConfigs: AppConfig[],\n options?: ConfigSchemaProcessingOptions,\n ): AppConfig[];\n\n serialize(): JsonObject;\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Ajv from 'ajv';\nimport { JSONSchema7 as JSONSchema } from 'json-schema';\nimport mergeAllOf, { Resolvers } from 'json-schema-merge-allof';\nimport traverse from 'json-schema-traverse';\nimport { ConfigReader } from '@backstage/config';\nimport {\n ConfigSchemaPackageEntry,\n ValidationFunc,\n CONFIG_VISIBILITIES,\n ConfigVisibility,\n} from './types';\n\n/**\n * This takes a collection of Backstage configuration schemas from various\n * sources and compiles them down into a single schema validation function.\n *\n * It also handles the implementation of the custom \"visibility\" keyword used\n * to specify the scope of different config paths.\n */\nexport function compileConfigSchemas(\n schemas: ConfigSchemaPackageEntry[],\n): ValidationFunc {\n // The ajv instance below is stateful and doesn't really allow for additional\n // output during validation. We work around this by having this extra piece\n // of state that we reset before each validation.\n const visibilityByDataPath = new Map<string, ConfigVisibility>();\n\n const ajv = new Ajv({\n allErrors: true,\n allowUnionTypes: true,\n schemas: {\n 'https://backstage.io/schema/config-v1': true,\n },\n }).addKeyword({\n keyword: 'visibility',\n metaSchema: {\n type: 'string',\n enum: CONFIG_VISIBILITIES,\n },\n compile(visibility: ConfigVisibility) {\n return (_data, context) => {\n if (context?.dataPath === undefined) {\n return false;\n }\n if (visibility && visibility !== 'backend') {\n const normalizedPath = context.dataPath.replace(\n /\\['?(.*?)'?\\]/g,\n (_, segment) => `/${segment}`,\n );\n visibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n });\n\n for (const schema of schemas) {\n try {\n ajv.compile(schema.value);\n } catch (error) {\n throw new Error(`Schema at ${schema.path} is invalid, ${error}`);\n }\n }\n\n const merged = mergeConfigSchemas(schemas.map(_ => _.value));\n const validate = ajv.compile(merged);\n\n const visibilityBySchemaPath = new Map<string, ConfigVisibility>();\n traverse(merged, (schema, path) => {\n if (schema.visibility && schema.visibility !== 'backend') {\n visibilityBySchemaPath.set(path, schema.visibility);\n }\n });\n\n return configs => {\n const config = ConfigReader.fromConfigs(configs).get();\n\n visibilityByDataPath.clear();\n\n const valid = validate(config);\n if (!valid) {\n return {\n errors: validate.errors ?? [],\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n }\n\n return {\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n };\n}\n\n/**\n * Given a list of configuration schemas from packages, merge them\n * into a single json schema.\n *\n * @public\n */\nexport function mergeConfigSchemas(schemas: JSONSchema[]): JSONSchema {\n const merged = mergeAllOf(\n { allOf: schemas },\n {\n // JSONSchema is typically subtractive, as in it always reduces the set of allowed\n // inputs through constraints. This changes the object property merging to be additive\n // rather than subtractive.\n ignoreAdditionalProperties: true,\n resolvers: {\n // This ensures that the visibilities across different schemas are sound, and\n // selects the most specific visibility for each path.\n visibility(values: string[], path: string[]) {\n const hasFrontend = values.some(_ => _ === 'frontend');\n const hasSecret = values.some(_ => _ === 'secret');\n if (hasFrontend && hasSecret) {\n throw new Error(\n `Config schema visibility is both 'frontend' and 'secret' for ${path.join(\n '/',\n )}`,\n );\n } else if (hasFrontend) {\n return 'frontend';\n } else if (hasSecret) {\n return 'secret';\n }\n\n return 'backend';\n },\n } as Partial<Resolvers<JSONSchema>>,\n },\n );\n return merged;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport {\n resolve as resolvePath,\n relative as relativePath,\n dirname,\n sep,\n} from 'path';\nimport { ConfigSchemaPackageEntry } from './types';\nimport { getProgramFromFiles, generateSchema } from 'typescript-json-schema';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\ntype Item = {\n name?: string;\n parentPath?: string;\n packagePath?: string;\n};\n\nconst req =\n typeof __non_webpack_require__ === 'undefined'\n ? require\n : __non_webpack_require__;\n\n/**\n * This collects all known config schemas across all dependencies of the app.\n */\nexport async function collectConfigSchemas(\n packageNames: string[],\n packagePaths: string[],\n): Promise<ConfigSchemaPackageEntry[]> {\n const schemas = new Array<ConfigSchemaPackageEntry>();\n const tsSchemaPaths = new Array<string>();\n const visitedPackageVersions = new Map<string, Set<string>>(); // pkgName: [versions...]\n\n const currentDir = await fs.realpath(process.cwd());\n\n async function processItem(item: Item) {\n let pkgPath = item.packagePath;\n\n if (pkgPath) {\n const pkgExists = await fs.pathExists(pkgPath);\n if (!pkgExists) {\n return;\n }\n } else if (item.name) {\n const { name, parentPath } = item;\n\n try {\n pkgPath = req.resolve(\n `${name}/package.json`,\n parentPath && {\n paths: [parentPath],\n },\n );\n } catch {\n // We can somewhat safely ignore packages that don't export package.json,\n // as they are likely not part of the Backstage ecosystem anyway.\n }\n }\n if (!pkgPath) {\n return;\n }\n\n const pkg = await fs.readJson(pkgPath);\n\n // Ensures that we only process the same version of each package once.\n let versions = visitedPackageVersions.get(pkg.name);\n if (versions?.has(pkg.version)) {\n return;\n }\n if (!versions) {\n versions = new Set();\n visitedPackageVersions.set(pkg.name, versions);\n }\n versions.add(pkg.version);\n\n const depNames = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ...Object.keys(pkg.optionalDependencies ?? {}),\n ...Object.keys(pkg.peerDependencies ?? {}),\n ];\n\n // TODO(Rugvip): Trying this out to avoid having to traverse the full dependency graph,\n // since that's pretty slow. We probably need a better way to determine when\n // we've left the Backstage ecosystem, but this will do for now.\n const hasSchema = 'configSchema' in pkg;\n const hasBackstageDep = depNames.some(_ => _.startsWith('@backstage/'));\n if (!hasSchema && !hasBackstageDep) {\n return;\n }\n if (hasSchema) {\n if (typeof pkg.configSchema === 'string') {\n const isJson = pkg.configSchema.endsWith('.json');\n const isDts = pkg.configSchema.endsWith('.d.ts');\n if (!isJson && !isDts) {\n throw new Error(\n `Config schema files must be .json or .d.ts, got ${pkg.configSchema}`,\n );\n }\n if (isDts) {\n tsSchemaPaths.push(\n relativePath(\n currentDir,\n resolvePath(dirname(pkgPath), pkg.configSchema),\n ),\n );\n } else {\n const path = resolvePath(dirname(pkgPath), pkg.configSchema);\n const value = await fs.readJson(path);\n schemas.push({\n value,\n path: relativePath(currentDir, path),\n });\n }\n } else {\n schemas.push({\n value: pkg.configSchema,\n path: relativePath(currentDir, pkgPath),\n });\n }\n }\n\n await Promise.all(\n depNames.map(depName =>\n processItem({ name: depName, parentPath: pkgPath }),\n ),\n );\n }\n\n await Promise.all([\n ...packageNames.map(name => processItem({ name, parentPath: currentDir })),\n ...packagePaths.map(path => processItem({ name: path, packagePath: path })),\n ]);\n\n const tsSchemas = compileTsSchemas(tsSchemaPaths);\n\n return schemas.concat(tsSchemas);\n}\n\n// This handles the support of TypeScript .d.ts config schema declarations.\n// We collect all typescript schema definition and compile them all in one go.\n// This is much faster than compiling them separately.\nfunction compileTsSchemas(paths: string[]) {\n if (paths.length === 0) {\n return [];\n }\n\n const program = getProgramFromFiles(paths, {\n incremental: false,\n isolatedModules: true,\n lib: ['ES5'], // Skipping most libs speeds processing up a lot, we just need the primitive types anyway\n noEmit: true,\n noResolve: true,\n skipLibCheck: true, // Skipping lib checks speeds things up\n skipDefaultLibCheck: true,\n strict: true,\n typeRoots: [], // Do not include any additional types\n types: [],\n });\n\n const tsSchemas = paths.map(path => {\n let value;\n try {\n value = generateSchema(\n program,\n // All schemas should export a `Config` symbol\n 'Config',\n // This enables usage of @visibility is doc comments\n {\n required: true,\n validationKeywords: ['visibility'],\n },\n [path.split(sep).join('/')], // Unix paths are expected for all OSes here\n ) as JsonObject | null;\n } catch (error) {\n assertError(error);\n if (error.message !== 'type Config not found') {\n throw error;\n }\n }\n\n if (!value) {\n throw new Error(`Invalid schema in ${path}, missing Config export`);\n }\n return { path, value };\n });\n\n return tsSchemas;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport {\n ConfigVisibility,\n DEFAULT_CONFIG_VISIBILITY,\n TransformFunc,\n ValidationError,\n} from './types';\n\n/**\n * This filters data by visibility by discovering the visibility of each\n * value, and then only keeping the ones that are specified in `includeVisibilities`.\n */\nexport function filterByVisibility(\n data: JsonObject,\n includeVisibilities: ConfigVisibility[],\n visibilityByDataPath: Map<string, ConfigVisibility>,\n transformFunc?: TransformFunc<number | string | boolean>,\n withFilteredKeys?: boolean,\n): { data: JsonObject; filteredKeys?: string[] } {\n const filteredKeys = new Array<string>();\n\n function transform(\n jsonVal: JsonValue,\n visibilityPath: string, // Matches the format we get from ajv\n filterPath: string, // Matches the format of the ConfigReader\n ): JsonValue | undefined {\n const visibility =\n visibilityByDataPath.get(visibilityPath) ?? DEFAULT_CONFIG_VISIBILITY;\n const isVisible = includeVisibilities.includes(visibility);\n\n if (typeof jsonVal !== 'object') {\n if (isVisible) {\n if (transformFunc) {\n return transformFunc(jsonVal, { visibility });\n }\n return jsonVal;\n }\n if (withFilteredKeys) {\n filteredKeys.push(filterPath);\n }\n return undefined;\n } else if (jsonVal === null) {\n return undefined;\n } else if (Array.isArray(jsonVal)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of jsonVal.entries()) {\n let path = visibilityPath;\n const hasVisibilityInIndex = visibilityByDataPath.get(\n `${visibilityPath}/${index}`,\n );\n\n if (hasVisibilityInIndex || typeof value === 'object') {\n path = `${visibilityPath}/${index}`;\n }\n\n const out = transform(value, path, `${filterPath}[${index}]`);\n\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n if (arr.length > 0 || isVisible) {\n return arr;\n }\n return undefined;\n }\n\n const outObj: JsonObject = {};\n let hasOutput = false;\n\n for (const [key, value] of Object.entries(jsonVal)) {\n if (value === undefined) {\n continue;\n }\n const out = transform(\n value,\n `${visibilityPath}/${key}`,\n filterPath ? `${filterPath}.${key}` : key,\n );\n if (out !== undefined) {\n outObj[key] = out;\n hasOutput = true;\n }\n }\n\n if (hasOutput || isVisible) {\n return outObj;\n }\n return undefined;\n }\n\n return {\n filteredKeys: withFilteredKeys ? filteredKeys : undefined,\n data: (transform(data, '', '') as JsonObject) ?? {},\n };\n}\n\nexport function filterErrorsByVisibility(\n errors: ValidationError[] | undefined,\n includeVisibilities: ConfigVisibility[] | undefined,\n visibilityByDataPath: Map<string, ConfigVisibility>,\n visibilityBySchemaPath: Map<string, ConfigVisibility>,\n): ValidationError[] {\n if (!errors) {\n return [];\n }\n if (!includeVisibilities) {\n return errors;\n }\n\n const visibleSchemaPaths = Array.from(visibilityBySchemaPath)\n .filter(([, v]) => includeVisibilities.includes(v))\n .map(([k]) => k);\n\n // If we're filtering by visibility we only care about the errors that happened\n // in a visible path.\n return errors.filter(error => {\n // We always include structural errors as we don't know whether there are\n // any visible paths within the structures.\n if (\n error.keyword === 'type' &&\n ['object', 'array'].includes(error.params.type)\n ) {\n return true;\n }\n\n // For fields that were required we use the schema path to determine whether\n // it was visible in addition to the data path. This is because the data path\n // visibilities are only populated for values that we reached, which we won't\n // if the value is missing.\n // We don't use this method for all the errors as the data path is more robust\n // and doesn't require us to properly trim the schema path.\n if (error.keyword === 'required') {\n const trimmedPath = error.schemaPath.slice(1, -'/required'.length);\n const fullPath = `${trimmedPath}/properties/${error.params.missingProperty}`;\n if (\n visibleSchemaPaths.some(visiblePath => visiblePath.startsWith(fullPath))\n ) {\n return true;\n }\n }\n\n const vis =\n visibilityByDataPath.get(error.dataPath) ?? DEFAULT_CONFIG_VISIBILITY;\n return vis && includeVisibilities.includes(vis);\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { compileConfigSchemas } from './compile';\nimport { collectConfigSchemas } from './collect';\nimport { filterByVisibility, filterErrorsByVisibility } from './filtering';\nimport {\n ValidationError,\n ConfigSchema,\n ConfigSchemaPackageEntry,\n CONFIG_VISIBILITIES,\n} from './types';\n\n/**\n * Options that control the loading of configuration schema files in the backend.\n *\n * @public\n */\nexport type LoadConfigSchemaOptions =\n | {\n dependencies: string[];\n packagePaths?: string[];\n }\n | {\n serialized: JsonObject;\n };\n\nfunction errorsToError(errors: ValidationError[]): Error {\n const messages = errors.map(({ dataPath, message, params }) => {\n const paramStr = Object.entries(params)\n .map(([name, value]) => `${name}=${value}`)\n .join(' ');\n return `Config ${message || ''} { ${paramStr} } at ${dataPath}`;\n });\n const error = new Error(`Config validation failed, ${messages.join('; ')}`);\n (error as any).messages = messages;\n return error;\n}\n\n/**\n * Loads config schema for a Backstage instance.\n *\n * @public\n */\nexport async function loadConfigSchema(\n options: LoadConfigSchemaOptions,\n): Promise<ConfigSchema> {\n let schemas: ConfigSchemaPackageEntry[];\n\n if ('dependencies' in options) {\n schemas = await collectConfigSchemas(\n options.dependencies,\n options.packagePaths ?? [],\n );\n } else {\n const { serialized } = options;\n if (serialized?.backstageConfigSchemaVersion !== 1) {\n throw new Error(\n 'Serialized configuration schema is invalid or has an invalid version number',\n );\n }\n schemas = serialized.schemas as ConfigSchemaPackageEntry[];\n }\n\n const validate = compileConfigSchemas(schemas);\n\n return {\n process(\n configs: AppConfig[],\n { visibility, valueTransform, withFilteredKeys } = {},\n ): AppConfig[] {\n const result = validate(configs);\n\n const visibleErrors = filterErrorsByVisibility(\n result.errors,\n visibility,\n result.visibilityByDataPath,\n result.visibilityBySchemaPath,\n );\n if (visibleErrors.length > 0) {\n throw errorsToError(visibleErrors);\n }\n\n let processedConfigs = configs;\n\n if (visibility) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n visibility,\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n } else if (valueTransform) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n Array.from(CONFIG_VISIBILITIES),\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n }\n\n return processedConfigs;\n },\n serialize(): JsonObject {\n return {\n schemas,\n backstageConfigSchemaVersion: 1,\n };\n },\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport function isValidUrl(url: string): boolean {\n try {\n // eslint-disable-next-line no-new\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport yaml from 'yaml';\nimport chokidar from 'chokidar';\nimport { basename, dirname, isAbsolute, resolve as resolvePath } from 'path';\nimport { AppConfig } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport {\n applyConfigTransforms,\n createIncludeTransform,\n createSubstitutionTransform,\n isValidUrl,\n readEnvConfig,\n} from './lib';\nimport fetch from 'node-fetch';\n\nexport type ConfigTarget = { path: string } | { url: string };\n\nexport type LoadConfigOptionsWatch = {\n /**\n * A listener that is called when a config file is changed.\n */\n onChange: (configs: AppConfig[]) => void;\n\n /**\n * An optional signal that stops the watcher once the promise resolves.\n */\n stopSignal?: Promise<void>;\n};\n\nexport type LoadConfigOptionsRemote = {\n /**\n * An optional remote config reloading period, in seconds\n */\n reloadIntervalSeconds: number;\n};\n\n/**\n * Options that control the loading of configuration files in the backend.\n *\n * @public\n */\nexport type LoadConfigOptions = {\n // The root directory of the config loading context. Used to find default configs.\n configRoot: string;\n\n /** Absolute paths to load config files from. Configs from earlier paths have lower priority.\n * @deprecated Use {@link configTargets} instead.\n */\n configPaths: string[];\n\n // Paths to load config files from. Configs from earlier paths have lower priority.\n configTargets: ConfigTarget[];\n\n /** @deprecated This option has been removed */\n env?: string;\n\n /**\n * Custom environment variable loading function\n *\n * @experimental This API is not stable and may change at any point\n */\n experimentalEnvFunc?: (name: string) => Promise<string | undefined>;\n\n /**\n * An optional remote config\n */\n remote?: LoadConfigOptionsRemote;\n\n /**\n * An optional configuration that enables watching of config files.\n */\n watch?: LoadConfigOptionsWatch;\n};\n\n/**\n * Results of loading configuration files.\n * @public\n */\nexport type LoadConfigResult = {\n /**\n * Array of all loaded configs.\n */\n appConfigs: AppConfig[];\n};\n\n/**\n * Load configuration data.\n *\n * @public\n */\nexport async function loadConfig(\n options: LoadConfigOptions,\n): Promise<LoadConfigResult> {\n const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options;\n\n const configPaths: string[] = options.configTargets\n .slice()\n .filter((e): e is { path: string } => e.hasOwnProperty('path'))\n .map(configTarget => configTarget.path);\n\n // Append deprecated configPaths to the absolute config paths received via configTargets.\n options.configPaths.forEach(cp => {\n if (!configPaths.includes(cp)) {\n configPaths.push(cp);\n }\n });\n\n const configUrls: string[] = options.configTargets\n .slice()\n .filter((e): e is { url: string } => e.hasOwnProperty('url'))\n .map(configTarget => configTarget.url);\n\n if (remote === undefined && configUrls.length > 0) {\n throw new Error(`Remote config detected but this feature is turned off`);\n }\n\n // If no paths are provided, we default to reading\n // `app-config.yaml` and, if it exists, `app-config.local.yaml`\n if (configPaths.length === 0 && configUrls.length === 0) {\n configPaths.push(resolvePath(configRoot, 'app-config.yaml'));\n\n const localConfig = resolvePath(configRoot, 'app-config.local.yaml');\n if (await fs.pathExists(localConfig)) {\n configPaths.push(localConfig);\n }\n }\n\n const env = envFunc ?? (async (name: string) => process.env[name]);\n\n const loadConfigFiles = async () => {\n const configs = [];\n\n for (const configPath of configPaths) {\n if (!isAbsolute(configPath)) {\n throw new Error(`Config load path is not absolute: '${configPath}'`);\n }\n\n const dir = dirname(configPath);\n const readFile = (path: string) =>\n fs.readFile(resolvePath(dir, path), 'utf8');\n\n const input = yaml.parse(await readFile(configPath));\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(dir, input, [\n createIncludeTransform(env, readFile, substitutionTransform),\n substitutionTransform,\n ]);\n\n configs.push({ data, context: basename(configPath) });\n }\n\n return configs;\n };\n\n const loadRemoteConfigFiles = async () => {\n const configs: AppConfig[] = [];\n\n const readConfigFromUrl = async (url: string) => {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Could not read config file at ${url}`);\n }\n\n return await response.text();\n };\n\n for (let i = 0; i < configUrls.length; i++) {\n const configUrl = configUrls[i];\n if (!isValidUrl(configUrl)) {\n throw new Error(`Config load path is not valid: '${configUrl}'`);\n }\n\n const remoteConfigContent = await readConfigFromUrl(configUrl);\n if (!remoteConfigContent) {\n throw new Error(`Config is not valid`);\n }\n const configYaml = yaml.parse(remoteConfigContent);\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(configRoot, configYaml, [\n substitutionTransform,\n ]);\n\n configs.push({ data, context: configUrl });\n }\n\n return configs;\n };\n\n let fileConfigs: AppConfig[];\n try {\n fileConfigs = await loadConfigFiles();\n } catch (error) {\n throw new ForwardedError('Failed to read static configuration file', error);\n }\n\n let remoteConfigs: AppConfig[] = [];\n if (remote) {\n try {\n remoteConfigs = await loadRemoteConfigFiles();\n } catch (error) {\n throw new ForwardedError(\n `Failed to read remote configuration file`,\n error,\n );\n }\n }\n\n const envConfigs = await readEnvConfig(process.env);\n\n const watchConfigFile = (watchProp: LoadConfigOptionsWatch) => {\n const watcher = chokidar.watch(configPaths, {\n usePolling: process.env.NODE_ENV === 'test',\n });\n\n let currentSerializedConfig = JSON.stringify(fileConfigs);\n watcher.on('change', async () => {\n try {\n const newConfigs = await loadConfigFiles();\n const newSerializedConfig = JSON.stringify(newConfigs);\n\n if (currentSerializedConfig === newSerializedConfig) {\n return;\n }\n currentSerializedConfig = newSerializedConfig;\n\n watchProp.onChange([...remoteConfigs, ...newConfigs, ...envConfigs]);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n });\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n watcher.close();\n });\n }\n };\n\n const watchRemoteConfig = (\n watchProp: LoadConfigOptionsWatch,\n remoteProp: LoadConfigOptionsRemote,\n ) => {\n const hasConfigChanged = async (\n oldRemoteConfigs: AppConfig[],\n newRemoteConfigs: AppConfig[],\n ) => {\n return (\n JSON.stringify(oldRemoteConfigs) !== JSON.stringify(newRemoteConfigs)\n );\n };\n\n let handle: NodeJS.Timeout | undefined;\n try {\n handle = setInterval(async () => {\n console.info(`Checking for config update`);\n const newRemoteConfigs = await loadRemoteConfigFiles();\n if (await hasConfigChanged(remoteConfigs, newRemoteConfigs)) {\n remoteConfigs = newRemoteConfigs;\n console.info(`Remote config change, reloading config ...`);\n watchProp.onChange([...remoteConfigs, ...fileConfigs, ...envConfigs]);\n console.info(`Remote config reloaded`);\n }\n }, remoteProp.reloadIntervalSeconds * 1000);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n if (handle !== undefined) {\n console.info(`Stopping remote config watch`);\n clearInterval(handle);\n handle = undefined;\n }\n });\n }\n };\n\n // Set up config file watching if requested by the caller\n if (watch) {\n watchConfigFile(watch);\n }\n\n if (watch && remote) {\n watchRemoteConfig(watch, remote);\n }\n\n return {\n appConfigs: remote\n ? [...remoteConfigs, ...fileConfigs, ...envConfigs]\n : [...fileConfigs, ...envConfigs],\n };\n}\n"],"names":["yaml","resolvePath","extname","path","dirname","Ajv","config","ConfigReader","mergeAllOf","fs","relativePath","getProgramFromFiles","generateSchema","sep","isAbsolute","basename","fetch","ForwardedError","chokidar"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/lib/env.ts","../src/lib/transform/utils.ts","../src/lib/transform/apply.ts","../src/lib/transform/include.ts","../src/lib/transform/substitution.ts","../src/lib/schema/types.ts","../src/lib/schema/compile.ts","../src/lib/schema/collect.ts","../src/lib/schema/filtering.ts","../src/lib/schema/load.ts","../src/lib/urls.ts","../src/loader.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\nconst ENV_PREFIX = 'APP_CONFIG_';\n\n// Update the same pattern in config package if this is changed\nconst CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;\n\n/**\n * Read runtime configuration from the environment.\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n */\nexport function readEnvConfig(env: {\n [name: string]: string | undefined;\n}): AppConfig[] {\n let data: JsonObject | undefined = undefined;\n\n for (const [name, value] of Object.entries(env)) {\n if (!value) {\n continue;\n }\n if (name.startsWith(ENV_PREFIX)) {\n const key = name.replace(ENV_PREFIX, '');\n const keyParts = key.split('_');\n\n let obj = (data = data ?? {});\n for (const [index, part] of keyParts.entries()) {\n if (!CONFIG_KEY_PART_PATTERN.test(part)) {\n throw new TypeError(`Invalid env config key '${key}'`);\n }\n if (index < keyParts.length - 1) {\n obj = (obj[part] = obj[part] ?? {}) as JsonObject;\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n const subKey = keyParts.slice(0, index + 1).join('_');\n throw new TypeError(\n `Could not nest config for key '${key}' under existing value '${subKey}'`,\n );\n }\n } else {\n if (part in obj) {\n throw new TypeError(\n `Refusing to override existing config at key '${key}'`,\n );\n }\n try {\n const [, parsedValue] = safeJsonParse(value);\n if (parsedValue === null) {\n throw new Error('value may not be null');\n }\n obj[part] = parsedValue;\n } catch (error) {\n throw new TypeError(\n `Failed to parse JSON-serialized config value for key '${key}', ${error}`,\n );\n }\n }\n }\n }\n }\n\n return data ? [{ data, context: 'env' }] : [];\n}\n\nfunction safeJsonParse(str: string): [Error | null, any] {\n try {\n return [null, JSON.parse(str)];\n } catch (err) {\n assertError(err);\n return [err, str];\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue, JsonObject } from '@backstage/types';\n\nexport function isObject(obj: JsonValue | undefined): obj is JsonObject {\n if (typeof obj !== 'object') {\n return false;\n } else if (Array.isArray(obj)) {\n return false;\n }\n return obj !== null;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\nimport { TransformFunc } from './types';\nimport { isObject } from './utils';\n\n/**\n * Applies a set of transforms to raw configuration data.\n */\nexport async function applyConfigTransforms(\n initialDir: string,\n input: JsonValue,\n transforms: TransformFunc[],\n): Promise<JsonObject> {\n async function transform(\n inputObj: JsonValue,\n path: string,\n baseDir: string,\n ): Promise<JsonValue | undefined> {\n let obj = inputObj;\n let dir = baseDir;\n\n for (const tf of transforms) {\n try {\n const result = await tf(inputObj, baseDir);\n if (result.applied) {\n if (result.value === undefined) {\n return undefined;\n }\n obj = result.value;\n dir = result.newBaseDir ?? dir;\n break;\n }\n } catch (error) {\n assertError(error);\n throw new Error(`error at ${path}, ${error.message}`);\n }\n }\n\n if (typeof obj !== 'object') {\n return obj;\n } else if (obj === null) {\n return undefined;\n } else if (Array.isArray(obj)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of obj.entries()) {\n const out = await transform(value, `${path}[${index}]`, dir);\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n return arr;\n }\n\n const out: JsonObject = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // undefined covers optional fields\n if (value !== undefined) {\n const result = await transform(value, `${path}.${key}`, dir);\n if (result !== undefined) {\n out[key] = result;\n }\n }\n }\n\n return out;\n }\n\n const finalData = await transform(input, '', initialDir);\n if (!isObject(finalData)) {\n throw new TypeError('expected object at config root');\n }\n return finalData;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport yaml from 'yaml';\nimport { extname, dirname, resolve as resolvePath } from 'path';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { isObject } from './utils';\nimport { TransformFunc, EnvFunc, ReadFileFunc } from './types';\n\n// Parsers for each type of included file\nconst includeFileParser: {\n [ext in string]: (content: string) => Promise<JsonObject>;\n} = {\n '.json': async content => JSON.parse(content),\n '.yaml': async content => yaml.parse(content),\n '.yml': async content => yaml.parse(content),\n};\n\n/**\n * Transforms a include description into the actual included value.\n */\nexport function createIncludeTransform(\n env: EnvFunc,\n readFile: ReadFileFunc,\n substitute: TransformFunc,\n): TransformFunc {\n return async (input: JsonValue, baseDir: string) => {\n if (!isObject(input)) {\n return { applied: false };\n }\n // Check if there's any key that starts with a '$', in that case we treat\n // this entire object as an include description.\n const [includeKey] = Object.keys(input).filter(key => key.startsWith('$'));\n if (includeKey) {\n if (Object.keys(input).length !== 1) {\n throw new Error(\n `include key ${includeKey} should not have adjacent keys`,\n );\n }\n } else {\n return { applied: false };\n }\n\n const rawIncludedValue = input[includeKey];\n if (typeof rawIncludedValue !== 'string') {\n throw new Error(`${includeKey} include value is not a string`);\n }\n\n const substituteResults = await substitute(rawIncludedValue, baseDir);\n const includeValue = substituteResults.applied\n ? substituteResults.value\n : rawIncludedValue;\n\n // The second string check is needed for Typescript to know this is a string.\n if (includeValue === undefined || typeof includeValue !== 'string') {\n throw new Error(`${includeKey} substitution value was undefined`);\n }\n\n switch (includeKey) {\n case '$file':\n try {\n const value = await readFile(resolvePath(baseDir, includeValue));\n return { applied: true, value };\n } catch (error) {\n throw new Error(`failed to read file ${includeValue}, ${error}`);\n }\n case '$env':\n try {\n return { applied: true, value: await env(includeValue) };\n } catch (error) {\n throw new Error(`failed to read env ${includeValue}, ${error}`);\n }\n\n case '$include': {\n const [filePath, dataPath] = includeValue.split(/#(.*)/);\n\n const ext = extname(filePath);\n const parser = includeFileParser[ext];\n if (!parser) {\n throw new Error(\n `no configuration parser available for included file ${filePath}`,\n );\n }\n\n const path = resolvePath(baseDir, filePath);\n const content = await readFile(path);\n const newBaseDir = dirname(path);\n\n const parts = dataPath ? dataPath.split('.') : [];\n\n let value: JsonValue | undefined;\n try {\n value = await parser(content);\n } catch (error) {\n throw new Error(\n `failed to parse included file ${filePath}, ${error}`,\n );\n }\n\n // This bit handles selecting a subtree in the included file, if a path was provided after a #\n for (const [index, part] of parts.entries()) {\n if (!isObject(value)) {\n const errPath = parts.slice(0, index).join('.');\n throw new Error(\n `value at '${errPath}' in included file ${filePath} is not an object`,\n );\n }\n value = value[part];\n }\n\n return {\n applied: true,\n value,\n newBaseDir: newBaseDir !== baseDir ? newBaseDir : undefined,\n };\n }\n\n default:\n throw new Error(`unknown include ${includeKey}`);\n }\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue } from '@backstage/types';\nimport { TransformFunc, EnvFunc } from './types';\n\n/**\n * A environment variable substitution transform that transforms e.g. 'token ${MY_TOKEN}'\n * to 'token abc' if MY_TOKEN is 'abc'. If any of the substituted variables are undefined,\n * the entire expression ends up undefined.\n */\nexport function createSubstitutionTransform(env: EnvFunc): TransformFunc {\n return async (input: JsonValue) => {\n if (typeof input !== 'string') {\n return { applied: false };\n }\n\n const parts: (string | undefined)[] = input.split(/(\\$?\\$\\{[^{}]*\\})/);\n for (let i = 1; i < parts.length; i += 2) {\n const part = parts[i]!;\n if (part.startsWith('$$')) {\n parts[i] = part.slice(1);\n } else {\n parts[i] = await env(part.slice(2, -1).trim());\n }\n }\n\n if (parts.some(part => part === undefined)) {\n return { applied: true, value: undefined };\n }\n return { applied: true, value: parts.join('') };\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\n\n/**\n * An sub-set of configuration schema.\n */\nexport type ConfigSchemaPackageEntry = {\n /**\n * The configuration schema itself.\n */\n value: JsonObject;\n /**\n * The relative path that the configuration schema was discovered at.\n */\n path: string;\n};\n\n/**\n * A list of all possible configuration value visibilities.\n */\nexport const CONFIG_VISIBILITIES = ['frontend', 'backend', 'secret'] as const;\n\n/**\n * A type representing the possible configuration value visibilities\n *\n * @public\n */\nexport type ConfigVisibility = 'frontend' | 'backend' | 'secret';\n\n/**\n * The default configuration visibility if no other values is given.\n */\nexport const DEFAULT_CONFIG_VISIBILITY: ConfigVisibility = 'backend';\n\n/**\n * An explanation of a configuration validation error.\n */\nexport type ValidationError = {\n keyword: string;\n dataPath: string;\n schemaPath: string;\n params: Record<string, any>;\n propertyName?: string;\n message?: string;\n};\n\n/**\n * The result of validating configuration data using a schema.\n */\ntype ValidationResult = {\n /**\n * Errors that where emitted during validation, if any.\n */\n errors?: ValidationError[];\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/<key>/<sub-key>/<array-index>/<leaf-key>`\n */\n visibilityByDataPath: Map<string, ConfigVisibility>;\n\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/properties/<key>/items/additionalProperties/<leaf-key>`\n */\n visibilityBySchemaPath: Map<string, ConfigVisibility>;\n};\n\n/**\n * A function used validate configuration data.\n */\nexport type ValidationFunc = (configs: AppConfig[]) => ValidationResult;\n\n/**\n * A function used to transform primitive configuration values.\n *\n * @public\n */\nexport type TransformFunc<T extends number | string | boolean> = (\n value: T,\n context: { visibility: ConfigVisibility },\n) => T | undefined;\n\n/**\n * Options used to process configuration data with a schema.\n *\n * @public\n */\nexport type ConfigSchemaProcessingOptions = {\n /**\n * The visibilities that should be included in the output data.\n * If omitted, the data will not be filtered by visibility.\n */\n visibility?: ConfigVisibility[];\n\n /**\n * A transform function that can be used to transform primitive configuration values\n * during validation. The value returned from the transform function will be used\n * instead of the original value. If the transform returns `undefined`, the value\n * will be omitted.\n */\n valueTransform?: TransformFunc<any>;\n\n /**\n * Whether or not to include the `filteredKeys` property in the output `AppConfig`s.\n *\n * Default: `false`.\n */\n withFilteredKeys?: boolean;\n};\n\n/**\n * A loaded configuration schema that is ready to process configuration data.\n *\n * @public\n */\nexport type ConfigSchema = {\n process(\n appConfigs: AppConfig[],\n options?: ConfigSchemaProcessingOptions,\n ): AppConfig[];\n\n serialize(): JsonObject;\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Ajv from 'ajv';\nimport { JSONSchema7 as JSONSchema } from 'json-schema';\nimport mergeAllOf, { Resolvers } from 'json-schema-merge-allof';\nimport traverse from 'json-schema-traverse';\nimport { ConfigReader } from '@backstage/config';\nimport {\n ConfigSchemaPackageEntry,\n ValidationFunc,\n CONFIG_VISIBILITIES,\n ConfigVisibility,\n} from './types';\n\n/**\n * This takes a collection of Backstage configuration schemas from various\n * sources and compiles them down into a single schema validation function.\n *\n * It also handles the implementation of the custom \"visibility\" keyword used\n * to specify the scope of different config paths.\n */\nexport function compileConfigSchemas(\n schemas: ConfigSchemaPackageEntry[],\n): ValidationFunc {\n // The ajv instance below is stateful and doesn't really allow for additional\n // output during validation. We work around this by having this extra piece\n // of state that we reset before each validation.\n const visibilityByDataPath = new Map<string, ConfigVisibility>();\n\n const ajv = new Ajv({\n allErrors: true,\n allowUnionTypes: true,\n schemas: {\n 'https://backstage.io/schema/config-v1': true,\n },\n }).addKeyword({\n keyword: 'visibility',\n metaSchema: {\n type: 'string',\n enum: CONFIG_VISIBILITIES,\n },\n compile(visibility: ConfigVisibility) {\n return (_data, context) => {\n if (context?.dataPath === undefined) {\n return false;\n }\n if (visibility && visibility !== 'backend') {\n const normalizedPath = context.dataPath.replace(\n /\\['?(.*?)'?\\]/g,\n (_, segment) => `/${segment}`,\n );\n visibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n });\n\n for (const schema of schemas) {\n try {\n ajv.compile(schema.value);\n } catch (error) {\n throw new Error(`Schema at ${schema.path} is invalid, ${error}`);\n }\n }\n\n const merged = mergeConfigSchemas(schemas.map(_ => _.value));\n const validate = ajv.compile(merged);\n\n const visibilityBySchemaPath = new Map<string, ConfigVisibility>();\n traverse(merged, (schema, path) => {\n if (schema.visibility && schema.visibility !== 'backend') {\n visibilityBySchemaPath.set(path, schema.visibility);\n }\n });\n\n return configs => {\n const config = ConfigReader.fromConfigs(configs).get();\n\n visibilityByDataPath.clear();\n\n const valid = validate(config);\n if (!valid) {\n return {\n errors: validate.errors ?? [],\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n }\n\n return {\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n };\n}\n\n/**\n * Given a list of configuration schemas from packages, merge them\n * into a single json schema.\n *\n * @public\n */\nexport function mergeConfigSchemas(schemas: JSONSchema[]): JSONSchema {\n const merged = mergeAllOf(\n { allOf: schemas },\n {\n // JSONSchema is typically subtractive, as in it always reduces the set of allowed\n // inputs through constraints. This changes the object property merging to be additive\n // rather than subtractive.\n ignoreAdditionalProperties: true,\n resolvers: {\n // This ensures that the visibilities across different schemas are sound, and\n // selects the most specific visibility for each path.\n visibility(values: string[], path: string[]) {\n const hasFrontend = values.some(_ => _ === 'frontend');\n const hasSecret = values.some(_ => _ === 'secret');\n if (hasFrontend && hasSecret) {\n throw new Error(\n `Config schema visibility is both 'frontend' and 'secret' for ${path.join(\n '/',\n )}`,\n );\n } else if (hasFrontend) {\n return 'frontend';\n } else if (hasSecret) {\n return 'secret';\n }\n\n return 'backend';\n },\n } as Partial<Resolvers<JSONSchema>>,\n },\n );\n return merged;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport {\n resolve as resolvePath,\n relative as relativePath,\n dirname,\n sep,\n} from 'path';\nimport { ConfigSchemaPackageEntry } from './types';\nimport { getProgramFromFiles, generateSchema } from 'typescript-json-schema';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\ntype Item = {\n name?: string;\n parentPath?: string;\n packagePath?: string;\n};\n\nconst req =\n typeof __non_webpack_require__ === 'undefined'\n ? require\n : __non_webpack_require__;\n\n/**\n * This collects all known config schemas across all dependencies of the app.\n */\nexport async function collectConfigSchemas(\n packageNames: string[],\n packagePaths: string[],\n): Promise<ConfigSchemaPackageEntry[]> {\n const schemas = new Array<ConfigSchemaPackageEntry>();\n const tsSchemaPaths = new Array<string>();\n const visitedPackageVersions = new Map<string, Set<string>>(); // pkgName: [versions...]\n\n const currentDir = await fs.realpath(process.cwd());\n\n async function processItem(item: Item) {\n let pkgPath = item.packagePath;\n\n if (pkgPath) {\n const pkgExists = await fs.pathExists(pkgPath);\n if (!pkgExists) {\n return;\n }\n } else if (item.name) {\n const { name, parentPath } = item;\n\n try {\n pkgPath = req.resolve(\n `${name}/package.json`,\n parentPath && {\n paths: [parentPath],\n },\n );\n } catch {\n // We can somewhat safely ignore packages that don't export package.json,\n // as they are likely not part of the Backstage ecosystem anyway.\n }\n }\n if (!pkgPath) {\n return;\n }\n\n const pkg = await fs.readJson(pkgPath);\n\n // Ensures that we only process the same version of each package once.\n let versions = visitedPackageVersions.get(pkg.name);\n if (versions?.has(pkg.version)) {\n return;\n }\n if (!versions) {\n versions = new Set();\n visitedPackageVersions.set(pkg.name, versions);\n }\n versions.add(pkg.version);\n\n const depNames = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ...Object.keys(pkg.optionalDependencies ?? {}),\n ...Object.keys(pkg.peerDependencies ?? {}),\n ];\n\n // TODO(Rugvip): Trying this out to avoid having to traverse the full dependency graph,\n // since that's pretty slow. We probably need a better way to determine when\n // we've left the Backstage ecosystem, but this will do for now.\n const hasSchema = 'configSchema' in pkg;\n const hasBackstageDep = depNames.some(_ => _.startsWith('@backstage/'));\n if (!hasSchema && !hasBackstageDep) {\n return;\n }\n if (hasSchema) {\n if (typeof pkg.configSchema === 'string') {\n const isJson = pkg.configSchema.endsWith('.json');\n const isDts = pkg.configSchema.endsWith('.d.ts');\n if (!isJson && !isDts) {\n throw new Error(\n `Config schema files must be .json or .d.ts, got ${pkg.configSchema}`,\n );\n }\n if (isDts) {\n tsSchemaPaths.push(\n relativePath(\n currentDir,\n resolvePath(dirname(pkgPath), pkg.configSchema),\n ),\n );\n } else {\n const path = resolvePath(dirname(pkgPath), pkg.configSchema);\n const value = await fs.readJson(path);\n schemas.push({\n value,\n path: relativePath(currentDir, path),\n });\n }\n } else {\n schemas.push({\n value: pkg.configSchema,\n path: relativePath(currentDir, pkgPath),\n });\n }\n }\n\n await Promise.all(\n depNames.map(depName =>\n processItem({ name: depName, parentPath: pkgPath }),\n ),\n );\n }\n\n await Promise.all([\n ...packageNames.map(name => processItem({ name, parentPath: currentDir })),\n ...packagePaths.map(path => processItem({ name: path, packagePath: path })),\n ]);\n\n const tsSchemas = compileTsSchemas(tsSchemaPaths);\n\n return schemas.concat(tsSchemas);\n}\n\n// This handles the support of TypeScript .d.ts config schema declarations.\n// We collect all typescript schema definition and compile them all in one go.\n// This is much faster than compiling them separately.\nfunction compileTsSchemas(paths: string[]) {\n if (paths.length === 0) {\n return [];\n }\n\n const program = getProgramFromFiles(paths, {\n incremental: false,\n isolatedModules: true,\n lib: ['ES5'], // Skipping most libs speeds processing up a lot, we just need the primitive types anyway\n noEmit: true,\n noResolve: true,\n skipLibCheck: true, // Skipping lib checks speeds things up\n skipDefaultLibCheck: true,\n strict: true,\n typeRoots: [], // Do not include any additional types\n types: [],\n });\n\n const tsSchemas = paths.map(path => {\n let value;\n try {\n value = generateSchema(\n program,\n // All schemas should export a `Config` symbol\n 'Config',\n // This enables usage of @visibility is doc comments\n {\n required: true,\n validationKeywords: ['visibility'],\n },\n [path.split(sep).join('/')], // Unix paths are expected for all OSes here\n ) as JsonObject | null;\n } catch (error) {\n assertError(error);\n if (error.message !== 'type Config not found') {\n throw error;\n }\n }\n\n if (!value) {\n throw new Error(`Invalid schema in ${path}, missing Config export`);\n }\n return { path, value };\n });\n\n return tsSchemas;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport {\n ConfigVisibility,\n DEFAULT_CONFIG_VISIBILITY,\n TransformFunc,\n ValidationError,\n} from './types';\n\n/**\n * This filters data by visibility by discovering the visibility of each\n * value, and then only keeping the ones that are specified in `includeVisibilities`.\n */\nexport function filterByVisibility(\n data: JsonObject,\n includeVisibilities: ConfigVisibility[],\n visibilityByDataPath: Map<string, ConfigVisibility>,\n transformFunc?: TransformFunc<number | string | boolean>,\n withFilteredKeys?: boolean,\n): { data: JsonObject; filteredKeys?: string[] } {\n const filteredKeys = new Array<string>();\n\n function transform(\n jsonVal: JsonValue,\n visibilityPath: string, // Matches the format we get from ajv\n filterPath: string, // Matches the format of the ConfigReader\n ): JsonValue | undefined {\n const visibility =\n visibilityByDataPath.get(visibilityPath) ?? DEFAULT_CONFIG_VISIBILITY;\n const isVisible = includeVisibilities.includes(visibility);\n\n if (typeof jsonVal !== 'object') {\n if (isVisible) {\n if (transformFunc) {\n return transformFunc(jsonVal, { visibility });\n }\n return jsonVal;\n }\n if (withFilteredKeys) {\n filteredKeys.push(filterPath);\n }\n return undefined;\n } else if (jsonVal === null) {\n return undefined;\n } else if (Array.isArray(jsonVal)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of jsonVal.entries()) {\n let path = visibilityPath;\n const hasVisibilityInIndex = visibilityByDataPath.get(\n `${visibilityPath}/${index}`,\n );\n\n if (hasVisibilityInIndex || typeof value === 'object') {\n path = `${visibilityPath}/${index}`;\n }\n\n const out = transform(value, path, `${filterPath}[${index}]`);\n\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n if (arr.length > 0 || isVisible) {\n return arr;\n }\n return undefined;\n }\n\n const outObj: JsonObject = {};\n let hasOutput = false;\n\n for (const [key, value] of Object.entries(jsonVal)) {\n if (value === undefined) {\n continue;\n }\n const out = transform(\n value,\n `${visibilityPath}/${key}`,\n filterPath ? `${filterPath}.${key}` : key,\n );\n if (out !== undefined) {\n outObj[key] = out;\n hasOutput = true;\n }\n }\n\n if (hasOutput || isVisible) {\n return outObj;\n }\n return undefined;\n }\n\n return {\n filteredKeys: withFilteredKeys ? filteredKeys : undefined,\n data: (transform(data, '', '') as JsonObject) ?? {},\n };\n}\n\nexport function filterErrorsByVisibility(\n errors: ValidationError[] | undefined,\n includeVisibilities: ConfigVisibility[] | undefined,\n visibilityByDataPath: Map<string, ConfigVisibility>,\n visibilityBySchemaPath: Map<string, ConfigVisibility>,\n): ValidationError[] {\n if (!errors) {\n return [];\n }\n if (!includeVisibilities) {\n return errors;\n }\n\n const visibleSchemaPaths = Array.from(visibilityBySchemaPath)\n .filter(([, v]) => includeVisibilities.includes(v))\n .map(([k]) => k);\n\n // If we're filtering by visibility we only care about the errors that happened\n // in a visible path.\n return errors.filter(error => {\n // We always include structural errors as we don't know whether there are\n // any visible paths within the structures.\n if (\n error.keyword === 'type' &&\n ['object', 'array'].includes(error.params.type)\n ) {\n return true;\n }\n\n // For fields that were required we use the schema path to determine whether\n // it was visible in addition to the data path. This is because the data path\n // visibilities are only populated for values that we reached, which we won't\n // if the value is missing.\n // We don't use this method for all the errors as the data path is more robust\n // and doesn't require us to properly trim the schema path.\n if (error.keyword === 'required') {\n const trimmedPath = error.schemaPath.slice(1, -'/required'.length);\n const fullPath = `${trimmedPath}/properties/${error.params.missingProperty}`;\n if (\n visibleSchemaPaths.some(visiblePath => visiblePath.startsWith(fullPath))\n ) {\n return true;\n }\n }\n\n const vis =\n visibilityByDataPath.get(error.dataPath) ?? DEFAULT_CONFIG_VISIBILITY;\n return vis && includeVisibilities.includes(vis);\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { compileConfigSchemas } from './compile';\nimport { collectConfigSchemas } from './collect';\nimport { filterByVisibility, filterErrorsByVisibility } from './filtering';\nimport {\n ValidationError,\n ConfigSchema,\n ConfigSchemaPackageEntry,\n CONFIG_VISIBILITIES,\n} from './types';\n\n/**\n * Options that control the loading of configuration schema files in the backend.\n *\n * @public\n */\nexport type LoadConfigSchemaOptions =\n | {\n dependencies: string[];\n packagePaths?: string[];\n }\n | {\n serialized: JsonObject;\n };\n\nfunction errorsToError(errors: ValidationError[]): Error {\n const messages = errors.map(({ dataPath, message, params }) => {\n const paramStr = Object.entries(params)\n .map(([name, value]) => `${name}=${value}`)\n .join(' ');\n return `Config ${message || ''} { ${paramStr} } at ${dataPath}`;\n });\n const error = new Error(`Config validation failed, ${messages.join('; ')}`);\n (error as any).messages = messages;\n return error;\n}\n\n/**\n * Loads config schema for a Backstage instance.\n *\n * @public\n */\nexport async function loadConfigSchema(\n options: LoadConfigSchemaOptions,\n): Promise<ConfigSchema> {\n let schemas: ConfigSchemaPackageEntry[];\n\n if ('dependencies' in options) {\n schemas = await collectConfigSchemas(\n options.dependencies,\n options.packagePaths ?? [],\n );\n } else {\n const { serialized } = options;\n if (serialized?.backstageConfigSchemaVersion !== 1) {\n throw new Error(\n 'Serialized configuration schema is invalid or has an invalid version number',\n );\n }\n schemas = serialized.schemas as ConfigSchemaPackageEntry[];\n }\n\n const validate = compileConfigSchemas(schemas);\n\n return {\n process(\n configs: AppConfig[],\n { visibility, valueTransform, withFilteredKeys } = {},\n ): AppConfig[] {\n const result = validate(configs);\n\n const visibleErrors = filterErrorsByVisibility(\n result.errors,\n visibility,\n result.visibilityByDataPath,\n result.visibilityBySchemaPath,\n );\n if (visibleErrors.length > 0) {\n throw errorsToError(visibleErrors);\n }\n\n let processedConfigs = configs;\n\n if (visibility) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n visibility,\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n } else if (valueTransform) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n Array.from(CONFIG_VISIBILITIES),\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n }\n\n return processedConfigs;\n },\n serialize(): JsonObject {\n return {\n schemas,\n backstageConfigSchemaVersion: 1,\n };\n },\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport function isValidUrl(url: string): boolean {\n try {\n // eslint-disable-next-line no-new\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport yaml from 'yaml';\nimport chokidar from 'chokidar';\nimport { basename, dirname, isAbsolute, resolve as resolvePath } from 'path';\nimport { AppConfig } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport {\n applyConfigTransforms,\n createIncludeTransform,\n createSubstitutionTransform,\n isValidUrl,\n readEnvConfig,\n} from './lib';\nimport fetch from 'node-fetch';\n\nexport type ConfigTarget = { path: string } | { url: string };\n\nexport type LoadConfigOptionsWatch = {\n /**\n * A listener that is called when a config file is changed.\n */\n onChange: (configs: AppConfig[]) => void;\n\n /**\n * An optional signal that stops the watcher once the promise resolves.\n */\n stopSignal?: Promise<void>;\n};\n\nexport type LoadConfigOptionsRemote = {\n /**\n * A remote config reloading period, in seconds\n */\n reloadIntervalSeconds: number;\n};\n\n/**\n * Options that control the loading of configuration files in the backend.\n *\n * @public\n */\nexport type LoadConfigOptions = {\n // The root directory of the config loading context. Used to find default configs.\n configRoot: string;\n\n // Paths to load config files from. Configs from earlier paths have lower priority.\n configTargets: ConfigTarget[];\n\n /**\n * Custom environment variable loading function\n *\n * @experimental This API is not stable and may change at any point\n */\n experimentalEnvFunc?: (name: string) => Promise<string | undefined>;\n\n /**\n * An optional remote config\n */\n remote?: LoadConfigOptionsRemote;\n\n /**\n * An optional configuration that enables watching of config files.\n */\n watch?: LoadConfigOptionsWatch;\n};\n\n/**\n * Results of loading configuration files.\n * @public\n */\nexport type LoadConfigResult = {\n /**\n * Array of all loaded configs.\n */\n appConfigs: AppConfig[];\n};\n\n/**\n * Load configuration data.\n *\n * @public\n */\nexport async function loadConfig(\n options: LoadConfigOptions,\n): Promise<LoadConfigResult> {\n const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options;\n\n const configPaths: string[] = options.configTargets\n .slice()\n .filter((e): e is { path: string } => e.hasOwnProperty('path'))\n .map(configTarget => configTarget.path);\n\n const configUrls: string[] = options.configTargets\n .slice()\n .filter((e): e is { url: string } => e.hasOwnProperty('url'))\n .map(configTarget => configTarget.url);\n\n if (remote === undefined) {\n if (configUrls.length > 0) {\n throw new Error(\n `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.`,\n );\n }\n } else if (remote.reloadIntervalSeconds <= 0) {\n throw new Error(\n `Remote config must be contain a non zero reloadIntervalSeconds: <seconds> value`,\n );\n }\n\n // If no paths are provided, we default to reading\n // `app-config.yaml` and, if it exists, `app-config.local.yaml`\n if (configPaths.length === 0 && configUrls.length === 0) {\n configPaths.push(resolvePath(configRoot, 'app-config.yaml'));\n\n const localConfig = resolvePath(configRoot, 'app-config.local.yaml');\n if (await fs.pathExists(localConfig)) {\n configPaths.push(localConfig);\n }\n }\n\n const env = envFunc ?? (async (name: string) => process.env[name]);\n\n const loadConfigFiles = async () => {\n const configs = [];\n\n for (const configPath of configPaths) {\n if (!isAbsolute(configPath)) {\n throw new Error(`Config load path is not absolute: '${configPath}'`);\n }\n\n const dir = dirname(configPath);\n const readFile = (path: string) =>\n fs.readFile(resolvePath(dir, path), 'utf8');\n\n const input = yaml.parse(await readFile(configPath));\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(dir, input, [\n createIncludeTransform(env, readFile, substitutionTransform),\n substitutionTransform,\n ]);\n\n configs.push({ data, context: basename(configPath) });\n }\n\n return configs;\n };\n\n const loadRemoteConfigFiles = async () => {\n const configs: AppConfig[] = [];\n\n const readConfigFromUrl = async (url: string) => {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Could not read config file at ${url}`);\n }\n\n return await response.text();\n };\n\n for (let i = 0; i < configUrls.length; i++) {\n const configUrl = configUrls[i];\n if (!isValidUrl(configUrl)) {\n throw new Error(`Config load path is not valid: '${configUrl}'`);\n }\n\n const remoteConfigContent = await readConfigFromUrl(configUrl);\n if (!remoteConfigContent) {\n throw new Error(`Config is not valid`);\n }\n const configYaml = yaml.parse(remoteConfigContent);\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(configRoot, configYaml, [\n substitutionTransform,\n ]);\n\n configs.push({ data, context: configUrl });\n }\n\n return configs;\n };\n\n let fileConfigs: AppConfig[];\n try {\n fileConfigs = await loadConfigFiles();\n } catch (error) {\n throw new ForwardedError('Failed to read static configuration file', error);\n }\n\n let remoteConfigs: AppConfig[] = [];\n if (remote) {\n try {\n remoteConfigs = await loadRemoteConfigFiles();\n } catch (error) {\n throw new ForwardedError(\n `Failed to read remote configuration file`,\n error,\n );\n }\n }\n\n const envConfigs = await readEnvConfig(process.env);\n\n const watchConfigFile = (watchProp: LoadConfigOptionsWatch) => {\n const watcher = chokidar.watch(configPaths, {\n usePolling: process.env.NODE_ENV === 'test',\n });\n\n let currentSerializedConfig = JSON.stringify(fileConfigs);\n watcher.on('change', async () => {\n try {\n const newConfigs = await loadConfigFiles();\n const newSerializedConfig = JSON.stringify(newConfigs);\n\n if (currentSerializedConfig === newSerializedConfig) {\n return;\n }\n currentSerializedConfig = newSerializedConfig;\n\n watchProp.onChange([...remoteConfigs, ...newConfigs, ...envConfigs]);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n });\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n watcher.close();\n });\n }\n };\n\n const watchRemoteConfig = (\n watchProp: LoadConfigOptionsWatch,\n remoteProp: LoadConfigOptionsRemote,\n ) => {\n const hasConfigChanged = async (\n oldRemoteConfigs: AppConfig[],\n newRemoteConfigs: AppConfig[],\n ) => {\n return (\n JSON.stringify(oldRemoteConfigs) !== JSON.stringify(newRemoteConfigs)\n );\n };\n\n let handle: NodeJS.Timeout | undefined;\n try {\n handle = setInterval(async () => {\n console.info(`Checking for config update`);\n const newRemoteConfigs = await loadRemoteConfigFiles();\n if (await hasConfigChanged(remoteConfigs, newRemoteConfigs)) {\n remoteConfigs = newRemoteConfigs;\n console.info(`Remote config change, reloading config ...`);\n watchProp.onChange([...remoteConfigs, ...fileConfigs, ...envConfigs]);\n console.info(`Remote config reloaded`);\n }\n }, remoteProp.reloadIntervalSeconds * 1000);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n if (handle !== undefined) {\n console.info(`Stopping remote config watch`);\n clearInterval(handle);\n handle = undefined;\n }\n });\n }\n };\n\n // Set up config file watching if requested by the caller\n if (watch) {\n watchConfigFile(watch);\n }\n\n if (watch && remote) {\n watchRemoteConfig(watch, remote);\n }\n\n return {\n appConfigs: remote\n ? [...remoteConfigs, ...fileConfigs, ...envConfigs]\n : [...fileConfigs, ...envConfigs],\n };\n}\n"],"names":["yaml","resolvePath","extname","path","dirname","Ajv","config","ConfigReader","mergeAllOf","fs","relativePath","getProgramFromFiles","generateSchema","sep","isAbsolute","basename","fetch","ForwardedError","chokidar"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -118,7 +118,7 @@ declare type LoadConfigOptionsWatch = {
118
118
  };
119
119
  declare type LoadConfigOptionsRemote = {
120
120
  /**
121
- * An optional remote config reloading period, in seconds
121
+ * A remote config reloading period, in seconds
122
122
  */
123
123
  reloadIntervalSeconds: number;
124
124
  };
@@ -129,13 +129,7 @@ declare type LoadConfigOptionsRemote = {
129
129
  */
130
130
  declare type LoadConfigOptions = {
131
131
  configRoot: string;
132
- /** Absolute paths to load config files from. Configs from earlier paths have lower priority.
133
- * @deprecated Use {@link configTargets} instead.
134
- */
135
- configPaths: string[];
136
132
  configTargets: ConfigTarget[];
137
- /** @deprecated This option has been removed */
138
- env?: string;
139
133
  /**
140
134
  * Custom environment variable loading function
141
135
  *
package/dist/index.esm.js CHANGED
@@ -50,7 +50,7 @@ function readEnvConfig(env) {
50
50
  }
51
51
  }
52
52
  }
53
- return data ? [{data, context: "env"}] : [];
53
+ return data ? [{ data, context: "env" }] : [];
54
54
  }
55
55
  function safeJsonParse(str) {
56
56
  try {
@@ -131,7 +131,7 @@ const includeFileParser = {
131
131
  function createIncludeTransform(env, readFile, substitute) {
132
132
  return async (input, baseDir) => {
133
133
  if (!isObject(input)) {
134
- return {applied: false};
134
+ return { applied: false };
135
135
  }
136
136
  const [includeKey] = Object.keys(input).filter((key) => key.startsWith("$"));
137
137
  if (includeKey) {
@@ -139,7 +139,7 @@ function createIncludeTransform(env, readFile, substitute) {
139
139
  throw new Error(`include key ${includeKey} should not have adjacent keys`);
140
140
  }
141
141
  } else {
142
- return {applied: false};
142
+ return { applied: false };
143
143
  }
144
144
  const rawIncludedValue = input[includeKey];
145
145
  if (typeof rawIncludedValue !== "string") {
@@ -154,13 +154,13 @@ function createIncludeTransform(env, readFile, substitute) {
154
154
  case "$file":
155
155
  try {
156
156
  const value = await readFile(resolve(baseDir, includeValue));
157
- return {applied: true, value};
157
+ return { applied: true, value };
158
158
  } catch (error) {
159
159
  throw new Error(`failed to read file ${includeValue}, ${error}`);
160
160
  }
161
161
  case "$env":
162
162
  try {
163
- return {applied: true, value: await env(includeValue)};
163
+ return { applied: true, value: await env(includeValue) };
164
164
  } catch (error) {
165
165
  throw new Error(`failed to read env ${includeValue}, ${error}`);
166
166
  }
@@ -203,7 +203,7 @@ function createIncludeTransform(env, readFile, substitute) {
203
203
  function createSubstitutionTransform(env) {
204
204
  return async (input) => {
205
205
  if (typeof input !== "string") {
206
- return {applied: false};
206
+ return { applied: false };
207
207
  }
208
208
  const parts = input.split(/(\$?\$\{[^{}]*\})/);
209
209
  for (let i = 1; i < parts.length; i += 2) {
@@ -215,9 +215,9 @@ function createSubstitutionTransform(env) {
215
215
  }
216
216
  }
217
217
  if (parts.some((part) => part === void 0)) {
218
- return {applied: true, value: void 0};
218
+ return { applied: true, value: void 0 };
219
219
  }
220
- return {applied: true, value: parts.join("")};
220
+ return { applied: true, value: parts.join("") };
221
221
  };
222
222
  }
223
223
 
@@ -225,7 +225,7 @@ const CONFIG_VISIBILITIES = ["frontend", "backend", "secret"];
225
225
  const DEFAULT_CONFIG_VISIBILITY = "backend";
226
226
 
227
227
  function compileConfigSchemas(schemas) {
228
- const visibilityByDataPath = new Map();
228
+ const visibilityByDataPath = /* @__PURE__ */ new Map();
229
229
  const ajv = new Ajv({
230
230
  allErrors: true,
231
231
  allowUnionTypes: true,
@@ -260,7 +260,7 @@ function compileConfigSchemas(schemas) {
260
260
  }
261
261
  const merged = mergeConfigSchemas(schemas.map((_) => _.value));
262
262
  const validate = ajv.compile(merged);
263
- const visibilityBySchemaPath = new Map();
263
+ const visibilityBySchemaPath = /* @__PURE__ */ new Map();
264
264
  traverse(merged, (schema, path) => {
265
265
  if (schema.visibility && schema.visibility !== "backend") {
266
266
  visibilityBySchemaPath.set(path, schema.visibility);
@@ -285,7 +285,7 @@ function compileConfigSchemas(schemas) {
285
285
  };
286
286
  }
287
287
  function mergeConfigSchemas(schemas) {
288
- const merged = mergeAllOf({allOf: schemas}, {
288
+ const merged = mergeAllOf({ allOf: schemas }, {
289
289
  ignoreAdditionalProperties: true,
290
290
  resolvers: {
291
291
  visibility(values, path) {
@@ -309,7 +309,7 @@ const req = typeof __non_webpack_require__ === "undefined" ? require : __non_web
309
309
  async function collectConfigSchemas(packageNames, packagePaths) {
310
310
  const schemas = new Array();
311
311
  const tsSchemaPaths = new Array();
312
- const visitedPackageVersions = new Map();
312
+ const visitedPackageVersions = /* @__PURE__ */ new Map();
313
313
  const currentDir = await fs.realpath(process.cwd());
314
314
  async function processItem(item) {
315
315
  var _a, _b, _c, _d;
@@ -320,7 +320,7 @@ async function collectConfigSchemas(packageNames, packagePaths) {
320
320
  return;
321
321
  }
322
322
  } else if (item.name) {
323
- const {name, parentPath} = item;
323
+ const { name, parentPath } = item;
324
324
  try {
325
325
  pkgPath = req.resolve(`${name}/package.json`, parentPath && {
326
326
  paths: [parentPath]
@@ -337,7 +337,7 @@ async function collectConfigSchemas(packageNames, packagePaths) {
337
337
  return;
338
338
  }
339
339
  if (!versions) {
340
- versions = new Set();
340
+ versions = /* @__PURE__ */ new Set();
341
341
  visitedPackageVersions.set(pkg.name, versions);
342
342
  }
343
343
  versions.add(pkg.version);
@@ -376,11 +376,11 @@ async function collectConfigSchemas(packageNames, packagePaths) {
376
376
  });
377
377
  }
378
378
  }
379
- await Promise.all(depNames.map((depName) => processItem({name: depName, parentPath: pkgPath})));
379
+ await Promise.all(depNames.map((depName) => processItem({ name: depName, parentPath: pkgPath })));
380
380
  }
381
381
  await Promise.all([
382
- ...packageNames.map((name) => processItem({name, parentPath: currentDir})),
383
- ...packagePaths.map((path) => processItem({name: path, packagePath: path}))
382
+ ...packageNames.map((name) => processItem({ name, parentPath: currentDir })),
383
+ ...packagePaths.map((path) => processItem({ name: path, packagePath: path }))
384
384
  ]);
385
385
  const tsSchemas = compileTsSchemas(tsSchemaPaths);
386
386
  return schemas.concat(tsSchemas);
@@ -417,7 +417,7 @@ function compileTsSchemas(paths) {
417
417
  if (!value) {
418
418
  throw new Error(`Invalid schema in ${path}, missing Config export`);
419
419
  }
420
- return {path, value};
420
+ return { path, value };
421
421
  });
422
422
  return tsSchemas;
423
423
  }
@@ -432,7 +432,7 @@ function filterByVisibility(data, includeVisibilities, visibilityByDataPath, tra
432
432
  if (typeof jsonVal !== "object") {
433
433
  if (isVisible) {
434
434
  if (transformFunc) {
435
- return transformFunc(jsonVal, {visibility});
435
+ return transformFunc(jsonVal, { visibility });
436
436
  }
437
437
  return jsonVal;
438
438
  }
@@ -508,7 +508,7 @@ function filterErrorsByVisibility(errors, includeVisibilities, visibilityByDataP
508
508
  }
509
509
 
510
510
  function errorsToError(errors) {
511
- const messages = errors.map(({dataPath, message, params}) => {
511
+ const messages = errors.map(({ dataPath, message, params }) => {
512
512
  const paramStr = Object.entries(params).map(([name, value]) => `${name}=${value}`).join(" ");
513
513
  return `Config ${message || ""} { ${paramStr} } at ${dataPath}`;
514
514
  });
@@ -522,7 +522,7 @@ async function loadConfigSchema(options) {
522
522
  if ("dependencies" in options) {
523
523
  schemas = await collectConfigSchemas(options.dependencies, (_a = options.packagePaths) != null ? _a : []);
524
524
  } else {
525
- const {serialized} = options;
525
+ const { serialized } = options;
526
526
  if ((serialized == null ? void 0 : serialized.backstageConfigSchemaVersion) !== 1) {
527
527
  throw new Error("Serialized configuration schema is invalid or has an invalid version number");
528
528
  }
@@ -530,7 +530,7 @@ async function loadConfigSchema(options) {
530
530
  }
531
531
  const validate = compileConfigSchemas(schemas);
532
532
  return {
533
- process(configs, {visibility, valueTransform, withFilteredKeys} = {}) {
533
+ process(configs, { visibility, valueTransform, withFilteredKeys } = {}) {
534
534
  const result = validate(configs);
535
535
  const visibleErrors = filterErrorsByVisibility(result.errors, visibility, result.visibilityByDataPath, result.visibilityBySchemaPath);
536
536
  if (visibleErrors.length > 0) {
@@ -538,12 +538,12 @@ async function loadConfigSchema(options) {
538
538
  }
539
539
  let processedConfigs = configs;
540
540
  if (visibility) {
541
- processedConfigs = processedConfigs.map(({data, context}) => ({
541
+ processedConfigs = processedConfigs.map(({ data, context }) => ({
542
542
  context,
543
543
  ...filterByVisibility(data, visibility, result.visibilityByDataPath, valueTransform, withFilteredKeys)
544
544
  }));
545
545
  } else if (valueTransform) {
546
- processedConfigs = processedConfigs.map(({data, context}) => ({
546
+ processedConfigs = processedConfigs.map(({ data, context }) => ({
547
547
  context,
548
548
  ...filterByVisibility(data, Array.from(CONFIG_VISIBILITIES), result.visibilityByDataPath, valueTransform, withFilteredKeys)
549
549
  }));
@@ -569,16 +569,15 @@ function isValidUrl(url) {
569
569
  }
570
570
 
571
571
  async function loadConfig(options) {
572
- const {configRoot, experimentalEnvFunc: envFunc, watch, remote} = options;
572
+ const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options;
573
573
  const configPaths = options.configTargets.slice().filter((e) => e.hasOwnProperty("path")).map((configTarget) => configTarget.path);
574
- options.configPaths.forEach((cp) => {
575
- if (!configPaths.includes(cp)) {
576
- configPaths.push(cp);
577
- }
578
- });
579
574
  const configUrls = options.configTargets.slice().filter((e) => e.hasOwnProperty("url")).map((configTarget) => configTarget.url);
580
- if (remote === void 0 && configUrls.length > 0) {
581
- throw new Error(`Remote config detected but this feature is turned off`);
575
+ if (remote === void 0) {
576
+ if (configUrls.length > 0) {
577
+ 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.`);
578
+ }
579
+ } else if (remote.reloadIntervalSeconds <= 0) {
580
+ throw new Error(`Remote config must be contain a non zero reloadIntervalSeconds: <seconds> value`);
582
581
  }
583
582
  if (configPaths.length === 0 && configUrls.length === 0) {
584
583
  configPaths.push(resolve(configRoot, "app-config.yaml"));
@@ -602,7 +601,7 @@ async function loadConfig(options) {
602
601
  createIncludeTransform(env, readFile, substitutionTransform),
603
602
  substitutionTransform
604
603
  ]);
605
- configs.push({data, context: basename(configPath)});
604
+ configs.push({ data, context: basename(configPath) });
606
605
  }
607
606
  return configs;
608
607
  };
@@ -629,7 +628,7 @@ async function loadConfig(options) {
629
628
  const data = await applyConfigTransforms(configRoot, configYaml, [
630
629
  substitutionTransform
631
630
  ]);
632
- configs.push({data, context: configUrl});
631
+ configs.push({ data, context: configUrl });
633
632
  }
634
633
  return configs;
635
634
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/lib/env.ts","../src/lib/transform/utils.ts","../src/lib/transform/apply.ts","../src/lib/transform/include.ts","../src/lib/transform/substitution.ts","../src/lib/schema/types.ts","../src/lib/schema/compile.ts","../src/lib/schema/collect.ts","../src/lib/schema/filtering.ts","../src/lib/schema/load.ts","../src/lib/urls.ts","../src/loader.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\nconst ENV_PREFIX = 'APP_CONFIG_';\n\n// Update the same pattern in config package if this is changed\nconst CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;\n\n/**\n * Read runtime configuration from the environment.\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n */\nexport function readEnvConfig(env: {\n [name: string]: string | undefined;\n}): AppConfig[] {\n let data: JsonObject | undefined = undefined;\n\n for (const [name, value] of Object.entries(env)) {\n if (!value) {\n continue;\n }\n if (name.startsWith(ENV_PREFIX)) {\n const key = name.replace(ENV_PREFIX, '');\n const keyParts = key.split('_');\n\n let obj = (data = data ?? {});\n for (const [index, part] of keyParts.entries()) {\n if (!CONFIG_KEY_PART_PATTERN.test(part)) {\n throw new TypeError(`Invalid env config key '${key}'`);\n }\n if (index < keyParts.length - 1) {\n obj = (obj[part] = obj[part] ?? {}) as JsonObject;\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n const subKey = keyParts.slice(0, index + 1).join('_');\n throw new TypeError(\n `Could not nest config for key '${key}' under existing value '${subKey}'`,\n );\n }\n } else {\n if (part in obj) {\n throw new TypeError(\n `Refusing to override existing config at key '${key}'`,\n );\n }\n try {\n const [, parsedValue] = safeJsonParse(value);\n if (parsedValue === null) {\n throw new Error('value may not be null');\n }\n obj[part] = parsedValue;\n } catch (error) {\n throw new TypeError(\n `Failed to parse JSON-serialized config value for key '${key}', ${error}`,\n );\n }\n }\n }\n }\n }\n\n return data ? [{ data, context: 'env' }] : [];\n}\n\nfunction safeJsonParse(str: string): [Error | null, any] {\n try {\n return [null, JSON.parse(str)];\n } catch (err) {\n assertError(err);\n return [err, str];\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue, JsonObject } from '@backstage/types';\n\nexport function isObject(obj: JsonValue | undefined): obj is JsonObject {\n if (typeof obj !== 'object') {\n return false;\n } else if (Array.isArray(obj)) {\n return false;\n }\n return obj !== null;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\nimport { TransformFunc } from './types';\nimport { isObject } from './utils';\n\n/**\n * Applies a set of transforms to raw configuration data.\n */\nexport async function applyConfigTransforms(\n initialDir: string,\n input: JsonValue,\n transforms: TransformFunc[],\n): Promise<JsonObject> {\n async function transform(\n inputObj: JsonValue,\n path: string,\n baseDir: string,\n ): Promise<JsonValue | undefined> {\n let obj = inputObj;\n let dir = baseDir;\n\n for (const tf of transforms) {\n try {\n const result = await tf(inputObj, baseDir);\n if (result.applied) {\n if (result.value === undefined) {\n return undefined;\n }\n obj = result.value;\n dir = result.newBaseDir ?? dir;\n break;\n }\n } catch (error) {\n assertError(error);\n throw new Error(`error at ${path}, ${error.message}`);\n }\n }\n\n if (typeof obj !== 'object') {\n return obj;\n } else if (obj === null) {\n return undefined;\n } else if (Array.isArray(obj)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of obj.entries()) {\n const out = await transform(value, `${path}[${index}]`, dir);\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n return arr;\n }\n\n const out: JsonObject = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // undefined covers optional fields\n if (value !== undefined) {\n const result = await transform(value, `${path}.${key}`, dir);\n if (result !== undefined) {\n out[key] = result;\n }\n }\n }\n\n return out;\n }\n\n const finalData = await transform(input, '', initialDir);\n if (!isObject(finalData)) {\n throw new TypeError('expected object at config root');\n }\n return finalData;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport yaml from 'yaml';\nimport { extname, dirname, resolve as resolvePath } from 'path';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { isObject } from './utils';\nimport { TransformFunc, EnvFunc, ReadFileFunc } from './types';\n\n// Parsers for each type of included file\nconst includeFileParser: {\n [ext in string]: (content: string) => Promise<JsonObject>;\n} = {\n '.json': async content => JSON.parse(content),\n '.yaml': async content => yaml.parse(content),\n '.yml': async content => yaml.parse(content),\n};\n\n/**\n * Transforms a include description into the actual included value.\n */\nexport function createIncludeTransform(\n env: EnvFunc,\n readFile: ReadFileFunc,\n substitute: TransformFunc,\n): TransformFunc {\n return async (input: JsonValue, baseDir: string) => {\n if (!isObject(input)) {\n return { applied: false };\n }\n // Check if there's any key that starts with a '$', in that case we treat\n // this entire object as an include description.\n const [includeKey] = Object.keys(input).filter(key => key.startsWith('$'));\n if (includeKey) {\n if (Object.keys(input).length !== 1) {\n throw new Error(\n `include key ${includeKey} should not have adjacent keys`,\n );\n }\n } else {\n return { applied: false };\n }\n\n const rawIncludedValue = input[includeKey];\n if (typeof rawIncludedValue !== 'string') {\n throw new Error(`${includeKey} include value is not a string`);\n }\n\n const substituteResults = await substitute(rawIncludedValue, baseDir);\n const includeValue = substituteResults.applied\n ? substituteResults.value\n : rawIncludedValue;\n\n // The second string check is needed for Typescript to know this is a string.\n if (includeValue === undefined || typeof includeValue !== 'string') {\n throw new Error(`${includeKey} substitution value was undefined`);\n }\n\n switch (includeKey) {\n case '$file':\n try {\n const value = await readFile(resolvePath(baseDir, includeValue));\n return { applied: true, value };\n } catch (error) {\n throw new Error(`failed to read file ${includeValue}, ${error}`);\n }\n case '$env':\n try {\n return { applied: true, value: await env(includeValue) };\n } catch (error) {\n throw new Error(`failed to read env ${includeValue}, ${error}`);\n }\n\n case '$include': {\n const [filePath, dataPath] = includeValue.split(/#(.*)/);\n\n const ext = extname(filePath);\n const parser = includeFileParser[ext];\n if (!parser) {\n throw new Error(\n `no configuration parser available for included file ${filePath}`,\n );\n }\n\n const path = resolvePath(baseDir, filePath);\n const content = await readFile(path);\n const newBaseDir = dirname(path);\n\n const parts = dataPath ? dataPath.split('.') : [];\n\n let value: JsonValue | undefined;\n try {\n value = await parser(content);\n } catch (error) {\n throw new Error(\n `failed to parse included file ${filePath}, ${error}`,\n );\n }\n\n // This bit handles selecting a subtree in the included file, if a path was provided after a #\n for (const [index, part] of parts.entries()) {\n if (!isObject(value)) {\n const errPath = parts.slice(0, index).join('.');\n throw new Error(\n `value at '${errPath}' in included file ${filePath} is not an object`,\n );\n }\n value = value[part];\n }\n\n return {\n applied: true,\n value,\n newBaseDir: newBaseDir !== baseDir ? newBaseDir : undefined,\n };\n }\n\n default:\n throw new Error(`unknown include ${includeKey}`);\n }\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue } from '@backstage/types';\nimport { TransformFunc, EnvFunc } from './types';\n\n/**\n * A environment variable substitution transform that transforms e.g. 'token ${MY_TOKEN}'\n * to 'token abc' if MY_TOKEN is 'abc'. If any of the substituted variables are undefined,\n * the entire expression ends up undefined.\n */\nexport function createSubstitutionTransform(env: EnvFunc): TransformFunc {\n return async (input: JsonValue) => {\n if (typeof input !== 'string') {\n return { applied: false };\n }\n\n const parts: (string | undefined)[] = input.split(/(\\$?\\$\\{[^{}]*\\})/);\n for (let i = 1; i < parts.length; i += 2) {\n const part = parts[i]!;\n if (part.startsWith('$$')) {\n parts[i] = part.slice(1);\n } else {\n parts[i] = await env(part.slice(2, -1).trim());\n }\n }\n\n if (parts.some(part => part === undefined)) {\n return { applied: true, value: undefined };\n }\n return { applied: true, value: parts.join('') };\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\n\n/**\n * An sub-set of configuration schema.\n */\nexport type ConfigSchemaPackageEntry = {\n /**\n * The configuration schema itself.\n */\n value: JsonObject;\n /**\n * The relative path that the configuration schema was discovered at.\n */\n path: string;\n};\n\n/**\n * A list of all possible configuration value visibilities.\n */\nexport const CONFIG_VISIBILITIES = ['frontend', 'backend', 'secret'] as const;\n\n/**\n * A type representing the possible configuration value visibilities\n *\n * @public\n */\nexport type ConfigVisibility = 'frontend' | 'backend' | 'secret';\n\n/**\n * The default configuration visibility if no other values is given.\n */\nexport const DEFAULT_CONFIG_VISIBILITY: ConfigVisibility = 'backend';\n\n/**\n * An explanation of a configuration validation error.\n */\nexport type ValidationError = {\n keyword: string;\n dataPath: string;\n schemaPath: string;\n params: Record<string, any>;\n propertyName?: string;\n message?: string;\n};\n\n/**\n * The result of validating configuration data using a schema.\n */\ntype ValidationResult = {\n /**\n * Errors that where emitted during validation, if any.\n */\n errors?: ValidationError[];\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/<key>/<sub-key>/<array-index>/<leaf-key>`\n */\n visibilityByDataPath: Map<string, ConfigVisibility>;\n\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/properties/<key>/items/additionalProperties/<leaf-key>`\n */\n visibilityBySchemaPath: Map<string, ConfigVisibility>;\n};\n\n/**\n * A function used validate configuration data.\n */\nexport type ValidationFunc = (configs: AppConfig[]) => ValidationResult;\n\n/**\n * A function used to transform primitive configuration values.\n *\n * @public\n */\nexport type TransformFunc<T extends number | string | boolean> = (\n value: T,\n context: { visibility: ConfigVisibility },\n) => T | undefined;\n\n/**\n * Options used to process configuration data with a schema.\n *\n * @public\n */\nexport type ConfigSchemaProcessingOptions = {\n /**\n * The visibilities that should be included in the output data.\n * If omitted, the data will not be filtered by visibility.\n */\n visibility?: ConfigVisibility[];\n\n /**\n * A transform function that can be used to transform primitive configuration values\n * during validation. The value returned from the transform function will be used\n * instead of the original value. If the transform returns `undefined`, the value\n * will be omitted.\n */\n valueTransform?: TransformFunc<any>;\n\n /**\n * Whether or not to include the `filteredKeys` property in the output `AppConfig`s.\n *\n * Default: `false`.\n */\n withFilteredKeys?: boolean;\n};\n\n/**\n * A loaded configuration schema that is ready to process configuration data.\n *\n * @public\n */\nexport type ConfigSchema = {\n process(\n appConfigs: AppConfig[],\n options?: ConfigSchemaProcessingOptions,\n ): AppConfig[];\n\n serialize(): JsonObject;\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Ajv from 'ajv';\nimport { JSONSchema7 as JSONSchema } from 'json-schema';\nimport mergeAllOf, { Resolvers } from 'json-schema-merge-allof';\nimport traverse from 'json-schema-traverse';\nimport { ConfigReader } from '@backstage/config';\nimport {\n ConfigSchemaPackageEntry,\n ValidationFunc,\n CONFIG_VISIBILITIES,\n ConfigVisibility,\n} from './types';\n\n/**\n * This takes a collection of Backstage configuration schemas from various\n * sources and compiles them down into a single schema validation function.\n *\n * It also handles the implementation of the custom \"visibility\" keyword used\n * to specify the scope of different config paths.\n */\nexport function compileConfigSchemas(\n schemas: ConfigSchemaPackageEntry[],\n): ValidationFunc {\n // The ajv instance below is stateful and doesn't really allow for additional\n // output during validation. We work around this by having this extra piece\n // of state that we reset before each validation.\n const visibilityByDataPath = new Map<string, ConfigVisibility>();\n\n const ajv = new Ajv({\n allErrors: true,\n allowUnionTypes: true,\n schemas: {\n 'https://backstage.io/schema/config-v1': true,\n },\n }).addKeyword({\n keyword: 'visibility',\n metaSchema: {\n type: 'string',\n enum: CONFIG_VISIBILITIES,\n },\n compile(visibility: ConfigVisibility) {\n return (_data, context) => {\n if (context?.dataPath === undefined) {\n return false;\n }\n if (visibility && visibility !== 'backend') {\n const normalizedPath = context.dataPath.replace(\n /\\['?(.*?)'?\\]/g,\n (_, segment) => `/${segment}`,\n );\n visibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n });\n\n for (const schema of schemas) {\n try {\n ajv.compile(schema.value);\n } catch (error) {\n throw new Error(`Schema at ${schema.path} is invalid, ${error}`);\n }\n }\n\n const merged = mergeConfigSchemas(schemas.map(_ => _.value));\n const validate = ajv.compile(merged);\n\n const visibilityBySchemaPath = new Map<string, ConfigVisibility>();\n traverse(merged, (schema, path) => {\n if (schema.visibility && schema.visibility !== 'backend') {\n visibilityBySchemaPath.set(path, schema.visibility);\n }\n });\n\n return configs => {\n const config = ConfigReader.fromConfigs(configs).get();\n\n visibilityByDataPath.clear();\n\n const valid = validate(config);\n if (!valid) {\n return {\n errors: validate.errors ?? [],\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n }\n\n return {\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n };\n}\n\n/**\n * Given a list of configuration schemas from packages, merge them\n * into a single json schema.\n *\n * @public\n */\nexport function mergeConfigSchemas(schemas: JSONSchema[]): JSONSchema {\n const merged = mergeAllOf(\n { allOf: schemas },\n {\n // JSONSchema is typically subtractive, as in it always reduces the set of allowed\n // inputs through constraints. This changes the object property merging to be additive\n // rather than subtractive.\n ignoreAdditionalProperties: true,\n resolvers: {\n // This ensures that the visibilities across different schemas are sound, and\n // selects the most specific visibility for each path.\n visibility(values: string[], path: string[]) {\n const hasFrontend = values.some(_ => _ === 'frontend');\n const hasSecret = values.some(_ => _ === 'secret');\n if (hasFrontend && hasSecret) {\n throw new Error(\n `Config schema visibility is both 'frontend' and 'secret' for ${path.join(\n '/',\n )}`,\n );\n } else if (hasFrontend) {\n return 'frontend';\n } else if (hasSecret) {\n return 'secret';\n }\n\n return 'backend';\n },\n } as Partial<Resolvers<JSONSchema>>,\n },\n );\n return merged;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport {\n resolve as resolvePath,\n relative as relativePath,\n dirname,\n sep,\n} from 'path';\nimport { ConfigSchemaPackageEntry } from './types';\nimport { getProgramFromFiles, generateSchema } from 'typescript-json-schema';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\ntype Item = {\n name?: string;\n parentPath?: string;\n packagePath?: string;\n};\n\nconst req =\n typeof __non_webpack_require__ === 'undefined'\n ? require\n : __non_webpack_require__;\n\n/**\n * This collects all known config schemas across all dependencies of the app.\n */\nexport async function collectConfigSchemas(\n packageNames: string[],\n packagePaths: string[],\n): Promise<ConfigSchemaPackageEntry[]> {\n const schemas = new Array<ConfigSchemaPackageEntry>();\n const tsSchemaPaths = new Array<string>();\n const visitedPackageVersions = new Map<string, Set<string>>(); // pkgName: [versions...]\n\n const currentDir = await fs.realpath(process.cwd());\n\n async function processItem(item: Item) {\n let pkgPath = item.packagePath;\n\n if (pkgPath) {\n const pkgExists = await fs.pathExists(pkgPath);\n if (!pkgExists) {\n return;\n }\n } else if (item.name) {\n const { name, parentPath } = item;\n\n try {\n pkgPath = req.resolve(\n `${name}/package.json`,\n parentPath && {\n paths: [parentPath],\n },\n );\n } catch {\n // We can somewhat safely ignore packages that don't export package.json,\n // as they are likely not part of the Backstage ecosystem anyway.\n }\n }\n if (!pkgPath) {\n return;\n }\n\n const pkg = await fs.readJson(pkgPath);\n\n // Ensures that we only process the same version of each package once.\n let versions = visitedPackageVersions.get(pkg.name);\n if (versions?.has(pkg.version)) {\n return;\n }\n if (!versions) {\n versions = new Set();\n visitedPackageVersions.set(pkg.name, versions);\n }\n versions.add(pkg.version);\n\n const depNames = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ...Object.keys(pkg.optionalDependencies ?? {}),\n ...Object.keys(pkg.peerDependencies ?? {}),\n ];\n\n // TODO(Rugvip): Trying this out to avoid having to traverse the full dependency graph,\n // since that's pretty slow. We probably need a better way to determine when\n // we've left the Backstage ecosystem, but this will do for now.\n const hasSchema = 'configSchema' in pkg;\n const hasBackstageDep = depNames.some(_ => _.startsWith('@backstage/'));\n if (!hasSchema && !hasBackstageDep) {\n return;\n }\n if (hasSchema) {\n if (typeof pkg.configSchema === 'string') {\n const isJson = pkg.configSchema.endsWith('.json');\n const isDts = pkg.configSchema.endsWith('.d.ts');\n if (!isJson && !isDts) {\n throw new Error(\n `Config schema files must be .json or .d.ts, got ${pkg.configSchema}`,\n );\n }\n if (isDts) {\n tsSchemaPaths.push(\n relativePath(\n currentDir,\n resolvePath(dirname(pkgPath), pkg.configSchema),\n ),\n );\n } else {\n const path = resolvePath(dirname(pkgPath), pkg.configSchema);\n const value = await fs.readJson(path);\n schemas.push({\n value,\n path: relativePath(currentDir, path),\n });\n }\n } else {\n schemas.push({\n value: pkg.configSchema,\n path: relativePath(currentDir, pkgPath),\n });\n }\n }\n\n await Promise.all(\n depNames.map(depName =>\n processItem({ name: depName, parentPath: pkgPath }),\n ),\n );\n }\n\n await Promise.all([\n ...packageNames.map(name => processItem({ name, parentPath: currentDir })),\n ...packagePaths.map(path => processItem({ name: path, packagePath: path })),\n ]);\n\n const tsSchemas = compileTsSchemas(tsSchemaPaths);\n\n return schemas.concat(tsSchemas);\n}\n\n// This handles the support of TypeScript .d.ts config schema declarations.\n// We collect all typescript schema definition and compile them all in one go.\n// This is much faster than compiling them separately.\nfunction compileTsSchemas(paths: string[]) {\n if (paths.length === 0) {\n return [];\n }\n\n const program = getProgramFromFiles(paths, {\n incremental: false,\n isolatedModules: true,\n lib: ['ES5'], // Skipping most libs speeds processing up a lot, we just need the primitive types anyway\n noEmit: true,\n noResolve: true,\n skipLibCheck: true, // Skipping lib checks speeds things up\n skipDefaultLibCheck: true,\n strict: true,\n typeRoots: [], // Do not include any additional types\n types: [],\n });\n\n const tsSchemas = paths.map(path => {\n let value;\n try {\n value = generateSchema(\n program,\n // All schemas should export a `Config` symbol\n 'Config',\n // This enables usage of @visibility is doc comments\n {\n required: true,\n validationKeywords: ['visibility'],\n },\n [path.split(sep).join('/')], // Unix paths are expected for all OSes here\n ) as JsonObject | null;\n } catch (error) {\n assertError(error);\n if (error.message !== 'type Config not found') {\n throw error;\n }\n }\n\n if (!value) {\n throw new Error(`Invalid schema in ${path}, missing Config export`);\n }\n return { path, value };\n });\n\n return tsSchemas;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport {\n ConfigVisibility,\n DEFAULT_CONFIG_VISIBILITY,\n TransformFunc,\n ValidationError,\n} from './types';\n\n/**\n * This filters data by visibility by discovering the visibility of each\n * value, and then only keeping the ones that are specified in `includeVisibilities`.\n */\nexport function filterByVisibility(\n data: JsonObject,\n includeVisibilities: ConfigVisibility[],\n visibilityByDataPath: Map<string, ConfigVisibility>,\n transformFunc?: TransformFunc<number | string | boolean>,\n withFilteredKeys?: boolean,\n): { data: JsonObject; filteredKeys?: string[] } {\n const filteredKeys = new Array<string>();\n\n function transform(\n jsonVal: JsonValue,\n visibilityPath: string, // Matches the format we get from ajv\n filterPath: string, // Matches the format of the ConfigReader\n ): JsonValue | undefined {\n const visibility =\n visibilityByDataPath.get(visibilityPath) ?? DEFAULT_CONFIG_VISIBILITY;\n const isVisible = includeVisibilities.includes(visibility);\n\n if (typeof jsonVal !== 'object') {\n if (isVisible) {\n if (transformFunc) {\n return transformFunc(jsonVal, { visibility });\n }\n return jsonVal;\n }\n if (withFilteredKeys) {\n filteredKeys.push(filterPath);\n }\n return undefined;\n } else if (jsonVal === null) {\n return undefined;\n } else if (Array.isArray(jsonVal)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of jsonVal.entries()) {\n let path = visibilityPath;\n const hasVisibilityInIndex = visibilityByDataPath.get(\n `${visibilityPath}/${index}`,\n );\n\n if (hasVisibilityInIndex || typeof value === 'object') {\n path = `${visibilityPath}/${index}`;\n }\n\n const out = transform(value, path, `${filterPath}[${index}]`);\n\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n if (arr.length > 0 || isVisible) {\n return arr;\n }\n return undefined;\n }\n\n const outObj: JsonObject = {};\n let hasOutput = false;\n\n for (const [key, value] of Object.entries(jsonVal)) {\n if (value === undefined) {\n continue;\n }\n const out = transform(\n value,\n `${visibilityPath}/${key}`,\n filterPath ? `${filterPath}.${key}` : key,\n );\n if (out !== undefined) {\n outObj[key] = out;\n hasOutput = true;\n }\n }\n\n if (hasOutput || isVisible) {\n return outObj;\n }\n return undefined;\n }\n\n return {\n filteredKeys: withFilteredKeys ? filteredKeys : undefined,\n data: (transform(data, '', '') as JsonObject) ?? {},\n };\n}\n\nexport function filterErrorsByVisibility(\n errors: ValidationError[] | undefined,\n includeVisibilities: ConfigVisibility[] | undefined,\n visibilityByDataPath: Map<string, ConfigVisibility>,\n visibilityBySchemaPath: Map<string, ConfigVisibility>,\n): ValidationError[] {\n if (!errors) {\n return [];\n }\n if (!includeVisibilities) {\n return errors;\n }\n\n const visibleSchemaPaths = Array.from(visibilityBySchemaPath)\n .filter(([, v]) => includeVisibilities.includes(v))\n .map(([k]) => k);\n\n // If we're filtering by visibility we only care about the errors that happened\n // in a visible path.\n return errors.filter(error => {\n // We always include structural errors as we don't know whether there are\n // any visible paths within the structures.\n if (\n error.keyword === 'type' &&\n ['object', 'array'].includes(error.params.type)\n ) {\n return true;\n }\n\n // For fields that were required we use the schema path to determine whether\n // it was visible in addition to the data path. This is because the data path\n // visibilities are only populated for values that we reached, which we won't\n // if the value is missing.\n // We don't use this method for all the errors as the data path is more robust\n // and doesn't require us to properly trim the schema path.\n if (error.keyword === 'required') {\n const trimmedPath = error.schemaPath.slice(1, -'/required'.length);\n const fullPath = `${trimmedPath}/properties/${error.params.missingProperty}`;\n if (\n visibleSchemaPaths.some(visiblePath => visiblePath.startsWith(fullPath))\n ) {\n return true;\n }\n }\n\n const vis =\n visibilityByDataPath.get(error.dataPath) ?? DEFAULT_CONFIG_VISIBILITY;\n return vis && includeVisibilities.includes(vis);\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { compileConfigSchemas } from './compile';\nimport { collectConfigSchemas } from './collect';\nimport { filterByVisibility, filterErrorsByVisibility } from './filtering';\nimport {\n ValidationError,\n ConfigSchema,\n ConfigSchemaPackageEntry,\n CONFIG_VISIBILITIES,\n} from './types';\n\n/**\n * Options that control the loading of configuration schema files in the backend.\n *\n * @public\n */\nexport type LoadConfigSchemaOptions =\n | {\n dependencies: string[];\n packagePaths?: string[];\n }\n | {\n serialized: JsonObject;\n };\n\nfunction errorsToError(errors: ValidationError[]): Error {\n const messages = errors.map(({ dataPath, message, params }) => {\n const paramStr = Object.entries(params)\n .map(([name, value]) => `${name}=${value}`)\n .join(' ');\n return `Config ${message || ''} { ${paramStr} } at ${dataPath}`;\n });\n const error = new Error(`Config validation failed, ${messages.join('; ')}`);\n (error as any).messages = messages;\n return error;\n}\n\n/**\n * Loads config schema for a Backstage instance.\n *\n * @public\n */\nexport async function loadConfigSchema(\n options: LoadConfigSchemaOptions,\n): Promise<ConfigSchema> {\n let schemas: ConfigSchemaPackageEntry[];\n\n if ('dependencies' in options) {\n schemas = await collectConfigSchemas(\n options.dependencies,\n options.packagePaths ?? [],\n );\n } else {\n const { serialized } = options;\n if (serialized?.backstageConfigSchemaVersion !== 1) {\n throw new Error(\n 'Serialized configuration schema is invalid or has an invalid version number',\n );\n }\n schemas = serialized.schemas as ConfigSchemaPackageEntry[];\n }\n\n const validate = compileConfigSchemas(schemas);\n\n return {\n process(\n configs: AppConfig[],\n { visibility, valueTransform, withFilteredKeys } = {},\n ): AppConfig[] {\n const result = validate(configs);\n\n const visibleErrors = filterErrorsByVisibility(\n result.errors,\n visibility,\n result.visibilityByDataPath,\n result.visibilityBySchemaPath,\n );\n if (visibleErrors.length > 0) {\n throw errorsToError(visibleErrors);\n }\n\n let processedConfigs = configs;\n\n if (visibility) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n visibility,\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n } else if (valueTransform) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n Array.from(CONFIG_VISIBILITIES),\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n }\n\n return processedConfigs;\n },\n serialize(): JsonObject {\n return {\n schemas,\n backstageConfigSchemaVersion: 1,\n };\n },\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport function isValidUrl(url: string): boolean {\n try {\n // eslint-disable-next-line no-new\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport yaml from 'yaml';\nimport chokidar from 'chokidar';\nimport { basename, dirname, isAbsolute, resolve as resolvePath } from 'path';\nimport { AppConfig } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport {\n applyConfigTransforms,\n createIncludeTransform,\n createSubstitutionTransform,\n isValidUrl,\n readEnvConfig,\n} from './lib';\nimport fetch from 'node-fetch';\n\nexport type ConfigTarget = { path: string } | { url: string };\n\nexport type LoadConfigOptionsWatch = {\n /**\n * A listener that is called when a config file is changed.\n */\n onChange: (configs: AppConfig[]) => void;\n\n /**\n * An optional signal that stops the watcher once the promise resolves.\n */\n stopSignal?: Promise<void>;\n};\n\nexport type LoadConfigOptionsRemote = {\n /**\n * An optional remote config reloading period, in seconds\n */\n reloadIntervalSeconds: number;\n};\n\n/**\n * Options that control the loading of configuration files in the backend.\n *\n * @public\n */\nexport type LoadConfigOptions = {\n // The root directory of the config loading context. Used to find default configs.\n configRoot: string;\n\n /** Absolute paths to load config files from. Configs from earlier paths have lower priority.\n * @deprecated Use {@link configTargets} instead.\n */\n configPaths: string[];\n\n // Paths to load config files from. Configs from earlier paths have lower priority.\n configTargets: ConfigTarget[];\n\n /** @deprecated This option has been removed */\n env?: string;\n\n /**\n * Custom environment variable loading function\n *\n * @experimental This API is not stable and may change at any point\n */\n experimentalEnvFunc?: (name: string) => Promise<string | undefined>;\n\n /**\n * An optional remote config\n */\n remote?: LoadConfigOptionsRemote;\n\n /**\n * An optional configuration that enables watching of config files.\n */\n watch?: LoadConfigOptionsWatch;\n};\n\n/**\n * Results of loading configuration files.\n * @public\n */\nexport type LoadConfigResult = {\n /**\n * Array of all loaded configs.\n */\n appConfigs: AppConfig[];\n};\n\n/**\n * Load configuration data.\n *\n * @public\n */\nexport async function loadConfig(\n options: LoadConfigOptions,\n): Promise<LoadConfigResult> {\n const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options;\n\n const configPaths: string[] = options.configTargets\n .slice()\n .filter((e): e is { path: string } => e.hasOwnProperty('path'))\n .map(configTarget => configTarget.path);\n\n // Append deprecated configPaths to the absolute config paths received via configTargets.\n options.configPaths.forEach(cp => {\n if (!configPaths.includes(cp)) {\n configPaths.push(cp);\n }\n });\n\n const configUrls: string[] = options.configTargets\n .slice()\n .filter((e): e is { url: string } => e.hasOwnProperty('url'))\n .map(configTarget => configTarget.url);\n\n if (remote === undefined && configUrls.length > 0) {\n throw new Error(`Remote config detected but this feature is turned off`);\n }\n\n // If no paths are provided, we default to reading\n // `app-config.yaml` and, if it exists, `app-config.local.yaml`\n if (configPaths.length === 0 && configUrls.length === 0) {\n configPaths.push(resolvePath(configRoot, 'app-config.yaml'));\n\n const localConfig = resolvePath(configRoot, 'app-config.local.yaml');\n if (await fs.pathExists(localConfig)) {\n configPaths.push(localConfig);\n }\n }\n\n const env = envFunc ?? (async (name: string) => process.env[name]);\n\n const loadConfigFiles = async () => {\n const configs = [];\n\n for (const configPath of configPaths) {\n if (!isAbsolute(configPath)) {\n throw new Error(`Config load path is not absolute: '${configPath}'`);\n }\n\n const dir = dirname(configPath);\n const readFile = (path: string) =>\n fs.readFile(resolvePath(dir, path), 'utf8');\n\n const input = yaml.parse(await readFile(configPath));\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(dir, input, [\n createIncludeTransform(env, readFile, substitutionTransform),\n substitutionTransform,\n ]);\n\n configs.push({ data, context: basename(configPath) });\n }\n\n return configs;\n };\n\n const loadRemoteConfigFiles = async () => {\n const configs: AppConfig[] = [];\n\n const readConfigFromUrl = async (url: string) => {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Could not read config file at ${url}`);\n }\n\n return await response.text();\n };\n\n for (let i = 0; i < configUrls.length; i++) {\n const configUrl = configUrls[i];\n if (!isValidUrl(configUrl)) {\n throw new Error(`Config load path is not valid: '${configUrl}'`);\n }\n\n const remoteConfigContent = await readConfigFromUrl(configUrl);\n if (!remoteConfigContent) {\n throw new Error(`Config is not valid`);\n }\n const configYaml = yaml.parse(remoteConfigContent);\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(configRoot, configYaml, [\n substitutionTransform,\n ]);\n\n configs.push({ data, context: configUrl });\n }\n\n return configs;\n };\n\n let fileConfigs: AppConfig[];\n try {\n fileConfigs = await loadConfigFiles();\n } catch (error) {\n throw new ForwardedError('Failed to read static configuration file', error);\n }\n\n let remoteConfigs: AppConfig[] = [];\n if (remote) {\n try {\n remoteConfigs = await loadRemoteConfigFiles();\n } catch (error) {\n throw new ForwardedError(\n `Failed to read remote configuration file`,\n error,\n );\n }\n }\n\n const envConfigs = await readEnvConfig(process.env);\n\n const watchConfigFile = (watchProp: LoadConfigOptionsWatch) => {\n const watcher = chokidar.watch(configPaths, {\n usePolling: process.env.NODE_ENV === 'test',\n });\n\n let currentSerializedConfig = JSON.stringify(fileConfigs);\n watcher.on('change', async () => {\n try {\n const newConfigs = await loadConfigFiles();\n const newSerializedConfig = JSON.stringify(newConfigs);\n\n if (currentSerializedConfig === newSerializedConfig) {\n return;\n }\n currentSerializedConfig = newSerializedConfig;\n\n watchProp.onChange([...remoteConfigs, ...newConfigs, ...envConfigs]);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n });\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n watcher.close();\n });\n }\n };\n\n const watchRemoteConfig = (\n watchProp: LoadConfigOptionsWatch,\n remoteProp: LoadConfigOptionsRemote,\n ) => {\n const hasConfigChanged = async (\n oldRemoteConfigs: AppConfig[],\n newRemoteConfigs: AppConfig[],\n ) => {\n return (\n JSON.stringify(oldRemoteConfigs) !== JSON.stringify(newRemoteConfigs)\n );\n };\n\n let handle: NodeJS.Timeout | undefined;\n try {\n handle = setInterval(async () => {\n console.info(`Checking for config update`);\n const newRemoteConfigs = await loadRemoteConfigFiles();\n if (await hasConfigChanged(remoteConfigs, newRemoteConfigs)) {\n remoteConfigs = newRemoteConfigs;\n console.info(`Remote config change, reloading config ...`);\n watchProp.onChange([...remoteConfigs, ...fileConfigs, ...envConfigs]);\n console.info(`Remote config reloaded`);\n }\n }, remoteProp.reloadIntervalSeconds * 1000);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n if (handle !== undefined) {\n console.info(`Stopping remote config watch`);\n clearInterval(handle);\n handle = undefined;\n }\n });\n }\n };\n\n // Set up config file watching if requested by the caller\n if (watch) {\n watchConfigFile(watch);\n }\n\n if (watch && remote) {\n watchRemoteConfig(watch, remote);\n }\n\n return {\n appConfigs: remote\n ? [...remoteConfigs, ...fileConfigs, ...envConfigs]\n : [...fileConfigs, ...envConfigs],\n };\n}\n"],"names":["resolvePath","relativePath"],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/lib/env.ts","../src/lib/transform/utils.ts","../src/lib/transform/apply.ts","../src/lib/transform/include.ts","../src/lib/transform/substitution.ts","../src/lib/schema/types.ts","../src/lib/schema/compile.ts","../src/lib/schema/collect.ts","../src/lib/schema/filtering.ts","../src/lib/schema/load.ts","../src/lib/urls.ts","../src/loader.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\nconst ENV_PREFIX = 'APP_CONFIG_';\n\n// Update the same pattern in config package if this is changed\nconst CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;\n\n/**\n * Read runtime configuration from the environment.\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n */\nexport function readEnvConfig(env: {\n [name: string]: string | undefined;\n}): AppConfig[] {\n let data: JsonObject | undefined = undefined;\n\n for (const [name, value] of Object.entries(env)) {\n if (!value) {\n continue;\n }\n if (name.startsWith(ENV_PREFIX)) {\n const key = name.replace(ENV_PREFIX, '');\n const keyParts = key.split('_');\n\n let obj = (data = data ?? {});\n for (const [index, part] of keyParts.entries()) {\n if (!CONFIG_KEY_PART_PATTERN.test(part)) {\n throw new TypeError(`Invalid env config key '${key}'`);\n }\n if (index < keyParts.length - 1) {\n obj = (obj[part] = obj[part] ?? {}) as JsonObject;\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n const subKey = keyParts.slice(0, index + 1).join('_');\n throw new TypeError(\n `Could not nest config for key '${key}' under existing value '${subKey}'`,\n );\n }\n } else {\n if (part in obj) {\n throw new TypeError(\n `Refusing to override existing config at key '${key}'`,\n );\n }\n try {\n const [, parsedValue] = safeJsonParse(value);\n if (parsedValue === null) {\n throw new Error('value may not be null');\n }\n obj[part] = parsedValue;\n } catch (error) {\n throw new TypeError(\n `Failed to parse JSON-serialized config value for key '${key}', ${error}`,\n );\n }\n }\n }\n }\n }\n\n return data ? [{ data, context: 'env' }] : [];\n}\n\nfunction safeJsonParse(str: string): [Error | null, any] {\n try {\n return [null, JSON.parse(str)];\n } catch (err) {\n assertError(err);\n return [err, str];\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue, JsonObject } from '@backstage/types';\n\nexport function isObject(obj: JsonValue | undefined): obj is JsonObject {\n if (typeof obj !== 'object') {\n return false;\n } else if (Array.isArray(obj)) {\n return false;\n }\n return obj !== null;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\nimport { TransformFunc } from './types';\nimport { isObject } from './utils';\n\n/**\n * Applies a set of transforms to raw configuration data.\n */\nexport async function applyConfigTransforms(\n initialDir: string,\n input: JsonValue,\n transforms: TransformFunc[],\n): Promise<JsonObject> {\n async function transform(\n inputObj: JsonValue,\n path: string,\n baseDir: string,\n ): Promise<JsonValue | undefined> {\n let obj = inputObj;\n let dir = baseDir;\n\n for (const tf of transforms) {\n try {\n const result = await tf(inputObj, baseDir);\n if (result.applied) {\n if (result.value === undefined) {\n return undefined;\n }\n obj = result.value;\n dir = result.newBaseDir ?? dir;\n break;\n }\n } catch (error) {\n assertError(error);\n throw new Error(`error at ${path}, ${error.message}`);\n }\n }\n\n if (typeof obj !== 'object') {\n return obj;\n } else if (obj === null) {\n return undefined;\n } else if (Array.isArray(obj)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of obj.entries()) {\n const out = await transform(value, `${path}[${index}]`, dir);\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n return arr;\n }\n\n const out: JsonObject = {};\n\n for (const [key, value] of Object.entries(obj)) {\n // undefined covers optional fields\n if (value !== undefined) {\n const result = await transform(value, `${path}.${key}`, dir);\n if (result !== undefined) {\n out[key] = result;\n }\n }\n }\n\n return out;\n }\n\n const finalData = await transform(input, '', initialDir);\n if (!isObject(finalData)) {\n throw new TypeError('expected object at config root');\n }\n return finalData;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport yaml from 'yaml';\nimport { extname, dirname, resolve as resolvePath } from 'path';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { isObject } from './utils';\nimport { TransformFunc, EnvFunc, ReadFileFunc } from './types';\n\n// Parsers for each type of included file\nconst includeFileParser: {\n [ext in string]: (content: string) => Promise<JsonObject>;\n} = {\n '.json': async content => JSON.parse(content),\n '.yaml': async content => yaml.parse(content),\n '.yml': async content => yaml.parse(content),\n};\n\n/**\n * Transforms a include description into the actual included value.\n */\nexport function createIncludeTransform(\n env: EnvFunc,\n readFile: ReadFileFunc,\n substitute: TransformFunc,\n): TransformFunc {\n return async (input: JsonValue, baseDir: string) => {\n if (!isObject(input)) {\n return { applied: false };\n }\n // Check if there's any key that starts with a '$', in that case we treat\n // this entire object as an include description.\n const [includeKey] = Object.keys(input).filter(key => key.startsWith('$'));\n if (includeKey) {\n if (Object.keys(input).length !== 1) {\n throw new Error(\n `include key ${includeKey} should not have adjacent keys`,\n );\n }\n } else {\n return { applied: false };\n }\n\n const rawIncludedValue = input[includeKey];\n if (typeof rawIncludedValue !== 'string') {\n throw new Error(`${includeKey} include value is not a string`);\n }\n\n const substituteResults = await substitute(rawIncludedValue, baseDir);\n const includeValue = substituteResults.applied\n ? substituteResults.value\n : rawIncludedValue;\n\n // The second string check is needed for Typescript to know this is a string.\n if (includeValue === undefined || typeof includeValue !== 'string') {\n throw new Error(`${includeKey} substitution value was undefined`);\n }\n\n switch (includeKey) {\n case '$file':\n try {\n const value = await readFile(resolvePath(baseDir, includeValue));\n return { applied: true, value };\n } catch (error) {\n throw new Error(`failed to read file ${includeValue}, ${error}`);\n }\n case '$env':\n try {\n return { applied: true, value: await env(includeValue) };\n } catch (error) {\n throw new Error(`failed to read env ${includeValue}, ${error}`);\n }\n\n case '$include': {\n const [filePath, dataPath] = includeValue.split(/#(.*)/);\n\n const ext = extname(filePath);\n const parser = includeFileParser[ext];\n if (!parser) {\n throw new Error(\n `no configuration parser available for included file ${filePath}`,\n );\n }\n\n const path = resolvePath(baseDir, filePath);\n const content = await readFile(path);\n const newBaseDir = dirname(path);\n\n const parts = dataPath ? dataPath.split('.') : [];\n\n let value: JsonValue | undefined;\n try {\n value = await parser(content);\n } catch (error) {\n throw new Error(\n `failed to parse included file ${filePath}, ${error}`,\n );\n }\n\n // This bit handles selecting a subtree in the included file, if a path was provided after a #\n for (const [index, part] of parts.entries()) {\n if (!isObject(value)) {\n const errPath = parts.slice(0, index).join('.');\n throw new Error(\n `value at '${errPath}' in included file ${filePath} is not an object`,\n );\n }\n value = value[part];\n }\n\n return {\n applied: true,\n value,\n newBaseDir: newBaseDir !== baseDir ? newBaseDir : undefined,\n };\n }\n\n default:\n throw new Error(`unknown include ${includeKey}`);\n }\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonValue } from '@backstage/types';\nimport { TransformFunc, EnvFunc } from './types';\n\n/**\n * A environment variable substitution transform that transforms e.g. 'token ${MY_TOKEN}'\n * to 'token abc' if MY_TOKEN is 'abc'. If any of the substituted variables are undefined,\n * the entire expression ends up undefined.\n */\nexport function createSubstitutionTransform(env: EnvFunc): TransformFunc {\n return async (input: JsonValue) => {\n if (typeof input !== 'string') {\n return { applied: false };\n }\n\n const parts: (string | undefined)[] = input.split(/(\\$?\\$\\{[^{}]*\\})/);\n for (let i = 1; i < parts.length; i += 2) {\n const part = parts[i]!;\n if (part.startsWith('$$')) {\n parts[i] = part.slice(1);\n } else {\n parts[i] = await env(part.slice(2, -1).trim());\n }\n }\n\n if (parts.some(part => part === undefined)) {\n return { applied: true, value: undefined };\n }\n return { applied: true, value: parts.join('') };\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\n\n/**\n * An sub-set of configuration schema.\n */\nexport type ConfigSchemaPackageEntry = {\n /**\n * The configuration schema itself.\n */\n value: JsonObject;\n /**\n * The relative path that the configuration schema was discovered at.\n */\n path: string;\n};\n\n/**\n * A list of all possible configuration value visibilities.\n */\nexport const CONFIG_VISIBILITIES = ['frontend', 'backend', 'secret'] as const;\n\n/**\n * A type representing the possible configuration value visibilities\n *\n * @public\n */\nexport type ConfigVisibility = 'frontend' | 'backend' | 'secret';\n\n/**\n * The default configuration visibility if no other values is given.\n */\nexport const DEFAULT_CONFIG_VISIBILITY: ConfigVisibility = 'backend';\n\n/**\n * An explanation of a configuration validation error.\n */\nexport type ValidationError = {\n keyword: string;\n dataPath: string;\n schemaPath: string;\n params: Record<string, any>;\n propertyName?: string;\n message?: string;\n};\n\n/**\n * The result of validating configuration data using a schema.\n */\ntype ValidationResult = {\n /**\n * Errors that where emitted during validation, if any.\n */\n errors?: ValidationError[];\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/<key>/<sub-key>/<array-index>/<leaf-key>`\n */\n visibilityByDataPath: Map<string, ConfigVisibility>;\n\n /**\n * The configuration visibilities that were discovered during validation.\n *\n * The path in the key uses the form `/properties/<key>/items/additionalProperties/<leaf-key>`\n */\n visibilityBySchemaPath: Map<string, ConfigVisibility>;\n};\n\n/**\n * A function used validate configuration data.\n */\nexport type ValidationFunc = (configs: AppConfig[]) => ValidationResult;\n\n/**\n * A function used to transform primitive configuration values.\n *\n * @public\n */\nexport type TransformFunc<T extends number | string | boolean> = (\n value: T,\n context: { visibility: ConfigVisibility },\n) => T | undefined;\n\n/**\n * Options used to process configuration data with a schema.\n *\n * @public\n */\nexport type ConfigSchemaProcessingOptions = {\n /**\n * The visibilities that should be included in the output data.\n * If omitted, the data will not be filtered by visibility.\n */\n visibility?: ConfigVisibility[];\n\n /**\n * A transform function that can be used to transform primitive configuration values\n * during validation. The value returned from the transform function will be used\n * instead of the original value. If the transform returns `undefined`, the value\n * will be omitted.\n */\n valueTransform?: TransformFunc<any>;\n\n /**\n * Whether or not to include the `filteredKeys` property in the output `AppConfig`s.\n *\n * Default: `false`.\n */\n withFilteredKeys?: boolean;\n};\n\n/**\n * A loaded configuration schema that is ready to process configuration data.\n *\n * @public\n */\nexport type ConfigSchema = {\n process(\n appConfigs: AppConfig[],\n options?: ConfigSchemaProcessingOptions,\n ): AppConfig[];\n\n serialize(): JsonObject;\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport Ajv from 'ajv';\nimport { JSONSchema7 as JSONSchema } from 'json-schema';\nimport mergeAllOf, { Resolvers } from 'json-schema-merge-allof';\nimport traverse from 'json-schema-traverse';\nimport { ConfigReader } from '@backstage/config';\nimport {\n ConfigSchemaPackageEntry,\n ValidationFunc,\n CONFIG_VISIBILITIES,\n ConfigVisibility,\n} from './types';\n\n/**\n * This takes a collection of Backstage configuration schemas from various\n * sources and compiles them down into a single schema validation function.\n *\n * It also handles the implementation of the custom \"visibility\" keyword used\n * to specify the scope of different config paths.\n */\nexport function compileConfigSchemas(\n schemas: ConfigSchemaPackageEntry[],\n): ValidationFunc {\n // The ajv instance below is stateful and doesn't really allow for additional\n // output during validation. We work around this by having this extra piece\n // of state that we reset before each validation.\n const visibilityByDataPath = new Map<string, ConfigVisibility>();\n\n const ajv = new Ajv({\n allErrors: true,\n allowUnionTypes: true,\n schemas: {\n 'https://backstage.io/schema/config-v1': true,\n },\n }).addKeyword({\n keyword: 'visibility',\n metaSchema: {\n type: 'string',\n enum: CONFIG_VISIBILITIES,\n },\n compile(visibility: ConfigVisibility) {\n return (_data, context) => {\n if (context?.dataPath === undefined) {\n return false;\n }\n if (visibility && visibility !== 'backend') {\n const normalizedPath = context.dataPath.replace(\n /\\['?(.*?)'?\\]/g,\n (_, segment) => `/${segment}`,\n );\n visibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n });\n\n for (const schema of schemas) {\n try {\n ajv.compile(schema.value);\n } catch (error) {\n throw new Error(`Schema at ${schema.path} is invalid, ${error}`);\n }\n }\n\n const merged = mergeConfigSchemas(schemas.map(_ => _.value));\n const validate = ajv.compile(merged);\n\n const visibilityBySchemaPath = new Map<string, ConfigVisibility>();\n traverse(merged, (schema, path) => {\n if (schema.visibility && schema.visibility !== 'backend') {\n visibilityBySchemaPath.set(path, schema.visibility);\n }\n });\n\n return configs => {\n const config = ConfigReader.fromConfigs(configs).get();\n\n visibilityByDataPath.clear();\n\n const valid = validate(config);\n if (!valid) {\n return {\n errors: validate.errors ?? [],\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n }\n\n return {\n visibilityByDataPath: new Map(visibilityByDataPath),\n visibilityBySchemaPath,\n };\n };\n}\n\n/**\n * Given a list of configuration schemas from packages, merge them\n * into a single json schema.\n *\n * @public\n */\nexport function mergeConfigSchemas(schemas: JSONSchema[]): JSONSchema {\n const merged = mergeAllOf(\n { allOf: schemas },\n {\n // JSONSchema is typically subtractive, as in it always reduces the set of allowed\n // inputs through constraints. This changes the object property merging to be additive\n // rather than subtractive.\n ignoreAdditionalProperties: true,\n resolvers: {\n // This ensures that the visibilities across different schemas are sound, and\n // selects the most specific visibility for each path.\n visibility(values: string[], path: string[]) {\n const hasFrontend = values.some(_ => _ === 'frontend');\n const hasSecret = values.some(_ => _ === 'secret');\n if (hasFrontend && hasSecret) {\n throw new Error(\n `Config schema visibility is both 'frontend' and 'secret' for ${path.join(\n '/',\n )}`,\n );\n } else if (hasFrontend) {\n return 'frontend';\n } else if (hasSecret) {\n return 'secret';\n }\n\n return 'backend';\n },\n } as Partial<Resolvers<JSONSchema>>,\n },\n );\n return merged;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport {\n resolve as resolvePath,\n relative as relativePath,\n dirname,\n sep,\n} from 'path';\nimport { ConfigSchemaPackageEntry } from './types';\nimport { getProgramFromFiles, generateSchema } from 'typescript-json-schema';\nimport { JsonObject } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\n\ntype Item = {\n name?: string;\n parentPath?: string;\n packagePath?: string;\n};\n\nconst req =\n typeof __non_webpack_require__ === 'undefined'\n ? require\n : __non_webpack_require__;\n\n/**\n * This collects all known config schemas across all dependencies of the app.\n */\nexport async function collectConfigSchemas(\n packageNames: string[],\n packagePaths: string[],\n): Promise<ConfigSchemaPackageEntry[]> {\n const schemas = new Array<ConfigSchemaPackageEntry>();\n const tsSchemaPaths = new Array<string>();\n const visitedPackageVersions = new Map<string, Set<string>>(); // pkgName: [versions...]\n\n const currentDir = await fs.realpath(process.cwd());\n\n async function processItem(item: Item) {\n let pkgPath = item.packagePath;\n\n if (pkgPath) {\n const pkgExists = await fs.pathExists(pkgPath);\n if (!pkgExists) {\n return;\n }\n } else if (item.name) {\n const { name, parentPath } = item;\n\n try {\n pkgPath = req.resolve(\n `${name}/package.json`,\n parentPath && {\n paths: [parentPath],\n },\n );\n } catch {\n // We can somewhat safely ignore packages that don't export package.json,\n // as they are likely not part of the Backstage ecosystem anyway.\n }\n }\n if (!pkgPath) {\n return;\n }\n\n const pkg = await fs.readJson(pkgPath);\n\n // Ensures that we only process the same version of each package once.\n let versions = visitedPackageVersions.get(pkg.name);\n if (versions?.has(pkg.version)) {\n return;\n }\n if (!versions) {\n versions = new Set();\n visitedPackageVersions.set(pkg.name, versions);\n }\n versions.add(pkg.version);\n\n const depNames = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ...Object.keys(pkg.optionalDependencies ?? {}),\n ...Object.keys(pkg.peerDependencies ?? {}),\n ];\n\n // TODO(Rugvip): Trying this out to avoid having to traverse the full dependency graph,\n // since that's pretty slow. We probably need a better way to determine when\n // we've left the Backstage ecosystem, but this will do for now.\n const hasSchema = 'configSchema' in pkg;\n const hasBackstageDep = depNames.some(_ => _.startsWith('@backstage/'));\n if (!hasSchema && !hasBackstageDep) {\n return;\n }\n if (hasSchema) {\n if (typeof pkg.configSchema === 'string') {\n const isJson = pkg.configSchema.endsWith('.json');\n const isDts = pkg.configSchema.endsWith('.d.ts');\n if (!isJson && !isDts) {\n throw new Error(\n `Config schema files must be .json or .d.ts, got ${pkg.configSchema}`,\n );\n }\n if (isDts) {\n tsSchemaPaths.push(\n relativePath(\n currentDir,\n resolvePath(dirname(pkgPath), pkg.configSchema),\n ),\n );\n } else {\n const path = resolvePath(dirname(pkgPath), pkg.configSchema);\n const value = await fs.readJson(path);\n schemas.push({\n value,\n path: relativePath(currentDir, path),\n });\n }\n } else {\n schemas.push({\n value: pkg.configSchema,\n path: relativePath(currentDir, pkgPath),\n });\n }\n }\n\n await Promise.all(\n depNames.map(depName =>\n processItem({ name: depName, parentPath: pkgPath }),\n ),\n );\n }\n\n await Promise.all([\n ...packageNames.map(name => processItem({ name, parentPath: currentDir })),\n ...packagePaths.map(path => processItem({ name: path, packagePath: path })),\n ]);\n\n const tsSchemas = compileTsSchemas(tsSchemaPaths);\n\n return schemas.concat(tsSchemas);\n}\n\n// This handles the support of TypeScript .d.ts config schema declarations.\n// We collect all typescript schema definition and compile them all in one go.\n// This is much faster than compiling them separately.\nfunction compileTsSchemas(paths: string[]) {\n if (paths.length === 0) {\n return [];\n }\n\n const program = getProgramFromFiles(paths, {\n incremental: false,\n isolatedModules: true,\n lib: ['ES5'], // Skipping most libs speeds processing up a lot, we just need the primitive types anyway\n noEmit: true,\n noResolve: true,\n skipLibCheck: true, // Skipping lib checks speeds things up\n skipDefaultLibCheck: true,\n strict: true,\n typeRoots: [], // Do not include any additional types\n types: [],\n });\n\n const tsSchemas = paths.map(path => {\n let value;\n try {\n value = generateSchema(\n program,\n // All schemas should export a `Config` symbol\n 'Config',\n // This enables usage of @visibility is doc comments\n {\n required: true,\n validationKeywords: ['visibility'],\n },\n [path.split(sep).join('/')], // Unix paths are expected for all OSes here\n ) as JsonObject | null;\n } catch (error) {\n assertError(error);\n if (error.message !== 'type Config not found') {\n throw error;\n }\n }\n\n if (!value) {\n throw new Error(`Invalid schema in ${path}, missing Config export`);\n }\n return { path, value };\n });\n\n return tsSchemas;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport {\n ConfigVisibility,\n DEFAULT_CONFIG_VISIBILITY,\n TransformFunc,\n ValidationError,\n} from './types';\n\n/**\n * This filters data by visibility by discovering the visibility of each\n * value, and then only keeping the ones that are specified in `includeVisibilities`.\n */\nexport function filterByVisibility(\n data: JsonObject,\n includeVisibilities: ConfigVisibility[],\n visibilityByDataPath: Map<string, ConfigVisibility>,\n transformFunc?: TransformFunc<number | string | boolean>,\n withFilteredKeys?: boolean,\n): { data: JsonObject; filteredKeys?: string[] } {\n const filteredKeys = new Array<string>();\n\n function transform(\n jsonVal: JsonValue,\n visibilityPath: string, // Matches the format we get from ajv\n filterPath: string, // Matches the format of the ConfigReader\n ): JsonValue | undefined {\n const visibility =\n visibilityByDataPath.get(visibilityPath) ?? DEFAULT_CONFIG_VISIBILITY;\n const isVisible = includeVisibilities.includes(visibility);\n\n if (typeof jsonVal !== 'object') {\n if (isVisible) {\n if (transformFunc) {\n return transformFunc(jsonVal, { visibility });\n }\n return jsonVal;\n }\n if (withFilteredKeys) {\n filteredKeys.push(filterPath);\n }\n return undefined;\n } else if (jsonVal === null) {\n return undefined;\n } else if (Array.isArray(jsonVal)) {\n const arr = new Array<JsonValue>();\n\n for (const [index, value] of jsonVal.entries()) {\n let path = visibilityPath;\n const hasVisibilityInIndex = visibilityByDataPath.get(\n `${visibilityPath}/${index}`,\n );\n\n if (hasVisibilityInIndex || typeof value === 'object') {\n path = `${visibilityPath}/${index}`;\n }\n\n const out = transform(value, path, `${filterPath}[${index}]`);\n\n if (out !== undefined) {\n arr.push(out);\n }\n }\n\n if (arr.length > 0 || isVisible) {\n return arr;\n }\n return undefined;\n }\n\n const outObj: JsonObject = {};\n let hasOutput = false;\n\n for (const [key, value] of Object.entries(jsonVal)) {\n if (value === undefined) {\n continue;\n }\n const out = transform(\n value,\n `${visibilityPath}/${key}`,\n filterPath ? `${filterPath}.${key}` : key,\n );\n if (out !== undefined) {\n outObj[key] = out;\n hasOutput = true;\n }\n }\n\n if (hasOutput || isVisible) {\n return outObj;\n }\n return undefined;\n }\n\n return {\n filteredKeys: withFilteredKeys ? filteredKeys : undefined,\n data: (transform(data, '', '') as JsonObject) ?? {},\n };\n}\n\nexport function filterErrorsByVisibility(\n errors: ValidationError[] | undefined,\n includeVisibilities: ConfigVisibility[] | undefined,\n visibilityByDataPath: Map<string, ConfigVisibility>,\n visibilityBySchemaPath: Map<string, ConfigVisibility>,\n): ValidationError[] {\n if (!errors) {\n return [];\n }\n if (!includeVisibilities) {\n return errors;\n }\n\n const visibleSchemaPaths = Array.from(visibilityBySchemaPath)\n .filter(([, v]) => includeVisibilities.includes(v))\n .map(([k]) => k);\n\n // If we're filtering by visibility we only care about the errors that happened\n // in a visible path.\n return errors.filter(error => {\n // We always include structural errors as we don't know whether there are\n // any visible paths within the structures.\n if (\n error.keyword === 'type' &&\n ['object', 'array'].includes(error.params.type)\n ) {\n return true;\n }\n\n // For fields that were required we use the schema path to determine whether\n // it was visible in addition to the data path. This is because the data path\n // visibilities are only populated for values that we reached, which we won't\n // if the value is missing.\n // We don't use this method for all the errors as the data path is more robust\n // and doesn't require us to properly trim the schema path.\n if (error.keyword === 'required') {\n const trimmedPath = error.schemaPath.slice(1, -'/required'.length);\n const fullPath = `${trimmedPath}/properties/${error.params.missingProperty}`;\n if (\n visibleSchemaPaths.some(visiblePath => visiblePath.startsWith(fullPath))\n ) {\n return true;\n }\n }\n\n const vis =\n visibilityByDataPath.get(error.dataPath) ?? DEFAULT_CONFIG_VISIBILITY;\n return vis && includeVisibilities.includes(vis);\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { compileConfigSchemas } from './compile';\nimport { collectConfigSchemas } from './collect';\nimport { filterByVisibility, filterErrorsByVisibility } from './filtering';\nimport {\n ValidationError,\n ConfigSchema,\n ConfigSchemaPackageEntry,\n CONFIG_VISIBILITIES,\n} from './types';\n\n/**\n * Options that control the loading of configuration schema files in the backend.\n *\n * @public\n */\nexport type LoadConfigSchemaOptions =\n | {\n dependencies: string[];\n packagePaths?: string[];\n }\n | {\n serialized: JsonObject;\n };\n\nfunction errorsToError(errors: ValidationError[]): Error {\n const messages = errors.map(({ dataPath, message, params }) => {\n const paramStr = Object.entries(params)\n .map(([name, value]) => `${name}=${value}`)\n .join(' ');\n return `Config ${message || ''} { ${paramStr} } at ${dataPath}`;\n });\n const error = new Error(`Config validation failed, ${messages.join('; ')}`);\n (error as any).messages = messages;\n return error;\n}\n\n/**\n * Loads config schema for a Backstage instance.\n *\n * @public\n */\nexport async function loadConfigSchema(\n options: LoadConfigSchemaOptions,\n): Promise<ConfigSchema> {\n let schemas: ConfigSchemaPackageEntry[];\n\n if ('dependencies' in options) {\n schemas = await collectConfigSchemas(\n options.dependencies,\n options.packagePaths ?? [],\n );\n } else {\n const { serialized } = options;\n if (serialized?.backstageConfigSchemaVersion !== 1) {\n throw new Error(\n 'Serialized configuration schema is invalid or has an invalid version number',\n );\n }\n schemas = serialized.schemas as ConfigSchemaPackageEntry[];\n }\n\n const validate = compileConfigSchemas(schemas);\n\n return {\n process(\n configs: AppConfig[],\n { visibility, valueTransform, withFilteredKeys } = {},\n ): AppConfig[] {\n const result = validate(configs);\n\n const visibleErrors = filterErrorsByVisibility(\n result.errors,\n visibility,\n result.visibilityByDataPath,\n result.visibilityBySchemaPath,\n );\n if (visibleErrors.length > 0) {\n throw errorsToError(visibleErrors);\n }\n\n let processedConfigs = configs;\n\n if (visibility) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n visibility,\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n } else if (valueTransform) {\n processedConfigs = processedConfigs.map(({ data, context }) => ({\n context,\n ...filterByVisibility(\n data,\n Array.from(CONFIG_VISIBILITIES),\n result.visibilityByDataPath,\n valueTransform,\n withFilteredKeys,\n ),\n }));\n }\n\n return processedConfigs;\n },\n serialize(): JsonObject {\n return {\n schemas,\n backstageConfigSchemaVersion: 1,\n };\n },\n };\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport function isValidUrl(url: string): boolean {\n try {\n // eslint-disable-next-line no-new\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fs from 'fs-extra';\nimport yaml from 'yaml';\nimport chokidar from 'chokidar';\nimport { basename, dirname, isAbsolute, resolve as resolvePath } from 'path';\nimport { AppConfig } from '@backstage/config';\nimport { ForwardedError } from '@backstage/errors';\nimport {\n applyConfigTransforms,\n createIncludeTransform,\n createSubstitutionTransform,\n isValidUrl,\n readEnvConfig,\n} from './lib';\nimport fetch from 'node-fetch';\n\nexport type ConfigTarget = { path: string } | { url: string };\n\nexport type LoadConfigOptionsWatch = {\n /**\n * A listener that is called when a config file is changed.\n */\n onChange: (configs: AppConfig[]) => void;\n\n /**\n * An optional signal that stops the watcher once the promise resolves.\n */\n stopSignal?: Promise<void>;\n};\n\nexport type LoadConfigOptionsRemote = {\n /**\n * A remote config reloading period, in seconds\n */\n reloadIntervalSeconds: number;\n};\n\n/**\n * Options that control the loading of configuration files in the backend.\n *\n * @public\n */\nexport type LoadConfigOptions = {\n // The root directory of the config loading context. Used to find default configs.\n configRoot: string;\n\n // Paths to load config files from. Configs from earlier paths have lower priority.\n configTargets: ConfigTarget[];\n\n /**\n * Custom environment variable loading function\n *\n * @experimental This API is not stable and may change at any point\n */\n experimentalEnvFunc?: (name: string) => Promise<string | undefined>;\n\n /**\n * An optional remote config\n */\n remote?: LoadConfigOptionsRemote;\n\n /**\n * An optional configuration that enables watching of config files.\n */\n watch?: LoadConfigOptionsWatch;\n};\n\n/**\n * Results of loading configuration files.\n * @public\n */\nexport type LoadConfigResult = {\n /**\n * Array of all loaded configs.\n */\n appConfigs: AppConfig[];\n};\n\n/**\n * Load configuration data.\n *\n * @public\n */\nexport async function loadConfig(\n options: LoadConfigOptions,\n): Promise<LoadConfigResult> {\n const { configRoot, experimentalEnvFunc: envFunc, watch, remote } = options;\n\n const configPaths: string[] = options.configTargets\n .slice()\n .filter((e): e is { path: string } => e.hasOwnProperty('path'))\n .map(configTarget => configTarget.path);\n\n const configUrls: string[] = options.configTargets\n .slice()\n .filter((e): e is { url: string } => e.hasOwnProperty('url'))\n .map(configTarget => configTarget.url);\n\n if (remote === undefined) {\n if (configUrls.length > 0) {\n throw new Error(\n `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.`,\n );\n }\n } else if (remote.reloadIntervalSeconds <= 0) {\n throw new Error(\n `Remote config must be contain a non zero reloadIntervalSeconds: <seconds> value`,\n );\n }\n\n // If no paths are provided, we default to reading\n // `app-config.yaml` and, if it exists, `app-config.local.yaml`\n if (configPaths.length === 0 && configUrls.length === 0) {\n configPaths.push(resolvePath(configRoot, 'app-config.yaml'));\n\n const localConfig = resolvePath(configRoot, 'app-config.local.yaml');\n if (await fs.pathExists(localConfig)) {\n configPaths.push(localConfig);\n }\n }\n\n const env = envFunc ?? (async (name: string) => process.env[name]);\n\n const loadConfigFiles = async () => {\n const configs = [];\n\n for (const configPath of configPaths) {\n if (!isAbsolute(configPath)) {\n throw new Error(`Config load path is not absolute: '${configPath}'`);\n }\n\n const dir = dirname(configPath);\n const readFile = (path: string) =>\n fs.readFile(resolvePath(dir, path), 'utf8');\n\n const input = yaml.parse(await readFile(configPath));\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(dir, input, [\n createIncludeTransform(env, readFile, substitutionTransform),\n substitutionTransform,\n ]);\n\n configs.push({ data, context: basename(configPath) });\n }\n\n return configs;\n };\n\n const loadRemoteConfigFiles = async () => {\n const configs: AppConfig[] = [];\n\n const readConfigFromUrl = async (url: string) => {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Could not read config file at ${url}`);\n }\n\n return await response.text();\n };\n\n for (let i = 0; i < configUrls.length; i++) {\n const configUrl = configUrls[i];\n if (!isValidUrl(configUrl)) {\n throw new Error(`Config load path is not valid: '${configUrl}'`);\n }\n\n const remoteConfigContent = await readConfigFromUrl(configUrl);\n if (!remoteConfigContent) {\n throw new Error(`Config is not valid`);\n }\n const configYaml = yaml.parse(remoteConfigContent);\n const substitutionTransform = createSubstitutionTransform(env);\n const data = await applyConfigTransforms(configRoot, configYaml, [\n substitutionTransform,\n ]);\n\n configs.push({ data, context: configUrl });\n }\n\n return configs;\n };\n\n let fileConfigs: AppConfig[];\n try {\n fileConfigs = await loadConfigFiles();\n } catch (error) {\n throw new ForwardedError('Failed to read static configuration file', error);\n }\n\n let remoteConfigs: AppConfig[] = [];\n if (remote) {\n try {\n remoteConfigs = await loadRemoteConfigFiles();\n } catch (error) {\n throw new ForwardedError(\n `Failed to read remote configuration file`,\n error,\n );\n }\n }\n\n const envConfigs = await readEnvConfig(process.env);\n\n const watchConfigFile = (watchProp: LoadConfigOptionsWatch) => {\n const watcher = chokidar.watch(configPaths, {\n usePolling: process.env.NODE_ENV === 'test',\n });\n\n let currentSerializedConfig = JSON.stringify(fileConfigs);\n watcher.on('change', async () => {\n try {\n const newConfigs = await loadConfigFiles();\n const newSerializedConfig = JSON.stringify(newConfigs);\n\n if (currentSerializedConfig === newSerializedConfig) {\n return;\n }\n currentSerializedConfig = newSerializedConfig;\n\n watchProp.onChange([...remoteConfigs, ...newConfigs, ...envConfigs]);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n });\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n watcher.close();\n });\n }\n };\n\n const watchRemoteConfig = (\n watchProp: LoadConfigOptionsWatch,\n remoteProp: LoadConfigOptionsRemote,\n ) => {\n const hasConfigChanged = async (\n oldRemoteConfigs: AppConfig[],\n newRemoteConfigs: AppConfig[],\n ) => {\n return (\n JSON.stringify(oldRemoteConfigs) !== JSON.stringify(newRemoteConfigs)\n );\n };\n\n let handle: NodeJS.Timeout | undefined;\n try {\n handle = setInterval(async () => {\n console.info(`Checking for config update`);\n const newRemoteConfigs = await loadRemoteConfigFiles();\n if (await hasConfigChanged(remoteConfigs, newRemoteConfigs)) {\n remoteConfigs = newRemoteConfigs;\n console.info(`Remote config change, reloading config ...`);\n watchProp.onChange([...remoteConfigs, ...fileConfigs, ...envConfigs]);\n console.info(`Remote config reloaded`);\n }\n }, remoteProp.reloadIntervalSeconds * 1000);\n } catch (error) {\n console.error(`Failed to reload configuration files, ${error}`);\n }\n\n if (watchProp.stopSignal) {\n watchProp.stopSignal.then(() => {\n if (handle !== undefined) {\n console.info(`Stopping remote config watch`);\n clearInterval(handle);\n handle = undefined;\n }\n });\n }\n };\n\n // Set up config file watching if requested by the caller\n if (watch) {\n watchConfigFile(watch);\n }\n\n if (watch && remote) {\n watchRemoteConfig(watch, remote);\n }\n\n return {\n appConfigs: remote\n ? [...remoteConfigs, ...fileConfigs, ...envConfigs]\n : [...fileConfigs, ...envConfigs],\n };\n}\n"],"names":["resolvePath","relativePath"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@backstage/config-loader",
3
3
  "description": "Config loading functionality used by Backstage backend, and CLI",
4
- "version": "0.8.1",
4
+ "version": "0.9.0",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -58,6 +58,6 @@
58
58
  "files": [
59
59
  "dist"
60
60
  ],
61
- "gitHead": "a05e7081b805006e3f0b2960a08a7753357f532f",
61
+ "gitHead": "b315430f9dfcfa19ab0dd90f5b4ac6904938fba7",
62
62
  "module": "dist/index.esm.js"
63
63
  }