@backstage/config-loader 1.9.1 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/index.cjs.js +22 -1650
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/loader.cjs.js +60 -0
  5. package/dist/loader.cjs.js.map +1 -0
  6. package/dist/schema/collect.cjs.js +169 -0
  7. package/dist/schema/collect.cjs.js.map +1 -0
  8. package/dist/schema/compile.cjs.js +185 -0
  9. package/dist/schema/compile.cjs.js.map +1 -0
  10. package/dist/schema/filtering.cjs.js +112 -0
  11. package/dist/schema/filtering.cjs.js.map +1 -0
  12. package/dist/schema/load.cjs.js +101 -0
  13. package/dist/schema/load.cjs.js.map +1 -0
  14. package/dist/schema/types.cjs.js +8 -0
  15. package/dist/schema/types.cjs.js.map +1 -0
  16. package/dist/schema/utils.cjs.js +8 -0
  17. package/dist/schema/utils.cjs.js.map +1 -0
  18. package/dist/sources/ConfigSources.cjs.js +178 -0
  19. package/dist/sources/ConfigSources.cjs.js.map +1 -0
  20. package/dist/sources/EnvConfigSource.cjs.js +88 -0
  21. package/dist/sources/EnvConfigSource.cjs.js.map +1 -0
  22. package/dist/sources/FileConfigSource.cjs.js +153 -0
  23. package/dist/sources/FileConfigSource.cjs.js.map +1 -0
  24. package/dist/sources/MergedConfigSource.cjs.js +72 -0
  25. package/dist/sources/MergedConfigSource.cjs.js.map +1 -0
  26. package/dist/sources/MutableConfigSource.cjs.js +75 -0
  27. package/dist/sources/MutableConfigSource.cjs.js.map +1 -0
  28. package/dist/sources/ObservableConfigProxy.cjs.js +123 -0
  29. package/dist/sources/ObservableConfigProxy.cjs.js.map +1 -0
  30. package/dist/sources/RemoteConfigSource.cjs.js +107 -0
  31. package/dist/sources/RemoteConfigSource.cjs.js.map +1 -0
  32. package/dist/sources/StaticConfigSource.cjs.js +95 -0
  33. package/dist/sources/StaticConfigSource.cjs.js.map +1 -0
  34. package/dist/sources/transform/apply.cjs.js +79 -0
  35. package/dist/sources/transform/apply.cjs.js.map +1 -0
  36. package/dist/sources/transform/include.cjs.js +107 -0
  37. package/dist/sources/transform/include.cjs.js.map +1 -0
  38. package/dist/sources/transform/substitution.cjs.js +34 -0
  39. package/dist/sources/transform/substitution.cjs.js.map +1 -0
  40. package/dist/sources/transform/utils.cjs.js +13 -0
  41. package/dist/sources/transform/utils.cjs.js.map +1 -0
  42. package/dist/sources/utils.cjs.js +38 -0
  43. package/dist/sources/utils.cjs.js.map +1 -0
  44. package/package.json +14 -7
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MutableConfigSource.cjs.js","sources":["../../src/sources/MutableConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DeferredPromise, JsonObject, createDeferred } from '@backstage/types';\nimport {\n AsyncConfigSourceGenerator,\n ConfigSource,\n ReadConfigDataOptions,\n} from './types';\nimport { waitOrAbort } from './utils';\n\n/**\n * Options for {@link MutableConfigSource.create}.\n *\n * @public\n */\nexport interface MutableConfigSourceOptions {\n data?: JsonObject;\n context?: string;\n}\n\n/**\n * A config source that can be updated with new data.\n *\n * @public\n */\nexport class MutableConfigSource implements ConfigSource {\n /**\n * Creates a new mutable config source.\n *\n * @param options - Options for the config source.\n * @returns A new mutable config source.\n */\n static create(options?: MutableConfigSourceOptions): MutableConfigSource {\n return new MutableConfigSource(\n options?.context ?? 'mutable-config',\n options?.data,\n );\n }\n\n #currentData?: JsonObject;\n #deferred: DeferredPromise<void>;\n readonly #context: string;\n readonly #abortController = new AbortController();\n\n private constructor(context: string, initialData?: JsonObject) {\n this.#currentData = initialData;\n this.#context = context;\n this.#deferred = createDeferred();\n }\n\n async *readConfigData(\n options?: ReadConfigDataOptions | undefined,\n ): AsyncConfigSourceGenerator {\n let deferredPromise = this.#deferred;\n\n if (this.#currentData !== undefined) {\n yield { configs: [{ data: this.#currentData, context: this.#context }] };\n }\n\n for (;;) {\n const [ok] = await waitOrAbort(deferredPromise, [\n options?.signal,\n this.#abortController.signal,\n ]);\n if (!ok) {\n return;\n }\n deferredPromise = this.#deferred;\n\n if (this.#currentData !== undefined) {\n yield {\n configs: [{ data: this.#currentData, context: this.#context }],\n };\n }\n }\n }\n\n /**\n * Set the data of the config source.\n *\n * @param data - The new data to set\n */\n setData(data: JsonObject): void {\n if (!this.#abortController.signal.aborted) {\n this.#currentData = data;\n const oldDeferred = this.#deferred;\n this.#deferred = createDeferred();\n oldDeferred.resolve();\n }\n }\n\n /**\n * Close the config source, preventing any further updates.\n */\n close(): void {\n this.#currentData = undefined;\n this.#abortController.abort();\n }\n\n toString() {\n return `MutableConfigSource{}`;\n }\n}\n"],"names":["createDeferred","waitOrAbort"],"mappings":";;;;;AAuCO,MAAM,mBAA4C,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOvD,OAAO,OAAO,OAA2D,EAAA;AACvE,IAAA,OAAO,IAAI,mBAAA;AAAA,MACT,SAAS,OAAW,IAAA,gBAAA;AAAA,MACpB,OAAS,EAAA;AAAA,KACX;AAAA;AACF,EAEA,YAAA;AAAA,EACA,SAAA;AAAA,EACS,QAAA;AAAA,EACA,gBAAA,GAAmB,IAAI,eAAgB,EAAA;AAAA,EAExC,WAAA,CAAY,SAAiB,WAA0B,EAAA;AAC7D,IAAA,IAAA,CAAK,YAAe,GAAA,WAAA;AACpB,IAAA,IAAA,CAAK,QAAW,GAAA,OAAA;AAChB,IAAA,IAAA,CAAK,YAAYA,oBAAe,EAAA;AAAA;AAClC,EAEA,OAAO,eACL,OAC4B,EAAA;AAC5B,IAAA,IAAI,kBAAkB,IAAK,CAAA,SAAA;AAE3B,IAAI,IAAA,IAAA,CAAK,iBAAiB,KAAW,CAAA,EAAA;AACnC,MAAM,MAAA,EAAE,OAAS,EAAA,CAAC,EAAE,IAAA,EAAM,IAAK,CAAA,YAAA,EAAc,OAAS,EAAA,IAAA,CAAK,QAAS,EAAC,CAAE,EAAA;AAAA;AAGzE,IAAS,WAAA;AACP,MAAA,MAAM,CAAC,EAAE,CAAI,GAAA,MAAMC,kBAAY,eAAiB,EAAA;AAAA,QAC9C,OAAS,EAAA,MAAA;AAAA,QACT,KAAK,gBAAiB,CAAA;AAAA,OACvB,CAAA;AACD,MAAA,IAAI,CAAC,EAAI,EAAA;AACP,QAAA;AAAA;AAEF,MAAA,eAAA,GAAkB,IAAK,CAAA,SAAA;AAEvB,MAAI,IAAA,IAAA,CAAK,iBAAiB,KAAW,CAAA,EAAA;AACnC,QAAM,MAAA;AAAA,UACJ,OAAA,EAAS,CAAC,EAAE,IAAA,EAAM,KAAK,YAAc,EAAA,OAAA,EAAS,IAAK,CAAA,QAAA,EAAU;AAAA,SAC/D;AAAA;AACF;AACF;AACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,IAAwB,EAAA;AAC9B,IAAA,IAAI,CAAC,IAAA,CAAK,gBAAiB,CAAA,MAAA,CAAO,OAAS,EAAA;AACzC,MAAA,IAAA,CAAK,YAAe,GAAA,IAAA;AACpB,MAAA,MAAM,cAAc,IAAK,CAAA,SAAA;AACzB,MAAA,IAAA,CAAK,YAAYD,oBAAe,EAAA;AAChC,MAAA,WAAA,CAAY,OAAQ,EAAA;AAAA;AACtB;AACF;AAAA;AAAA;AAAA,EAKA,KAAc,GAAA;AACZ,IAAA,IAAA,CAAK,YAAe,GAAA,KAAA,CAAA;AACpB,IAAA,IAAA,CAAK,iBAAiB,KAAM,EAAA;AAAA;AAC9B,EAEA,QAAW,GAAA;AACT,IAAO,OAAA,CAAA,qBAAA,CAAA;AAAA;AAEX;;;;"}
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ var config = require('@backstage/config');
4
+ var isEqual = require('lodash/isEqual');
5
+
6
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
7
+
8
+ var isEqual__default = /*#__PURE__*/_interopDefaultCompat(isEqual);
9
+
10
+ class ObservableConfigProxy {
11
+ constructor(parent, parentKey, abortController) {
12
+ this.parent = parent;
13
+ this.parentKey = parentKey;
14
+ this.abortController = abortController;
15
+ if (parent && !parentKey) {
16
+ throw new Error("parentKey is required if parent is set");
17
+ }
18
+ }
19
+ config = new config.ConfigReader({});
20
+ subscribers = [];
21
+ static create(abortController) {
22
+ return new ObservableConfigProxy(void 0, void 0, abortController);
23
+ }
24
+ setConfig(config) {
25
+ if (this.parent) {
26
+ throw new Error("immutable");
27
+ }
28
+ const changed = !isEqual__default.default(this.config.get(), config.get());
29
+ this.config = config;
30
+ if (changed) {
31
+ for (const subscriber of this.subscribers) {
32
+ try {
33
+ subscriber();
34
+ } catch (error) {
35
+ console.error(`Config subscriber threw error, ${error}`);
36
+ }
37
+ }
38
+ }
39
+ }
40
+ close() {
41
+ if (!this.abortController) {
42
+ throw new Error("Only the root config can be closed");
43
+ }
44
+ this.abortController.abort();
45
+ }
46
+ subscribe(onChange) {
47
+ if (this.parent) {
48
+ return this.parent.subscribe(onChange);
49
+ }
50
+ this.subscribers.push(onChange);
51
+ return {
52
+ unsubscribe: () => {
53
+ const index = this.subscribers.indexOf(onChange);
54
+ if (index >= 0) {
55
+ this.subscribers.splice(index, 1);
56
+ }
57
+ }
58
+ };
59
+ }
60
+ select(required) {
61
+ if (this.parent && this.parentKey) {
62
+ if (required) {
63
+ return this.parent.select(true).getConfig(this.parentKey);
64
+ }
65
+ return this.parent.select(false)?.getOptionalConfig(this.parentKey);
66
+ }
67
+ return this.config;
68
+ }
69
+ has(key) {
70
+ return this.select(false)?.has(key) ?? false;
71
+ }
72
+ keys() {
73
+ return this.select(false)?.keys() ?? [];
74
+ }
75
+ get(key) {
76
+ return this.select(true).get(key);
77
+ }
78
+ getOptional(key) {
79
+ return this.select(false)?.getOptional(key);
80
+ }
81
+ getConfig(key) {
82
+ return new ObservableConfigProxy(this, key);
83
+ }
84
+ getOptionalConfig(key) {
85
+ if (this.select(false)?.has(key)) {
86
+ return new ObservableConfigProxy(this, key);
87
+ }
88
+ return void 0;
89
+ }
90
+ getConfigArray(key) {
91
+ return this.select(true).getConfigArray(key);
92
+ }
93
+ getOptionalConfigArray(key) {
94
+ return this.select(false)?.getOptionalConfigArray(key);
95
+ }
96
+ getNumber(key) {
97
+ return this.select(true).getNumber(key);
98
+ }
99
+ getOptionalNumber(key) {
100
+ return this.select(false)?.getOptionalNumber(key);
101
+ }
102
+ getBoolean(key) {
103
+ return this.select(true).getBoolean(key);
104
+ }
105
+ getOptionalBoolean(key) {
106
+ return this.select(false)?.getOptionalBoolean(key);
107
+ }
108
+ getString(key) {
109
+ return this.select(true).getString(key);
110
+ }
111
+ getOptionalString(key) {
112
+ return this.select(false)?.getOptionalString(key);
113
+ }
114
+ getStringArray(key) {
115
+ return this.select(true).getStringArray(key);
116
+ }
117
+ getOptionalStringArray(key) {
118
+ return this.select(false)?.getOptionalStringArray(key);
119
+ }
120
+ }
121
+
122
+ exports.ObservableConfigProxy = ObservableConfigProxy;
123
+ //# sourceMappingURL=ObservableConfigProxy.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ObservableConfigProxy.cjs.js","sources":["../../src/sources/ObservableConfigProxy.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config, ConfigReader } from '@backstage/config';\nimport { JsonValue } from '@backstage/types';\nimport isEqual from 'lodash/isEqual';\n\nexport class ObservableConfigProxy implements Config {\n private config: Config = new ConfigReader({});\n\n private readonly subscribers: (() => void)[] = [];\n\n static create(abortController: AbortController): ObservableConfigProxy {\n return new ObservableConfigProxy(undefined, undefined, abortController);\n }\n\n private constructor(\n private readonly parent?: ObservableConfigProxy,\n private readonly parentKey?: string,\n private readonly abortController?: AbortController,\n ) {\n if (parent && !parentKey) {\n throw new Error('parentKey is required if parent is set');\n }\n }\n\n setConfig(config: Config) {\n if (this.parent) {\n throw new Error('immutable');\n }\n\n // We only notify subscribers if the data contents of the config actually\n // changed. If they didn't, there's no point in callers trying to re-read\n // them. However we still want to replace the local config object, since its\n // runtime implementation could be entirely different.\n const changed = !isEqual(this.config.get(), config.get());\n\n this.config = config;\n\n if (changed) {\n for (const subscriber of this.subscribers) {\n try {\n subscriber();\n } catch (error) {\n console.error(`Config subscriber threw error, ${error}`);\n }\n }\n }\n }\n\n close() {\n if (!this.abortController) {\n throw new Error('Only the root config can be closed');\n }\n this.abortController.abort();\n }\n\n subscribe(onChange: () => void): { unsubscribe: () => void } {\n if (this.parent) {\n return this.parent.subscribe(onChange);\n }\n\n this.subscribers.push(onChange);\n return {\n unsubscribe: () => {\n const index = this.subscribers.indexOf(onChange);\n if (index >= 0) {\n this.subscribers.splice(index, 1);\n }\n },\n };\n }\n\n private select(required: true): Config;\n private select(required: false): Config | undefined;\n private select(required: boolean): Config | undefined {\n if (this.parent && this.parentKey) {\n if (required) {\n return this.parent.select(true).getConfig(this.parentKey);\n }\n return this.parent.select(false)?.getOptionalConfig(this.parentKey);\n }\n\n return this.config;\n }\n\n has(key: string): boolean {\n return this.select(false)?.has(key) ?? false;\n }\n keys(): string[] {\n return this.select(false)?.keys() ?? [];\n }\n get<T = JsonValue>(key?: string): T {\n return this.select(true).get(key);\n }\n getOptional<T = JsonValue>(key?: string): T | undefined {\n return this.select(false)?.getOptional(key);\n }\n getConfig(key: string): Config {\n return new ObservableConfigProxy(this, key);\n }\n getOptionalConfig(key: string): Config | undefined {\n if (this.select(false)?.has(key)) {\n return new ObservableConfigProxy(this, key);\n }\n return undefined;\n }\n getConfigArray(key: string): Config[] {\n return this.select(true).getConfigArray(key);\n }\n getOptionalConfigArray(key: string): Config[] | undefined {\n return this.select(false)?.getOptionalConfigArray(key);\n }\n getNumber(key: string): number {\n return this.select(true).getNumber(key);\n }\n getOptionalNumber(key: string): number | undefined {\n return this.select(false)?.getOptionalNumber(key);\n }\n getBoolean(key: string): boolean {\n return this.select(true).getBoolean(key);\n }\n getOptionalBoolean(key: string): boolean | undefined {\n return this.select(false)?.getOptionalBoolean(key);\n }\n getString(key: string): string {\n return this.select(true).getString(key);\n }\n getOptionalString(key: string): string | undefined {\n return this.select(false)?.getOptionalString(key);\n }\n getStringArray(key: string): string[] {\n return this.select(true).getStringArray(key);\n }\n getOptionalStringArray(key: string): string[] | undefined {\n return this.select(false)?.getOptionalStringArray(key);\n }\n}\n"],"names":["ConfigReader","isEqual"],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var types = require('@backstage/types');
5
+ var isEqual = require('lodash/isEqual');
6
+ var fetch = require('node-fetch');
7
+ var apply = require('./transform/apply.cjs.js');
8
+ var utils = require('./utils.cjs.js');
9
+
10
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
11
+
12
+ var isEqual__default = /*#__PURE__*/_interopDefaultCompat(isEqual);
13
+ var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
14
+
15
+ const DEFAULT_RELOAD_INTERVAL = { seconds: 60 };
16
+ class RemoteConfigSource {
17
+ /**
18
+ * Creates a new {@link RemoteConfigSource}.
19
+ *
20
+ * @param options - Options for the source.
21
+ * @returns A new remote config source.
22
+ */
23
+ static create(options) {
24
+ try {
25
+ new URL(options.url);
26
+ } catch (error) {
27
+ throw new Error(
28
+ `Invalid URL provided to remote config source, '${options.url}', ${error}`
29
+ );
30
+ }
31
+ return new RemoteConfigSource(options);
32
+ }
33
+ #url;
34
+ #reloadIntervalMs;
35
+ #transformer;
36
+ #parser;
37
+ constructor(options) {
38
+ this.#url = options.url;
39
+ this.#reloadIntervalMs = types.durationToMilliseconds(
40
+ options.reloadInterval ?? DEFAULT_RELOAD_INTERVAL
41
+ );
42
+ this.#transformer = apply.createConfigTransformer({
43
+ substitutionFunc: options.substitutionFunc
44
+ });
45
+ this.#parser = options.parser ?? utils.parseYamlContent;
46
+ }
47
+ async *readConfigData(options) {
48
+ let data = await this.#load();
49
+ yield { configs: [{ data, context: this.#url }] };
50
+ for (; ; ) {
51
+ await this.#wait(options?.signal);
52
+ if (options?.signal?.aborted) {
53
+ return;
54
+ }
55
+ try {
56
+ const newData = await this.#load(options?.signal);
57
+ if (newData && !isEqual__default.default(data, newData)) {
58
+ data = newData;
59
+ yield { configs: [{ data, context: this.#url }] };
60
+ }
61
+ } catch (error) {
62
+ if (error.name !== "AbortError") {
63
+ console.error(`Failed to read config from ${this.#url}, ${error}`);
64
+ }
65
+ }
66
+ }
67
+ }
68
+ toString() {
69
+ return `RemoteConfigSource{path="${this.#url}"}`;
70
+ }
71
+ async #load(signal) {
72
+ const res = await fetch__default.default(this.#url, {
73
+ signal
74
+ });
75
+ if (!res.ok) {
76
+ throw await errors.ResponseError.fromResponse(res);
77
+ }
78
+ const contents = await res.text();
79
+ const { result: rawData } = await this.#parser({ contents });
80
+ if (rawData === void 0) {
81
+ throw new Error("configuration data is null");
82
+ }
83
+ const data = await this.#transformer(rawData);
84
+ if (typeof data !== "object") {
85
+ throw new Error("configuration data is not an object");
86
+ } else if (Array.isArray(data)) {
87
+ throw new Error(
88
+ "configuration data is an array, expected an object instead"
89
+ );
90
+ }
91
+ return data;
92
+ }
93
+ async #wait(signal) {
94
+ return new Promise((resolve) => {
95
+ const timeoutId = setTimeout(onDone, this.#reloadIntervalMs);
96
+ signal?.addEventListener("abort", onDone);
97
+ function onDone() {
98
+ clearTimeout(timeoutId);
99
+ signal?.removeEventListener("abort", onDone);
100
+ resolve();
101
+ }
102
+ });
103
+ }
104
+ }
105
+
106
+ exports.RemoteConfigSource = RemoteConfigSource;
107
+ //# sourceMappingURL=RemoteConfigSource.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RemoteConfigSource.cjs.js","sources":["../../src/sources/RemoteConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ResponseError } from '@backstage/errors';\nimport {\n HumanDuration,\n JsonObject,\n durationToMilliseconds,\n} from '@backstage/types';\nimport isEqual from 'lodash/isEqual';\nimport fetch from 'node-fetch';\nimport { ConfigTransformer, createConfigTransformer } from './transform';\nimport {\n AsyncConfigSourceGenerator,\n ConfigSource,\n SubstitutionFunc,\n ReadConfigDataOptions,\n Parser,\n} from './types';\nimport { parseYamlContent } from './utils';\n\nconst DEFAULT_RELOAD_INTERVAL = { seconds: 60 };\n\n/**\n * Options for {@link RemoteConfigSource.create}.\n *\n * @public\n */\nexport interface RemoteConfigSourceOptions {\n /**\n * The URL to load the config from.\n */\n url: string;\n\n /**\n * How often to reload the config from the remote URL, defaults to 1 minute.\n *\n * Set to Infinity to disable reloading, for example `{ days: Infinity }`.\n */\n reloadInterval?: HumanDuration;\n\n /**\n * A substitution function to use instead of the default environment substitution.\n */\n substitutionFunc?: SubstitutionFunc;\n\n /**\n * A content parsing function to transform string content to configuration values.\n */\n parser?: Parser;\n}\n\n/**\n * A config source that loads configuration from a remote URL.\n *\n * @public\n */\nexport class RemoteConfigSource implements ConfigSource {\n /**\n * Creates a new {@link RemoteConfigSource}.\n *\n * @param options - Options for the source.\n * @returns A new remote config source.\n */\n static create(options: RemoteConfigSourceOptions): ConfigSource {\n try {\n // eslint-disable-next-line no-new\n new URL(options.url);\n } catch (error) {\n throw new Error(\n `Invalid URL provided to remote config source, '${options.url}', ${error}`,\n );\n }\n return new RemoteConfigSource(options);\n }\n\n readonly #url: string;\n readonly #reloadIntervalMs: number;\n readonly #transformer: ConfigTransformer;\n readonly #parser: Parser;\n\n private constructor(options: RemoteConfigSourceOptions) {\n this.#url = options.url;\n this.#reloadIntervalMs = durationToMilliseconds(\n options.reloadInterval ?? DEFAULT_RELOAD_INTERVAL,\n );\n this.#transformer = createConfigTransformer({\n substitutionFunc: options.substitutionFunc,\n });\n this.#parser = options.parser ?? parseYamlContent;\n }\n\n async *readConfigData(\n options?: ReadConfigDataOptions | undefined,\n ): AsyncConfigSourceGenerator {\n let data = await this.#load();\n\n yield { configs: [{ data, context: this.#url }] };\n\n for (;;) {\n await this.#wait(options?.signal);\n\n if (options?.signal?.aborted) {\n return;\n }\n\n try {\n const newData = await this.#load(options?.signal);\n if (newData && !isEqual(data, newData)) {\n data = newData;\n yield { configs: [{ data, context: this.#url }] };\n }\n } catch (error) {\n if (error.name !== 'AbortError') {\n console.error(`Failed to read config from ${this.#url}, ${error}`);\n }\n }\n }\n }\n\n toString() {\n return `RemoteConfigSource{path=\"${this.#url}\"}`;\n }\n\n async #load(signal?: AbortSignal): Promise<JsonObject> {\n const res = await fetch(this.#url, {\n signal: signal as import('node-fetch').RequestInit['signal'],\n });\n if (!res.ok) {\n throw await ResponseError.fromResponse(res);\n }\n\n const contents = await res.text();\n const { result: rawData } = await this.#parser({ contents });\n if (rawData === undefined) {\n /**\n * This error message is/was coupled to the implementation and with refactoring it is no longer truly accurate\n * This behavior is also inconsistent with {@link FileConfigSource}, which doesn't error on unparseable or empty\n * content\n *\n * Preserving to not make a breaking change\n */\n throw new Error('configuration data is null');\n }\n\n const data = await this.#transformer(rawData);\n if (typeof data !== 'object') {\n throw new Error('configuration data is not an object');\n } else if (Array.isArray(data)) {\n throw new Error(\n 'configuration data is an array, expected an object instead',\n );\n }\n return data;\n }\n\n async #wait(signal?: AbortSignal) {\n return new Promise<void>(resolve => {\n const timeoutId = setTimeout(onDone, this.#reloadIntervalMs);\n signal?.addEventListener('abort', onDone);\n\n function onDone() {\n clearTimeout(timeoutId);\n signal?.removeEventListener('abort', onDone);\n resolve();\n }\n });\n }\n}\n"],"names":["durationToMilliseconds","createConfigTransformer","parseYamlContent","isEqual","fetch","ResponseError"],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ var types = require('@backstage/types');
4
+
5
+ class StaticObservableConfigSource {
6
+ constructor(data, context) {
7
+ this.data = data;
8
+ this.context = context;
9
+ }
10
+ async *readConfigData(options) {
11
+ const queue = new Array();
12
+ let deferred = types.createDeferred();
13
+ const sub = this.data.subscribe({
14
+ next(value) {
15
+ queue.push(value);
16
+ deferred.resolve();
17
+ deferred = types.createDeferred();
18
+ },
19
+ complete() {
20
+ deferred.resolve();
21
+ }
22
+ });
23
+ const signal = options?.signal;
24
+ if (signal) {
25
+ const onAbort = () => {
26
+ sub.unsubscribe();
27
+ queue.length = 0;
28
+ deferred.resolve();
29
+ signal.removeEventListener("abort", onAbort);
30
+ };
31
+ signal.addEventListener("abort", onAbort);
32
+ }
33
+ for (; ; ) {
34
+ await deferred;
35
+ if (queue.length === 0) {
36
+ return;
37
+ }
38
+ while (queue.length > 0) {
39
+ yield { configs: [{ data: queue.shift(), context: this.context }] };
40
+ }
41
+ }
42
+ }
43
+ }
44
+ function isObservable(value) {
45
+ return "subscribe" in value && typeof value.subscribe === "function";
46
+ }
47
+ function isAsyncIterable(value) {
48
+ return Symbol.asyncIterator in value;
49
+ }
50
+ class StaticConfigSource {
51
+ constructor(promise, context) {
52
+ this.promise = promise;
53
+ this.context = context;
54
+ }
55
+ /**
56
+ * Creates a new {@link StaticConfigSource}.
57
+ *
58
+ * @param options - Options for the config source
59
+ * @returns A new static config source
60
+ */
61
+ static create(options) {
62
+ const { data, context = "static-config" } = options;
63
+ if (!data) {
64
+ return {
65
+ async *readConfigData() {
66
+ yield { configs: [] };
67
+ return;
68
+ }
69
+ };
70
+ }
71
+ if (isObservable(data)) {
72
+ return new StaticObservableConfigSource(data, context);
73
+ }
74
+ if (isAsyncIterable(data)) {
75
+ return {
76
+ async *readConfigData() {
77
+ for await (const value of data) {
78
+ yield { configs: [{ data: value, context }] };
79
+ }
80
+ }
81
+ };
82
+ }
83
+ return new StaticConfigSource(data, context);
84
+ }
85
+ async *readConfigData() {
86
+ yield { configs: [{ data: await this.promise, context: this.context }] };
87
+ return;
88
+ }
89
+ toString() {
90
+ return `StaticConfigSource{}`;
91
+ }
92
+ }
93
+
94
+ exports.StaticConfigSource = StaticConfigSource;
95
+ //# sourceMappingURL=StaticConfigSource.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StaticConfigSource.cjs.js","sources":["../../src/sources/StaticConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { JsonObject, Observable, createDeferred } from '@backstage/types';\nimport {\n AsyncConfigSourceGenerator,\n ConfigSource,\n ReadConfigDataOptions,\n} from './types';\n\n/**\n * Options for {@link StaticConfigSource.create}.\n *\n * @public\n */\nexport interface StaticConfigSourceOptions {\n data:\n | JsonObject\n | Observable<JsonObject>\n | PromiseLike<JsonObject>\n | AsyncIterable<JsonObject>;\n context?: string;\n}\n\n/** @internal */\nclass StaticObservableConfigSource implements ConfigSource {\n constructor(\n private readonly data: Observable<JsonObject>,\n private readonly context: string,\n ) {}\n\n async *readConfigData(\n options?: ReadConfigDataOptions | undefined,\n ): AsyncConfigSourceGenerator {\n const queue = new Array<JsonObject>();\n let deferred = createDeferred();\n\n const sub = this.data.subscribe({\n next(value) {\n queue.push(value);\n deferred.resolve();\n deferred = createDeferred();\n },\n complete() {\n deferred.resolve();\n },\n });\n\n const signal = options?.signal;\n if (signal) {\n const onAbort = () => {\n sub.unsubscribe();\n queue.length = 0;\n deferred.resolve();\n signal.removeEventListener('abort', onAbort);\n };\n\n signal.addEventListener('abort', onAbort);\n }\n\n for (;;) {\n await deferred;\n if (queue.length === 0) {\n return;\n }\n while (queue.length > 0) {\n yield { configs: [{ data: queue.shift()!, context: this.context }] };\n }\n }\n }\n}\n\nfunction isObservable<T>(value: {}): value is Observable<T> {\n return 'subscribe' in value && typeof (value as any).subscribe === 'function';\n}\n\nfunction isAsyncIterable<T>(value: {}): value is AsyncIterable<T> {\n return Symbol.asyncIterator in value;\n}\n\n/**\n * A configuration source that reads from a static object, promise, iterable, or observable.\n *\n * @public\n */\nexport class StaticConfigSource implements ConfigSource {\n /**\n * Creates a new {@link StaticConfigSource}.\n *\n * @param options - Options for the config source\n * @returns A new static config source\n */\n static create(options: StaticConfigSourceOptions): ConfigSource {\n const { data, context = 'static-config' } = options;\n if (!data) {\n return {\n async *readConfigData(): AsyncConfigSourceGenerator {\n yield { configs: [] };\n return;\n },\n };\n }\n\n if (isObservable<JsonObject>(data)) {\n return new StaticObservableConfigSource(data, context);\n }\n\n if (isAsyncIterable(data)) {\n return {\n async *readConfigData(): AsyncConfigSourceGenerator {\n for await (const value of data) {\n yield { configs: [{ data: value, context }] };\n }\n },\n };\n }\n\n return new StaticConfigSource(data, context);\n }\n\n private constructor(\n private readonly promise: JsonObject | PromiseLike<JsonObject>,\n private readonly context: string,\n ) {}\n\n async *readConfigData(): AsyncConfigSourceGenerator {\n yield { configs: [{ data: await this.promise, context: this.context }] };\n return;\n }\n\n toString() {\n return `StaticConfigSource{}`;\n }\n}\n"],"names":["createDeferred"],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ var errors = require('@backstage/errors');
4
+ var utils = require('./utils.cjs.js');
5
+ var substitution = require('./substitution.cjs.js');
6
+ var include = require('./include.cjs.js');
7
+
8
+ async function applyConfigTransforms(input, context, transforms) {
9
+ async function transform(inputObj, path, baseDir) {
10
+ let obj = inputObj;
11
+ let dir = baseDir;
12
+ for (const tf of transforms) {
13
+ try {
14
+ const result = await tf(inputObj, { dir });
15
+ if (result.applied) {
16
+ if (result.value === void 0) {
17
+ return void 0;
18
+ }
19
+ obj = result.value;
20
+ dir = result?.newDir ?? dir;
21
+ break;
22
+ }
23
+ } catch (error) {
24
+ errors.assertError(error);
25
+ throw new Error(`error at ${path}, ${error.message}`);
26
+ }
27
+ }
28
+ if (typeof obj !== "object") {
29
+ return obj;
30
+ } else if (obj === null) {
31
+ return null;
32
+ } else if (Array.isArray(obj)) {
33
+ const arr = new Array();
34
+ for (const [index, value] of obj.entries()) {
35
+ const out2 = await transform(value, `${path}[${index}]`, dir);
36
+ if (out2 !== void 0) {
37
+ arr.push(out2);
38
+ }
39
+ }
40
+ return arr;
41
+ }
42
+ const out = {};
43
+ for (const [key, value] of Object.entries(obj)) {
44
+ if (value !== void 0) {
45
+ const result = await transform(value, `${path}.${key}`, dir);
46
+ if (result !== void 0) {
47
+ out[key] = result;
48
+ }
49
+ }
50
+ }
51
+ return out;
52
+ }
53
+ const finalData = await transform(input, "", context?.dir);
54
+ if (!utils.isObject(finalData)) {
55
+ throw new TypeError("expected object at config root");
56
+ }
57
+ return finalData;
58
+ }
59
+ function createConfigTransformer(options) {
60
+ const {
61
+ substitutionFunc = async (name) => process.env[name]?.trim(),
62
+ readFile
63
+ } = options;
64
+ const substitutionTransform = substitution.createSubstitutionTransform(substitutionFunc);
65
+ const transforms = [substitutionTransform];
66
+ if (readFile) {
67
+ const includeTransform = include.createIncludeTransform(
68
+ substitutionFunc,
69
+ readFile,
70
+ substitutionTransform
71
+ );
72
+ transforms.push(includeTransform);
73
+ }
74
+ return async (input, ctx) => applyConfigTransforms(input, ctx ?? {}, transforms);
75
+ }
76
+
77
+ exports.applyConfigTransforms = applyConfigTransforms;
78
+ exports.createConfigTransformer = createConfigTransformer;
79
+ //# sourceMappingURL=apply.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.cjs.js","sources":["../../../src/sources/transform/apply.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 { JsonObject, JsonValue } from '@backstage/types';\nimport { assertError } from '@backstage/errors';\nimport { TransformContext, TransformFunc } from './types';\nimport { isObject } from './utils';\nimport { createSubstitutionTransform } from './substitution';\nimport { createIncludeTransform } from './include';\nimport { SubstitutionFunc } from '../types';\n\n/**\n * Applies a set of transforms to raw configuration data.\n */\nexport async function applyConfigTransforms(\n input: JsonValue,\n context: { dir?: string },\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, { dir });\n if (result.applied) {\n if (result.value === undefined) {\n return undefined;\n }\n obj = result.value;\n dir = result?.newDir ?? 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 null;\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, '', context?.dir);\n if (!isObject(finalData)) {\n throw new TypeError('expected object at config root');\n }\n return finalData;\n}\n\n/** @internal */\nexport type ConfigTransformer = (\n input: JsonObject,\n context?: TransformContext,\n) => Promise<JsonObject>;\n\n/** @internal */\nexport function createConfigTransformer(options: {\n substitutionFunc?: SubstitutionFunc;\n readFile?(path: string): Promise<string>;\n}): ConfigTransformer {\n const {\n substitutionFunc = async name => process.env[name]?.trim(),\n readFile,\n } = options;\n const substitutionTransform = createSubstitutionTransform(substitutionFunc);\n const transforms = [substitutionTransform];\n if (readFile) {\n const includeTransform = createIncludeTransform(\n substitutionFunc,\n readFile,\n substitutionTransform,\n );\n transforms.push(includeTransform);\n }\n\n return async (input, ctx) =>\n applyConfigTransforms(input, ctx ?? {}, transforms);\n}\n"],"names":["assertError","out","isObject","createSubstitutionTransform","createIncludeTransform"],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ var yaml = require('yaml');
4
+ var path = require('path');
5
+ var utils = require('./utils.cjs.js');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
+
9
+ var yaml__default = /*#__PURE__*/_interopDefaultCompat(yaml);
10
+
11
+ const includeFileParser = {
12
+ ".json": async (content) => JSON.parse(content),
13
+ ".yaml": async (content) => yaml__default.default.parse(content),
14
+ ".yml": async (content) => yaml__default.default.parse(content)
15
+ };
16
+ function createIncludeTransform(env, readFile, substitute) {
17
+ return async (input, context) => {
18
+ const { dir } = context;
19
+ if (!dir) {
20
+ throw new Error("Include transform requires a base directory");
21
+ }
22
+ if (!utils.isObject(input)) {
23
+ return { applied: false };
24
+ }
25
+ const [includeKey] = Object.keys(input).filter((key) => key.startsWith("$"));
26
+ if (includeKey) {
27
+ if (Object.keys(input).length !== 1) {
28
+ throw new Error(
29
+ `include key ${includeKey} should not have adjacent keys`
30
+ );
31
+ }
32
+ } else {
33
+ return { applied: false };
34
+ }
35
+ const rawIncludedValue = input[includeKey];
36
+ if (typeof rawIncludedValue !== "string") {
37
+ throw new Error(`${includeKey} include value is not a string`);
38
+ }
39
+ const substituteResults = await substitute(rawIncludedValue, { dir });
40
+ const includeValue = substituteResults.applied ? substituteResults.value : rawIncludedValue;
41
+ if (includeValue === void 0 || typeof includeValue !== "string") {
42
+ throw new Error(`${includeKey} substitution value was undefined`);
43
+ }
44
+ switch (includeKey) {
45
+ case "$file":
46
+ try {
47
+ const value = await readFile(path.resolve(dir, includeValue));
48
+ return { applied: true, value: value.trimEnd() };
49
+ } catch (error) {
50
+ throw new Error(`failed to read file ${includeValue}, ${error}`);
51
+ }
52
+ case "$env":
53
+ try {
54
+ return { applied: true, value: await env(includeValue) };
55
+ } catch (error) {
56
+ throw new Error(`failed to read env ${includeValue}, ${error}`);
57
+ }
58
+ case "$include": {
59
+ const [filePath, dataPath] = includeValue.split(/#(.*)/);
60
+ const ext = path.extname(filePath);
61
+ const parser = includeFileParser[ext];
62
+ if (!parser) {
63
+ throw new Error(
64
+ `no configuration parser available for included file ${filePath}`
65
+ );
66
+ }
67
+ const path$1 = path.resolve(dir, filePath);
68
+ const content = await readFile(path$1);
69
+ const newDir = path.dirname(path$1);
70
+ const parts = dataPath ? dataPath.split(".") : [];
71
+ let value;
72
+ try {
73
+ value = await parser(content);
74
+ } catch (error) {
75
+ throw new Error(
76
+ `failed to parse included file ${filePath}, ${error}`
77
+ );
78
+ }
79
+ for (const [index, part] of parts.entries()) {
80
+ if (!utils.isObject(value)) {
81
+ const errPath = parts.slice(0, index).join(".");
82
+ throw new Error(
83
+ `value at '${errPath}' in included file ${filePath} is not an object`
84
+ );
85
+ }
86
+ value = value[part];
87
+ }
88
+ if (typeof value === "string") {
89
+ const substituted = await substitute(value, { dir: newDir });
90
+ if (substituted.applied) {
91
+ value = substituted.value;
92
+ }
93
+ }
94
+ return {
95
+ applied: true,
96
+ value,
97
+ newDir: newDir !== dir ? newDir : void 0
98
+ };
99
+ }
100
+ default:
101
+ throw new Error(`unknown include ${includeKey}`);
102
+ }
103
+ };
104
+ }
105
+
106
+ exports.createIncludeTransform = createIncludeTransform;
107
+ //# sourceMappingURL=include.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"include.cjs.js","sources":["../../../src/sources/transform/include.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 yaml from 'yaml';\nimport { extname, dirname, resolve as resolvePath } from 'path';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { isObject } from './utils';\nimport { TransformFunc, ReadFileFunc } from './types';\nimport { SubstitutionFunc } 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: SubstitutionFunc,\n readFile: ReadFileFunc,\n substitute: TransformFunc,\n): TransformFunc {\n return async (input, context) => {\n const { dir } = context;\n if (!dir) {\n throw new Error('Include transform requires a base directory');\n }\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, { dir });\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(dir, includeValue));\n return { applied: true, value: value.trimEnd() };\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(dir, filePath);\n const content = await readFile(path);\n const newDir = 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 if (typeof value === 'string') {\n const substituted = await substitute(value, { dir: newDir });\n if (substituted.applied) {\n value = substituted.value;\n }\n }\n\n return {\n applied: true,\n value,\n newDir: newDir !== dir ? newDir : undefined,\n };\n }\n\n default:\n throw new Error(`unknown include ${includeKey}`);\n }\n };\n}\n"],"names":["yaml","isObject","resolvePath","extname","path","dirname"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ function createSubstitutionTransform(env) {
4
+ return async (input) => {
5
+ if (typeof input !== "string") {
6
+ return { applied: false };
7
+ }
8
+ const parts = input.split(/(\$?\$\{[^{}]*\})/);
9
+ for (let i = 1; i < parts.length; i += 2) {
10
+ const part = parts[i];
11
+ if (part.startsWith("$$")) {
12
+ parts[i] = part.slice(1);
13
+ } else {
14
+ const indexOfFallbackSeparator = part.indexOf(":-");
15
+ if (indexOfFallbackSeparator > -1) {
16
+ const envVarValue = await env(
17
+ part.slice(2, indexOfFallbackSeparator).trim()
18
+ );
19
+ const fallbackValue = part.slice(indexOfFallbackSeparator + ":-".length, -1).trim();
20
+ parts[i] = envVarValue || fallbackValue || void 0;
21
+ } else {
22
+ parts[i] = await env(part.slice(2, -1).trim());
23
+ }
24
+ }
25
+ }
26
+ if (parts.some((part) => part === void 0)) {
27
+ return { applied: true, value: void 0 };
28
+ }
29
+ return { applied: true, value: parts.join("") };
30
+ };
31
+ }
32
+
33
+ exports.createSubstitutionTransform = createSubstitutionTransform;
34
+ //# sourceMappingURL=substitution.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"substitution.cjs.js","sources":["../../../src/sources/transform/substitution.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 { JsonValue } from '@backstage/types';\nimport { TransformFunc } from './types';\nimport { SubstitutionFunc } 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. Additionally, supports parameter substitution\n * syntax to provide a default or fallback value for a given environment variable if it is\n * unset; e.g. 'token ${MY_TOKEN:-xyz}' transforms to 'token xyz' if MY_TOKEN is unset.\n */\nexport function createSubstitutionTransform(\n env: SubstitutionFunc,\n): 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 const indexOfFallbackSeparator = part.indexOf(':-');\n\n if (indexOfFallbackSeparator > -1) {\n const envVarValue = await env(\n part.slice(2, indexOfFallbackSeparator).trim(),\n );\n const fallbackValue = part\n .slice(indexOfFallbackSeparator + ':-'.length, -1)\n .trim();\n\n parts[i] = envVarValue || fallbackValue || undefined;\n } else {\n parts[i] = await env(part.slice(2, -1).trim());\n }\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"],"names":[],"mappings":";;AA2BO,SAAS,4BACd,GACe,EAAA;AACf,EAAA,OAAO,OAAO,KAAqB,KAAA;AACjC,IAAI,IAAA,OAAO,UAAU,QAAU,EAAA;AAC7B,MAAO,OAAA,EAAE,SAAS,KAAM,EAAA;AAAA;AAG1B,IAAM,MAAA,KAAA,GAAgC,KAAM,CAAA,KAAA,CAAM,mBAAmB,CAAA;AACrE,IAAA,KAAA,IAAS,IAAI,CAAG,EAAA,CAAA,GAAI,KAAM,CAAA,MAAA,EAAQ,KAAK,CAAG,EAAA;AACxC,MAAM,MAAA,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,MAAI,IAAA,IAAA,CAAK,UAAW,CAAA,IAAI,CAAG,EAAA;AACzB,QAAA,KAAA,CAAM,CAAC,CAAA,GAAI,IAAK,CAAA,KAAA,CAAM,CAAC,CAAA;AAAA,OAClB,MAAA;AACL,QAAM,MAAA,wBAAA,GAA2B,IAAK,CAAA,OAAA,CAAQ,IAAI,CAAA;AAElD,QAAA,IAAI,2BAA2B,CAAI,CAAA,EAAA;AACjC,UAAA,MAAM,cAAc,MAAM,GAAA;AAAA,YACxB,IAAK,CAAA,KAAA,CAAM,CAAG,EAAA,wBAAwB,EAAE,IAAK;AAAA,WAC/C;AACA,UAAM,MAAA,aAAA,GAAgB,KACnB,KAAM,CAAA,wBAAA,GAA2B,KAAK,MAAQ,EAAA,CAAA,CAAE,EAChD,IAAK,EAAA;AAER,UAAM,KAAA,CAAA,CAAC,CAAI,GAAA,WAAA,IAAe,aAAiB,IAAA,KAAA,CAAA;AAAA,SACtC,MAAA;AACL,UAAM,KAAA,CAAA,CAAC,CAAI,GAAA,MAAM,GAAI,CAAA,IAAA,CAAK,MAAM,CAAG,EAAA,CAAA,CAAE,CAAE,CAAA,IAAA,EAAM,CAAA;AAAA;AAC/C;AACF;AAGF,IAAA,IAAI,KAAM,CAAA,IAAA,CAAK,CAAQ,IAAA,KAAA,IAAA,KAAS,MAAS,CAAG,EAAA;AAC1C,MAAA,OAAO,EAAE,OAAA,EAAS,IAAM,EAAA,KAAA,EAAO,KAAU,CAAA,EAAA;AAAA;AAE3C,IAAA,OAAO,EAAE,OAAS,EAAA,IAAA,EAAM,OAAO,KAAM,CAAA,IAAA,CAAK,EAAE,CAAE,EAAA;AAAA,GAChD;AACF;;;;"}