@backstage/config-loader 1.10.2 → 1.10.3-next.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,11 @@
1
1
  # @backstage/config-loader
2
2
 
3
+ ## 1.10.3-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - a73f495: Allow using `BACKSTAGE_ENV` for loading environment specific config files
8
+
3
9
  ## 1.10.2
4
10
 
5
11
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"loader.cjs.js","sources":["../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 { ConfigSources } from './sources';\n\n/**\n * @public\n * @deprecated Use {@link ConfigSources.default} instead.\n */\nexport type ConfigTarget = { path: string } | { url: string };\n\n/**\n * @public\n * @deprecated Use {@link ConfigSources.default} instead.\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\n/**\n * @public\n * @deprecated Use {@link ConfigSources.default} instead.\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 * @deprecated Use {@link ConfigSources.default} instead.\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 * @deprecated Use {@link ConfigSources.default} instead.\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 * @deprecated Use {@link ConfigSources.default} instead.\n */\nexport async function loadConfig(\n options: LoadConfigOptions,\n): Promise<LoadConfigResult> {\n const source = ConfigSources.default({\n substitutionFunc: options.experimentalEnvFunc,\n remote: options.remote && {\n reloadInterval: { seconds: options.remote.reloadIntervalSeconds },\n },\n watch: Boolean(options.watch),\n rootDir: options.configRoot,\n argv: options.configTargets.flatMap(t => [\n '--config',\n 'url' in t ? t.url : t.path,\n ]),\n });\n\n return new Promise<LoadConfigResult>((resolve, reject) => {\n async function loadConfigReaderLoop() {\n let loaded = false;\n\n try {\n const abortController = new AbortController();\n options.watch?.stopSignal?.then(() => abortController.abort());\n\n for await (const { configs } of source.readConfigData({\n signal: abortController.signal,\n })) {\n if (loaded) {\n options.watch?.onChange(configs);\n } else {\n resolve({ appConfigs: configs });\n loaded = true;\n\n if (options.watch) {\n options.watch.stopSignal?.then(() => abortController.abort());\n } else {\n abortController.abort();\n }\n }\n }\n } catch (error) {\n if (loaded) {\n console.error(`Failed to reload configuration, ${error}`);\n } else {\n reject(error);\n }\n }\n }\n loadConfigReaderLoop();\n });\n}\n"],"names":["ConfigSources"],"mappings":";;;;;;;;;;;AAqGA,eAAsB,WACpB,OAC2B,EAAA;AAC3B,EAAM,MAAA,MAAA,GAASA,4BAAc,OAAQ,CAAA;AAAA,IACnC,kBAAkB,OAAQ,CAAA,mBAAA;AAAA,IAC1B,MAAA,EAAQ,QAAQ,MAAU,IAAA;AAAA,MACxB,cAAgB,EAAA,EAAE,OAAS,EAAA,OAAA,CAAQ,OAAO,qBAAsB;AAAA,KAClE;AAAA,IACA,KAAA,EAAO,OAAQ,CAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,IAC5B,SAAS,OAAQ,CAAA,UAAA;AAAA,IACjB,IAAM,EAAA,OAAA,CAAQ,aAAc,CAAA,OAAA,CAAQ,CAAK,CAAA,KAAA;AAAA,MACvC,UAAA;AAAA,MACA,KAAS,IAAA,CAAA,GAAI,CAAE,CAAA,GAAA,GAAM,CAAE,CAAA;AAAA,KACxB;AAAA,GACF,CAAA;AAED,EAAA,OAAO,IAAI,OAAA,CAA0B,CAAC,OAAA,EAAS,MAAW,KAAA;AACxD,IAAA,eAAe,oBAAuB,GAAA;AACpC,MAAA,IAAI,MAAS,GAAA,KAAA;AAEb,MAAI,IAAA;AACF,QAAM,MAAA,eAAA,GAAkB,IAAI,eAAgB,EAAA;AAC5C,QAAA,OAAA,CAAQ,OAAO,UAAY,EAAA,IAAA,CAAK,MAAM,eAAA,CAAgB,OAAO,CAAA;AAE7D,QAAA,WAAA,MAAiB,EAAE,OAAA,EAAa,IAAA,MAAA,CAAO,cAAe,CAAA;AAAA,UACpD,QAAQ,eAAgB,CAAA;AAAA,SACzB,CAAG,EAAA;AACF,UAAA,IAAI,MAAQ,EAAA;AACV,YAAQ,OAAA,CAAA,KAAA,EAAO,SAAS,OAAO,CAAA;AAAA,WAC1B,MAAA;AACL,YAAQ,OAAA,CAAA,EAAE,UAAY,EAAA,OAAA,EAAS,CAAA;AAC/B,YAAS,MAAA,GAAA,IAAA;AAET,YAAA,IAAI,QAAQ,KAAO,EAAA;AACjB,cAAA,OAAA,CAAQ,MAAM,UAAY,EAAA,IAAA,CAAK,MAAM,eAAA,CAAgB,OAAO,CAAA;AAAA,aACvD,MAAA;AACL,cAAA,eAAA,CAAgB,KAAM,EAAA;AAAA;AACxB;AACF;AACF,eACO,KAAO,EAAA;AACd,QAAA,IAAI,MAAQ,EAAA;AACV,UAAQ,OAAA,CAAA,KAAA,CAAM,CAAmC,gCAAA,EAAA,KAAK,CAAE,CAAA,CAAA;AAAA,SACnD,MAAA;AACL,UAAA,MAAA,CAAO,KAAK,CAAA;AAAA;AACd;AACF;AAEF,IAAqB,oBAAA,EAAA;AAAA,GACtB,CAAA;AACH;;;;"}
1
+ {"version":3,"file":"loader.cjs.js","sources":["../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 { ConfigSources } from './sources';\n\n/**\n * @public\n * @deprecated Use {@link ConfigSources.default} instead.\n */\nexport type ConfigTarget = { path: string } | { url: string };\n\n/**\n * @public\n * @deprecated Use {@link ConfigSources.default} instead.\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\n/**\n * @public\n * @deprecated Use {@link ConfigSources.default} instead.\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 * @deprecated Use {@link ConfigSources.default} instead.\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 * @deprecated Use {@link ConfigSources.default} instead.\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 * @deprecated Use {@link ConfigSources.default} instead.\n */\nexport async function loadConfig(\n options: LoadConfigOptions,\n): Promise<LoadConfigResult> {\n const source = ConfigSources.default({\n substitutionFunc: options.experimentalEnvFunc,\n remote: options.remote && {\n reloadInterval: { seconds: options.remote.reloadIntervalSeconds },\n },\n watch: Boolean(options.watch),\n rootDir: options.configRoot,\n argv: options.configTargets.flatMap(t => [\n '--config',\n 'url' in t ? t.url : t.path,\n ]),\n });\n\n return new Promise<LoadConfigResult>((resolve, reject) => {\n async function loadConfigReaderLoop() {\n let loaded = false;\n\n try {\n const abortController = new AbortController();\n options.watch?.stopSignal?.then(() => abortController.abort());\n\n for await (const { configs } of source.readConfigData({\n signal: abortController.signal,\n })) {\n if (loaded) {\n options.watch?.onChange(configs);\n } else {\n resolve({ appConfigs: configs });\n loaded = true;\n\n if (options.watch) {\n options.watch.stopSignal?.then(() => abortController.abort());\n } else {\n abortController.abort();\n }\n }\n }\n } catch (error) {\n if (loaded) {\n console.error(`Failed to reload configuration, ${error}`);\n } else {\n reject(error);\n }\n }\n }\n loadConfigReaderLoop();\n });\n}\n"],"names":["ConfigSources"],"mappings":";;;;;;;;;;;AAqGA,eAAsB,WACpB,OAAA,EAC2B;AAC3B,EAAA,MAAM,MAAA,GAASA,4BAAc,OAAA,CAAQ;AAAA,IACnC,kBAAkB,OAAA,CAAQ,mBAAA;AAAA,IAC1B,MAAA,EAAQ,QAAQ,MAAA,IAAU;AAAA,MACxB,cAAA,EAAgB,EAAE,OAAA,EAAS,OAAA,CAAQ,OAAO,qBAAA;AAAsB,KAClE;AAAA,IACA,KAAA,EAAO,OAAA,CAAQ,OAAA,CAAQ,KAAK,CAAA;AAAA,IAC5B,SAAS,OAAA,CAAQ,UAAA;AAAA,IACjB,IAAA,EAAM,OAAA,CAAQ,aAAA,CAAc,OAAA,CAAQ,CAAA,CAAA,KAAK;AAAA,MACvC,UAAA;AAAA,MACA,KAAA,IAAS,CAAA,GAAI,CAAA,CAAE,GAAA,GAAM,CAAA,CAAE;AAAA,KACxB;AAAA,GACF,CAAA;AAED,EAAA,OAAO,IAAI,OAAA,CAA0B,CAAC,OAAA,EAAS,MAAA,KAAW;AACxD,IAAA,eAAe,oBAAA,GAAuB;AACpC,MAAA,IAAI,MAAA,GAAS,KAAA;AAEb,MAAA,IAAI;AACF,QAAA,MAAM,eAAA,GAAkB,IAAI,eAAA,EAAgB;AAC5C,QAAA,OAAA,CAAQ,OAAO,UAAA,EAAY,IAAA,CAAK,MAAM,eAAA,CAAgB,OAAO,CAAA;AAE7D,QAAA,WAAA,MAAiB,EAAE,OAAA,EAAQ,IAAK,MAAA,CAAO,cAAA,CAAe;AAAA,UACpD,QAAQ,eAAA,CAAgB;AAAA,SACzB,CAAA,EAAG;AACF,UAAA,IAAI,MAAA,EAAQ;AACV,YAAA,OAAA,CAAQ,KAAA,EAAO,SAAS,OAAO,CAAA;AAAA,UACjC,CAAA,MAAO;AACL,YAAA,OAAA,CAAQ,EAAE,UAAA,EAAY,OAAA,EAAS,CAAA;AAC/B,YAAA,MAAA,GAAS,IAAA;AAET,YAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,cAAA,OAAA,CAAQ,MAAM,UAAA,EAAY,IAAA,CAAK,MAAM,eAAA,CAAgB,OAAO,CAAA;AAAA,YAC9D,CAAA,MAAO;AACL,cAAA,eAAA,CAAgB,KAAA,EAAM;AAAA,YACxB;AAAA,UACF;AAAA,QACF;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,gCAAA,EAAmC,KAAK,CAAA,CAAE,CAAA;AAAA,QAC1D,CAAA,MAAO;AACL,UAAA,MAAA,CAAO,KAAK,CAAA;AAAA,QACd;AAAA,MACF;AAAA,IACF;AACA,IAAA,oBAAA,EAAqB;AAAA,EACvB,CAAC,CAAA;AACH;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"collect.cjs.js","sources":["../../src/schema/collect.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 fs from 'fs-extra';\nimport { EOL } from 'os';\nimport {\n resolve as resolvePath,\n relative as relativePath,\n dirname,\n sep,\n} from 'path';\nimport { ConfigSchemaPackageEntry } from './types';\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<{ packageName: string; path: 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 path: relativePath(\n currentDir,\n resolvePath(dirname(pkgPath), pkg.configSchema),\n ),\n packageName: pkg.name,\n });\n } else {\n const path = resolvePath(dirname(pkgPath), pkg.configSchema);\n const value = await fs.readJson(path);\n schemas.push({\n packageName: pkg.name,\n value,\n path: relativePath(currentDir, path),\n });\n }\n } else {\n schemas.push({\n packageName: pkg.name,\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 = await compileTsSchemas(tsSchemaPaths);\n const allSchemas = schemas.concat(tsSchemas);\n\n const hasBackendDefaults = allSchemas.some(\n ({ packageName }) => packageName === '@backstage/backend-defaults',\n );\n\n if (hasBackendDefaults) {\n // We filter out backend-common schemas here to avoid issues with\n // schema merging over different versions of the same schema.\n // led to issues such as https://github.com/backstage/backstage/issues/28170\n return allSchemas.filter(\n ({ packageName }) => packageName !== '@backstage/backend-common',\n );\n }\n\n return allSchemas;\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.\nasync function compileTsSchemas(\n entries: { path: string; packageName: string }[],\n) {\n if (entries.length === 0) {\n return [];\n }\n\n // Lazy loaded, because this brings up all of TypeScript and we don't\n // want that eagerly loaded in tests\n const { getProgramFromFiles, buildGenerator } =\n require('typescript-json-schema') as typeof import('typescript-json-schema');\n\n const program = getProgramFromFiles(\n entries.map(({ path }) => path),\n {\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\n const tsSchemas = entries.map(({ path, packageName }) => {\n let value;\n try {\n const generator = buildGenerator(\n program,\n // This enables the use of these tags in TSDoc comments\n {\n required: true,\n validationKeywords: ['visibility', 'deepVisibility', 'deprecated'],\n },\n [path.split(sep).join('/')], // Unix paths are expected for all OSes here\n );\n\n // All schemas should export a `Config` symbol\n value = generator?.getSchemaForSymbol('Config') as JsonObject | null;\n\n // This makes sure that no additional symbols are defined in the schema. We don't allow\n // this because they share a global namespace and will be merged together, leading to\n // unpredictable behavior.\n const userSymbols = new Set(generator?.getUserSymbols());\n userSymbols.delete('Config');\n if (userSymbols.size !== 0) {\n const names = Array.from(userSymbols).join(\"', '\");\n throw new Error(\n `Invalid configuration schema in ${path}, additional symbol definitions are not allowed, found '${names}'`,\n );\n }\n\n // This makes sure that no unsupported types are used in the schema, for example `Record<,>`.\n // The generator will extract these as a schema reference, which will in turn be broken for our usage.\n const reffedDefs = Object.keys(generator?.ReffedDefinitions ?? {});\n if (reffedDefs.length !== 0) {\n const lines = reffedDefs.join(`${EOL} `);\n throw new Error(\n `Invalid configuration schema in ${path}, the following definitions are not supported:${EOL}${EOL} ${lines}`,\n );\n }\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, packageName };\n });\n\n return tsSchemas;\n}\n"],"names":["fs","relativePath","resolvePath","dirname","path","sep","EOL","assertError"],"mappings":"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"}
1
+ {"version":3,"file":"collect.cjs.js","sources":["../../src/schema/collect.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 fs from 'fs-extra';\nimport { EOL } from 'os';\nimport {\n resolve as resolvePath,\n relative as relativePath,\n dirname,\n sep,\n} from 'path';\nimport { ConfigSchemaPackageEntry } from './types';\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<{ packageName: string; path: 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 path: relativePath(\n currentDir,\n resolvePath(dirname(pkgPath), pkg.configSchema),\n ),\n packageName: pkg.name,\n });\n } else {\n const path = resolvePath(dirname(pkgPath), pkg.configSchema);\n const value = await fs.readJson(path);\n schemas.push({\n packageName: pkg.name,\n value,\n path: relativePath(currentDir, path),\n });\n }\n } else {\n schemas.push({\n packageName: pkg.name,\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 = await compileTsSchemas(tsSchemaPaths);\n const allSchemas = schemas.concat(tsSchemas);\n\n const hasBackendDefaults = allSchemas.some(\n ({ packageName }) => packageName === '@backstage/backend-defaults',\n );\n\n if (hasBackendDefaults) {\n // We filter out backend-common schemas here to avoid issues with\n // schema merging over different versions of the same schema.\n // led to issues such as https://github.com/backstage/backstage/issues/28170\n return allSchemas.filter(\n ({ packageName }) => packageName !== '@backstage/backend-common',\n );\n }\n\n return allSchemas;\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.\nasync function compileTsSchemas(\n entries: { path: string; packageName: string }[],\n) {\n if (entries.length === 0) {\n return [];\n }\n\n // Lazy loaded, because this brings up all of TypeScript and we don't\n // want that eagerly loaded in tests\n const { getProgramFromFiles, buildGenerator } =\n require('typescript-json-schema') as typeof import('typescript-json-schema');\n\n const program = getProgramFromFiles(\n entries.map(({ path }) => path),\n {\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\n const tsSchemas = entries.map(({ path, packageName }) => {\n let value;\n try {\n const generator = buildGenerator(\n program,\n // This enables the use of these tags in TSDoc comments\n {\n required: true,\n validationKeywords: ['visibility', 'deepVisibility', 'deprecated'],\n },\n [path.split(sep).join('/')], // Unix paths are expected for all OSes here\n );\n\n // All schemas should export a `Config` symbol\n value = generator?.getSchemaForSymbol('Config') as JsonObject | null;\n\n // This makes sure that no additional symbols are defined in the schema. We don't allow\n // this because they share a global namespace and will be merged together, leading to\n // unpredictable behavior.\n const userSymbols = new Set(generator?.getUserSymbols());\n userSymbols.delete('Config');\n if (userSymbols.size !== 0) {\n const names = Array.from(userSymbols).join(\"', '\");\n throw new Error(\n `Invalid configuration schema in ${path}, additional symbol definitions are not allowed, found '${names}'`,\n );\n }\n\n // This makes sure that no unsupported types are used in the schema, for example `Record<,>`.\n // The generator will extract these as a schema reference, which will in turn be broken for our usage.\n const reffedDefs = Object.keys(generator?.ReffedDefinitions ?? {});\n if (reffedDefs.length !== 0) {\n const lines = reffedDefs.join(`${EOL} `);\n throw new Error(\n `Invalid configuration schema in ${path}, the following definitions are not supported:${EOL}${EOL} ${lines}`,\n );\n }\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, packageName };\n });\n\n return tsSchemas;\n}\n"],"names":["fs","relativePath","resolvePath","dirname","path","sep","EOL","assertError"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"compile.cjs.js","sources":["../../src/schema/compile.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 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';\nimport { SchemaObject } from 'json-schema-traverse';\nimport { normalizeAjvPath } from './utils';\n\n// Used to keep track of the internal deepVisibility inherited through the schema.\nconst inheritedVisibility = Symbol('inherited-visibility');\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 options?: {\n noUndeclaredProperties?: boolean;\n },\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 const deepVisibilityByDataPath = new Map<string, ConfigVisibility>();\n const deprecationByDataPath = new Map<string, string>();\n\n const ajv = new Ajv({\n allErrors: true,\n allowUnionTypes: true,\n coerceTypes: true,\n schemas: {\n 'https://backstage.io/schema/config-v1': true,\n },\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?.instancePath === undefined) {\n return false;\n }\n if (visibility && visibility !== 'backend') {\n const normalizedPath = normalizeAjvPath(context.instancePath);\n visibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n })\n .addKeyword({\n keyword: 'deepVisibility',\n metaSchema: {\n type: 'string',\n /**\n * Disallow 'backend' deepVisibility to prevent cases of permission escaping.\n *\n * Something like:\n * - deepVisibility secret -> backend -> frontend.\n * - deepVisibility secret -> backend -> visibility frontend.\n */\n enum: ['frontend', 'secret'],\n },\n compile(visibility: 'frontend' | 'secret') {\n return (_data, context) => {\n if (context?.instancePath === undefined) {\n return false;\n }\n if (visibility) {\n const normalizedPath = normalizeAjvPath(context.instancePath);\n deepVisibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n })\n .removeKeyword('deprecated') // remove `deprecated` keyword so that we can implement our own compiler\n .addKeyword({\n keyword: 'deprecated',\n metaSchema: { type: 'string' },\n compile(deprecationDescription: string) {\n return (_data, context) => {\n if (context?.instancePath === undefined) {\n return false;\n }\n const normalizedPath = normalizeAjvPath(context.instancePath);\n // create mapping of deprecation description and data path of property\n deprecationByDataPath.set(normalizedPath, deprecationDescription);\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\n traverse(\n merged,\n (\n schema: SchemaObject & { [inheritedVisibility]?: ConfigVisibility },\n jsonPtr: string,\n _1: any,\n _2: any,\n _3?: any,\n parentSchema?: SchemaObject & {\n [inheritedVisibility]?: ConfigVisibility;\n },\n ) => {\n // Inherit parent deepVisibility if we don't define one ourselves.\n // This is used to detect situations where conflicting deep visibilities are set.\n schema[inheritedVisibility] ??=\n schema?.deepVisibility ?? parentSchema?.[inheritedVisibility];\n\n if (schema[inheritedVisibility]) {\n // Validate that we're not trying to set a conflicting visibility. This can be done\n // either by setting a conflicting visibility directly or deep visibility\n const values = [\n schema.visibility,\n schema[inheritedVisibility],\n parentSchema?.[inheritedVisibility],\n ];\n const hasFrontend = values.some(e => e === 'frontend');\n const hasSecret = values.some(e => e === 'secret');\n if (hasFrontend && hasSecret) {\n throw new Error(\n `Config schema visibility is both 'frontend' and 'secret' for ${jsonPtr}`,\n );\n }\n }\n\n if (options?.noUndeclaredProperties) {\n /**\n * The `additionalProperties` key can only be applied to `type: object` in the JSON\n * schema.\n */\n if (schema?.type === 'object') {\n schema.additionalProperties ||= false;\n }\n }\n },\n );\n\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(normalizeAjvPath(path), schema.visibility);\n }\n if (schema.deepVisibility) {\n visibilityBySchemaPath.set(normalizeAjvPath(path), schema.deepVisibility);\n }\n });\n\n return configs => {\n const config = ConfigReader.fromConfigs(configs).getOptional();\n\n visibilityByDataPath.clear();\n deepVisibilityByDataPath.clear();\n\n const valid = validate(config);\n\n if (!valid) {\n return {\n errors: validate.errors ?? [],\n visibilityByDataPath: new Map(visibilityByDataPath),\n deepVisibilityByDataPath: new Map(deepVisibilityByDataPath),\n visibilityBySchemaPath,\n deprecationByDataPath,\n };\n }\n\n return {\n visibilityByDataPath: new Map(visibilityByDataPath),\n deepVisibilityByDataPath: new Map(deepVisibilityByDataPath),\n visibilityBySchemaPath,\n deprecationByDataPath,\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"],"names":["Ajv","CONFIG_VISIBILITIES","normalizeAjvPath","traverse","config","ConfigReader","mergeAllOf"],"mappings":";;;;;;;;;;;;;;;AA+BA,MAAM,mBAAA,GAAsB,OAAO,sBAAsB,CAAA;AASzC,SAAA,oBAAA,CACd,SACA,OAGgB,EAAA;AAIhB,EAAM,MAAA,oBAAA,uBAA2B,GAA8B,EAAA;AAC/D,EAAM,MAAA,wBAAA,uBAA+B,GAA8B,EAAA;AACnE,EAAM,MAAA,qBAAA,uBAA4B,GAAoB,EAAA;AAEtD,EAAM,MAAA,GAAA,GAAM,IAAIA,oBAAI,CAAA;AAAA,IAClB,SAAW,EAAA,IAAA;AAAA,IACX,eAAiB,EAAA,IAAA;AAAA,IACjB,WAAa,EAAA,IAAA;AAAA,IACb,OAAS,EAAA;AAAA,MACP,uCAAyC,EAAA;AAAA;AAC3C,GACD,EACE,UAAW,CAAA;AAAA,IACV,OAAS,EAAA,YAAA;AAAA,IACT,UAAY,EAAA;AAAA,MACV,IAAM,EAAA,QAAA;AAAA,MACN,IAAM,EAAAC;AAAA,KACR;AAAA,IACA,QAAQ,UAA8B,EAAA;AACpC,MAAO,OAAA,CAAC,OAAO,OAAY,KAAA;AACzB,QAAI,IAAA,OAAA,EAAS,iBAAiB,KAAW,CAAA,EAAA;AACvC,UAAO,OAAA,KAAA;AAAA;AAET,QAAI,IAAA,UAAA,IAAc,eAAe,SAAW,EAAA;AAC1C,UAAM,MAAA,cAAA,GAAiBC,sBAAiB,CAAA,OAAA,CAAQ,YAAY,CAAA;AAC5D,UAAqB,oBAAA,CAAA,GAAA,CAAI,gBAAgB,UAAU,CAAA;AAAA;AAErD,QAAO,OAAA,IAAA;AAAA,OACT;AAAA;AACF,GACD,EACA,UAAW,CAAA;AAAA,IACV,OAAS,EAAA,gBAAA;AAAA,IACT,UAAY,EAAA;AAAA,MACV,IAAM,EAAA,QAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQN,IAAA,EAAM,CAAC,UAAA,EAAY,QAAQ;AAAA,KAC7B;AAAA,IACA,QAAQ,UAAmC,EAAA;AACzC,MAAO,OAAA,CAAC,OAAO,OAAY,KAAA;AACzB,QAAI,IAAA,OAAA,EAAS,iBAAiB,KAAW,CAAA,EAAA;AACvC,UAAO,OAAA,KAAA;AAAA;AAET,QAAA,IAAI,UAAY,EAAA;AACd,UAAM,MAAA,cAAA,GAAiBA,sBAAiB,CAAA,OAAA,CAAQ,YAAY,CAAA;AAC5D,UAAyB,wBAAA,CAAA,GAAA,CAAI,gBAAgB,UAAU,CAAA;AAAA;AAEzD,QAAO,OAAA,IAAA;AAAA,OACT;AAAA;AACF,GACD,CAAA,CACA,aAAc,CAAA,YAAY,EAC1B,UAAW,CAAA;AAAA,IACV,OAAS,EAAA,YAAA;AAAA,IACT,UAAA,EAAY,EAAE,IAAA,EAAM,QAAS,EAAA;AAAA,IAC7B,QAAQ,sBAAgC,EAAA;AACtC,MAAO,OAAA,CAAC,OAAO,OAAY,KAAA;AACzB,QAAI,IAAA,OAAA,EAAS,iBAAiB,KAAW,CAAA,EAAA;AACvC,UAAO,OAAA,KAAA;AAAA;AAET,QAAM,MAAA,cAAA,GAAiBA,sBAAiB,CAAA,OAAA,CAAQ,YAAY,CAAA;AAE5D,QAAsB,qBAAA,CAAA,GAAA,CAAI,gBAAgB,sBAAsB,CAAA;AAChE,QAAO,OAAA,IAAA;AAAA,OACT;AAAA;AACF,GACD,CAAA;AAEH,EAAA,KAAA,MAAW,UAAU,OAAS,EAAA;AAC5B,IAAI,IAAA;AACF,MAAI,GAAA,CAAA,OAAA,CAAQ,OAAO,KAAK,CAAA;AAAA,aACjB,KAAO,EAAA;AACd,MAAA,MAAM,IAAI,KAAM,CAAA,CAAA,UAAA,EAAa,OAAO,IAAI,CAAA,aAAA,EAAgB,KAAK,CAAE,CAAA,CAAA;AAAA;AACjE;AAGF,EAAA,MAAM,SAAS,kBAAmB,CAAA,OAAA,CAAQ,IAAI,CAAK,CAAA,KAAA,CAAA,CAAE,KAAK,CAAC,CAAA;AAE3D,EAAAC,yBAAA;AAAA,IACE,MAAA;AAAA,IACA,CACE,MACA,EAAA,OAAA,EACA,EACA,EAAA,EAAA,EACA,IACA,YAGG,KAAA;AAGH,MAAA,MAAA,CAAO,mBAAmB,CAAA,KACxB,MAAQ,EAAA,cAAA,IAAkB,eAAe,mBAAmB,CAAA;AAE9D,MAAI,IAAA,MAAA,CAAO,mBAAmB,CAAG,EAAA;AAG/B,QAAA,MAAM,MAAS,GAAA;AAAA,UACb,MAAO,CAAA,UAAA;AAAA,UACP,OAAO,mBAAmB,CAAA;AAAA,UAC1B,eAAe,mBAAmB;AAAA,SACpC;AACA,QAAA,MAAM,WAAc,GAAA,MAAA,CAAO,IAAK,CAAA,CAAA,CAAA,KAAK,MAAM,UAAU,CAAA;AACrD,QAAA,MAAM,SAAY,GAAA,MAAA,CAAO,IAAK,CAAA,CAAA,CAAA,KAAK,MAAM,QAAQ,CAAA;AACjD,QAAA,IAAI,eAAe,SAAW,EAAA;AAC5B,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,gEAAgE,OAAO,CAAA;AAAA,WACzE;AAAA;AACF;AAGF,MAAA,IAAI,SAAS,sBAAwB,EAAA;AAKnC,QAAI,IAAA,MAAA,EAAQ,SAAS,QAAU,EAAA;AAC7B,UAAA,MAAA,CAAO,oBAAyB,KAAA,KAAA;AAAA;AAClC;AACF;AACF,GACF;AAEA,EAAM,MAAA,QAAA,GAAW,GAAI,CAAA,OAAA,CAAQ,MAAM,CAAA;AAEnC,EAAM,MAAA,sBAAA,uBAA6B,GAA8B,EAAA;AACjE,EAASA,yBAAA,CAAA,MAAA,EAAQ,CAAC,MAAA,EAAQ,IAAS,KAAA;AACjC,IAAA,IAAI,MAAO,CAAA,UAAA,IAAc,MAAO,CAAA,UAAA,KAAe,SAAW,EAAA;AACxD,MAAA,sBAAA,CAAuB,GAAI,CAAAD,sBAAA,CAAiB,IAAI,CAAA,EAAG,OAAO,UAAU,CAAA;AAAA;AAEtE,IAAA,IAAI,OAAO,cAAgB,EAAA;AACzB,MAAA,sBAAA,CAAuB,GAAI,CAAAA,sBAAA,CAAiB,IAAI,CAAA,EAAG,OAAO,cAAc,CAAA;AAAA;AAC1E,GACD,CAAA;AAED,EAAA,OAAO,CAAW,OAAA,KAAA;AAChB,IAAA,MAAME,QAAS,GAAAC,mBAAA,CAAa,WAAY,CAAA,OAAO,EAAE,WAAY,EAAA;AAE7D,IAAA,oBAAA,CAAqB,KAAM,EAAA;AAC3B,IAAA,wBAAA,CAAyB,KAAM,EAAA;AAE/B,IAAM,MAAA,KAAA,GAAQ,SAASD,QAAM,CAAA;AAE7B,IAAA,IAAI,CAAC,KAAO,EAAA;AACV,MAAO,OAAA;AAAA,QACL,MAAA,EAAQ,QAAS,CAAA,MAAA,IAAU,EAAC;AAAA,QAC5B,oBAAA,EAAsB,IAAI,GAAA,CAAI,oBAAoB,CAAA;AAAA,QAClD,wBAAA,EAA0B,IAAI,GAAA,CAAI,wBAAwB,CAAA;AAAA,QAC1D,sBAAA;AAAA,QACA;AAAA,OACF;AAAA;AAGF,IAAO,OAAA;AAAA,MACL,oBAAA,EAAsB,IAAI,GAAA,CAAI,oBAAoB,CAAA;AAAA,MAClD,wBAAA,EAA0B,IAAI,GAAA,CAAI,wBAAwB,CAAA;AAAA,MAC1D,sBAAA;AAAA,MACA;AAAA,KACF;AAAA,GACF;AACF;AAQO,SAAS,mBAAmB,OAAmC,EAAA;AACpE,EAAA,MAAM,MAAS,GAAAE,2BAAA;AAAA,IACb,EAAE,OAAO,OAAQ,EAAA;AAAA,IACjB;AAAA;AAAA;AAAA;AAAA,MAIE,0BAA4B,EAAA,IAAA;AAAA,MAC5B,SAAW,EAAA;AAAA;AAAA;AAAA,QAGT,UAAA,CAAW,QAAkB,IAAgB,EAAA;AAC3C,UAAA,MAAM,WAAc,GAAA,MAAA,CAAO,IAAK,CAAA,CAAA,CAAA,KAAK,MAAM,UAAU,CAAA;AACrD,UAAA,MAAM,SAAY,GAAA,MAAA,CAAO,IAAK,CAAA,CAAA,CAAA,KAAK,MAAM,QAAQ,CAAA;AACjD,UAAA,IAAI,eAAe,SAAW,EAAA;AAC5B,YAAA,MAAM,IAAI,KAAA;AAAA,cACR,gEAAgE,IAAK,CAAA,IAAA;AAAA,gBACnE;AAAA,eACD,CAAA;AAAA,aACH;AAAA,qBACS,WAAa,EAAA;AACtB,YAAO,OAAA,UAAA;AAAA,qBACE,SAAW,EAAA;AACpB,YAAO,OAAA,QAAA;AAAA;AAGT,UAAO,OAAA,SAAA;AAAA;AACT;AACF;AACF,GACF;AACA,EAAO,OAAA,MAAA;AACT;;;;;"}
1
+ {"version":3,"file":"compile.cjs.js","sources":["../../src/schema/compile.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 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';\nimport { SchemaObject } from 'json-schema-traverse';\nimport { normalizeAjvPath } from './utils';\n\n// Used to keep track of the internal deepVisibility inherited through the schema.\nconst inheritedVisibility = Symbol('inherited-visibility');\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 options?: {\n noUndeclaredProperties?: boolean;\n },\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 const deepVisibilityByDataPath = new Map<string, ConfigVisibility>();\n const deprecationByDataPath = new Map<string, string>();\n\n const ajv = new Ajv({\n allErrors: true,\n allowUnionTypes: true,\n coerceTypes: true,\n schemas: {\n 'https://backstage.io/schema/config-v1': true,\n },\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?.instancePath === undefined) {\n return false;\n }\n if (visibility && visibility !== 'backend') {\n const normalizedPath = normalizeAjvPath(context.instancePath);\n visibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n })\n .addKeyword({\n keyword: 'deepVisibility',\n metaSchema: {\n type: 'string',\n /**\n * Disallow 'backend' deepVisibility to prevent cases of permission escaping.\n *\n * Something like:\n * - deepVisibility secret -> backend -> frontend.\n * - deepVisibility secret -> backend -> visibility frontend.\n */\n enum: ['frontend', 'secret'],\n },\n compile(visibility: 'frontend' | 'secret') {\n return (_data, context) => {\n if (context?.instancePath === undefined) {\n return false;\n }\n if (visibility) {\n const normalizedPath = normalizeAjvPath(context.instancePath);\n deepVisibilityByDataPath.set(normalizedPath, visibility);\n }\n return true;\n };\n },\n })\n .removeKeyword('deprecated') // remove `deprecated` keyword so that we can implement our own compiler\n .addKeyword({\n keyword: 'deprecated',\n metaSchema: { type: 'string' },\n compile(deprecationDescription: string) {\n return (_data, context) => {\n if (context?.instancePath === undefined) {\n return false;\n }\n const normalizedPath = normalizeAjvPath(context.instancePath);\n // create mapping of deprecation description and data path of property\n deprecationByDataPath.set(normalizedPath, deprecationDescription);\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\n traverse(\n merged,\n (\n schema: SchemaObject & { [inheritedVisibility]?: ConfigVisibility },\n jsonPtr: string,\n _1: any,\n _2: any,\n _3?: any,\n parentSchema?: SchemaObject & {\n [inheritedVisibility]?: ConfigVisibility;\n },\n ) => {\n // Inherit parent deepVisibility if we don't define one ourselves.\n // This is used to detect situations where conflicting deep visibilities are set.\n schema[inheritedVisibility] ??=\n schema?.deepVisibility ?? parentSchema?.[inheritedVisibility];\n\n if (schema[inheritedVisibility]) {\n // Validate that we're not trying to set a conflicting visibility. This can be done\n // either by setting a conflicting visibility directly or deep visibility\n const values = [\n schema.visibility,\n schema[inheritedVisibility],\n parentSchema?.[inheritedVisibility],\n ];\n const hasFrontend = values.some(e => e === 'frontend');\n const hasSecret = values.some(e => e === 'secret');\n if (hasFrontend && hasSecret) {\n throw new Error(\n `Config schema visibility is both 'frontend' and 'secret' for ${jsonPtr}`,\n );\n }\n }\n\n if (options?.noUndeclaredProperties) {\n /**\n * The `additionalProperties` key can only be applied to `type: object` in the JSON\n * schema.\n */\n if (schema?.type === 'object') {\n schema.additionalProperties ||= false;\n }\n }\n },\n );\n\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(normalizeAjvPath(path), schema.visibility);\n }\n if (schema.deepVisibility) {\n visibilityBySchemaPath.set(normalizeAjvPath(path), schema.deepVisibility);\n }\n });\n\n return configs => {\n const config = ConfigReader.fromConfigs(configs).getOptional();\n\n visibilityByDataPath.clear();\n deepVisibilityByDataPath.clear();\n\n const valid = validate(config);\n\n if (!valid) {\n return {\n errors: validate.errors ?? [],\n visibilityByDataPath: new Map(visibilityByDataPath),\n deepVisibilityByDataPath: new Map(deepVisibilityByDataPath),\n visibilityBySchemaPath,\n deprecationByDataPath,\n };\n }\n\n return {\n visibilityByDataPath: new Map(visibilityByDataPath),\n deepVisibilityByDataPath: new Map(deepVisibilityByDataPath),\n visibilityBySchemaPath,\n deprecationByDataPath,\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"],"names":["Ajv","CONFIG_VISIBILITIES","normalizeAjvPath","traverse","config","ConfigReader","mergeAllOf"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"filtering.cjs.js","sources":["../../src/schema/filtering.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 {\n ConfigVisibility,\n DEFAULT_CONFIG_VISIBILITY,\n TransformFunc,\n ValidationError,\n} from './types';\nimport { normalizeAjvPath } from './utils';\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 deepVisibilityByDataPath: Map<string, ConfigVisibility>,\n deprecationByDataPath: Map<string, string>,\n transformFunc?: TransformFunc<number | string | boolean>,\n withFilteredKeys?: boolean,\n withDeprecatedKeys?: boolean,\n): {\n data: JsonObject;\n filteredKeys?: string[];\n deprecatedKeys?: { key: string; description: string }[];\n} {\n const filteredKeys = new Array<string>();\n const deprecatedKeys = new Array<{ key: string; description: 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 inheritedVisibility: ConfigVisibility,\n ): JsonValue | undefined {\n const visibility =\n visibilityByDataPath.get(visibilityPath) ?? inheritedVisibility;\n const isVisible = includeVisibilities.includes(visibility);\n\n // If a deep visibility is set for our current path, then we that as our\n // default visibility for all children until we encounter a different deep visibility\n const newInheritedVisibility =\n deepVisibilityByDataPath.get(visibilityPath) ?? inheritedVisibility;\n\n // deprecated keys are added regardless of visibility indicator\n const deprecation = deprecationByDataPath.get(visibilityPath);\n if (deprecation) {\n deprecatedKeys.push({ key: filterPath, description: deprecation });\n }\n\n if (typeof jsonVal !== 'object') {\n if (isVisible) {\n if (transformFunc) {\n return transformFunc(jsonVal, { visibility, path: filterPath });\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(\n value,\n path,\n `${filterPath}[${index}]`,\n newInheritedVisibility,\n );\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 newInheritedVisibility,\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 deprecatedKeys: withDeprecatedKeys ? deprecatedKeys : undefined,\n data:\n (transform(data, '', '', DEFAULT_CONFIG_VISIBILITY) 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 = normalizeAjvPath(error.schemaPath).slice(\n 1,\n -'/required'.length,\n );\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(normalizeAjvPath(error.instancePath)) ??\n DEFAULT_CONFIG_VISIBILITY;\n return vis && includeVisibilities.includes(vis);\n });\n}\n"],"names":["DEFAULT_CONFIG_VISIBILITY","normalizeAjvPath"],"mappings":"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"}
1
+ {"version":3,"file":"filtering.cjs.js","sources":["../../src/schema/filtering.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 {\n ConfigVisibility,\n DEFAULT_CONFIG_VISIBILITY,\n TransformFunc,\n ValidationError,\n} from './types';\nimport { normalizeAjvPath } from './utils';\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 deepVisibilityByDataPath: Map<string, ConfigVisibility>,\n deprecationByDataPath: Map<string, string>,\n transformFunc?: TransformFunc<number | string | boolean>,\n withFilteredKeys?: boolean,\n withDeprecatedKeys?: boolean,\n): {\n data: JsonObject;\n filteredKeys?: string[];\n deprecatedKeys?: { key: string; description: string }[];\n} {\n const filteredKeys = new Array<string>();\n const deprecatedKeys = new Array<{ key: string; description: 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 inheritedVisibility: ConfigVisibility,\n ): JsonValue | undefined {\n const visibility =\n visibilityByDataPath.get(visibilityPath) ?? inheritedVisibility;\n const isVisible = includeVisibilities.includes(visibility);\n\n // If a deep visibility is set for our current path, then we that as our\n // default visibility for all children until we encounter a different deep visibility\n const newInheritedVisibility =\n deepVisibilityByDataPath.get(visibilityPath) ?? inheritedVisibility;\n\n // deprecated keys are added regardless of visibility indicator\n const deprecation = deprecationByDataPath.get(visibilityPath);\n if (deprecation) {\n deprecatedKeys.push({ key: filterPath, description: deprecation });\n }\n\n if (typeof jsonVal !== 'object') {\n if (isVisible) {\n if (transformFunc) {\n return transformFunc(jsonVal, { visibility, path: filterPath });\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(\n value,\n path,\n `${filterPath}[${index}]`,\n newInheritedVisibility,\n );\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 newInheritedVisibility,\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 deprecatedKeys: withDeprecatedKeys ? deprecatedKeys : undefined,\n data:\n (transform(data, '', '', DEFAULT_CONFIG_VISIBILITY) 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 = normalizeAjvPath(error.schemaPath).slice(\n 1,\n -'/required'.length,\n );\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(normalizeAjvPath(error.instancePath)) ??\n DEFAULT_CONFIG_VISIBILITY;\n return vis && includeVisibilities.includes(vis);\n });\n}\n"],"names":["DEFAULT_CONFIG_VISIBILITY","normalizeAjvPath"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"load.cjs.js","sources":["../../src/schema/load.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 { 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';\nimport { normalizeAjvPath } from './utils';\n\n/**\n * Options that control the loading of configuration schema files in the backend.\n *\n * @public\n */\nexport type LoadConfigSchemaOptions =\n | (\n | {\n dependencies: string[];\n packagePaths?: string[];\n }\n | {\n serialized: JsonObject;\n }\n ) & {\n noUndeclaredProperties?: boolean;\n };\n\nfunction errorsToError(errors: ValidationError[]): Error {\n const messages = errors.map(({ instancePath, message, params }) => {\n const paramStr = Object.entries(params)\n .map(([name, value]) => `${name}=${value}`)\n .join(' ');\n return `Config ${message || ''} { ${paramStr} } at ${normalizeAjvPath(\n instancePath,\n )}`;\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 noUndeclaredProperties: options.noUndeclaredProperties,\n });\n\n return {\n process(\n configs: AppConfig[],\n {\n visibility,\n valueTransform,\n withFilteredKeys,\n withDeprecatedKeys,\n ignoreSchemaErrors,\n } = {},\n ): AppConfig[] {\n const result = validate(configs);\n\n if (!ignoreSchemaErrors) {\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\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 result.deepVisibilityByDataPath,\n result.deprecationByDataPath,\n valueTransform,\n withFilteredKeys,\n withDeprecatedKeys,\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 result.deepVisibilityByDataPath,\n result.deprecationByDataPath,\n valueTransform,\n withFilteredKeys,\n withDeprecatedKeys,\n ),\n }));\n }\n\n return processedConfigs;\n },\n serialize(): JsonObject {\n return {\n schemas,\n backstageConfigSchemaVersion: 1,\n };\n },\n };\n}\n"],"names":["normalizeAjvPath","collectConfigSchemas","compileConfigSchemas","filterErrorsByVisibility","filterByVisibility","CONFIG_VISIBILITIES"],"mappings":"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"}
1
+ {"version":3,"file":"load.cjs.js","sources":["../../src/schema/load.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 { 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';\nimport { normalizeAjvPath } from './utils';\n\n/**\n * Options that control the loading of configuration schema files in the backend.\n *\n * @public\n */\nexport type LoadConfigSchemaOptions =\n | (\n | {\n dependencies: string[];\n packagePaths?: string[];\n }\n | {\n serialized: JsonObject;\n }\n ) & {\n noUndeclaredProperties?: boolean;\n };\n\nfunction errorsToError(errors: ValidationError[]): Error {\n const messages = errors.map(({ instancePath, message, params }) => {\n const paramStr = Object.entries(params)\n .map(([name, value]) => `${name}=${value}`)\n .join(' ');\n return `Config ${message || ''} { ${paramStr} } at ${normalizeAjvPath(\n instancePath,\n )}`;\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 noUndeclaredProperties: options.noUndeclaredProperties,\n });\n\n return {\n process(\n configs: AppConfig[],\n {\n visibility,\n valueTransform,\n withFilteredKeys,\n withDeprecatedKeys,\n ignoreSchemaErrors,\n } = {},\n ): AppConfig[] {\n const result = validate(configs);\n\n if (!ignoreSchemaErrors) {\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\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 result.deepVisibilityByDataPath,\n result.deprecationByDataPath,\n valueTransform,\n withFilteredKeys,\n withDeprecatedKeys,\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 result.deepVisibilityByDataPath,\n result.deprecationByDataPath,\n valueTransform,\n withFilteredKeys,\n withDeprecatedKeys,\n ),\n }));\n }\n\n return processedConfigs;\n },\n serialize(): JsonObject {\n return {\n schemas,\n backstageConfigSchemaVersion: 1,\n };\n },\n };\n}\n"],"names":["normalizeAjvPath","collectConfigSchemas","compileConfigSchemas","filterErrorsByVisibility","filterByVisibility","CONFIG_VISIBILITIES"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.cjs.js","sources":["../../src/schema/types.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';\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 * The package name for the package this belongs to\n */\n packageName: 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 instancePath: 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 deep 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 deepVisibilityByDataPath: 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 * The deprecated options that were discovered during validation.\n *\n * The path in the key uses the form `/<key>/<sub-key>/<array-index>/<leaf-key>`\n */\n deprecationByDataPath: Map<string, string>;\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 * The \"path\" in the context is a JQ-style path to the current value from\n * within the original object passed to filterByVisibility().\n * For example, \"field.list[2]\" would refer to:\n * \\{\n * field: [\n * \"foo\",\n * \"bar\",\n * \"baz\" -- this one\n * ]\n * \\}\n *\n * @public\n */\nexport type TransformFunc<T extends number | string | boolean> = (\n value: T,\n context: { visibility: ConfigVisibility; path: string },\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 * When set to `true`, any schema errors in the provided configuration will be ignored.\n */\n ignoreSchemaErrors?: boolean;\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 * Whether or not to include the `deprecatedKeys` property in the output `AppConfig`s.\n *\n * Default: `true`.\n */\n withDeprecatedKeys?: 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"],"names":[],"mappings":";;AAwCO,MAAM,mBAAsB,GAAA,CAAC,UAAY,EAAA,SAAA,EAAW,QAAQ;AAY5D,MAAM,yBAA8C,GAAA;;;;;"}
1
+ {"version":3,"file":"types.cjs.js","sources":["../../src/schema/types.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';\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 * The package name for the package this belongs to\n */\n packageName: 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 instancePath: 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 deep 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 deepVisibilityByDataPath: 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 * The deprecated options that were discovered during validation.\n *\n * The path in the key uses the form `/<key>/<sub-key>/<array-index>/<leaf-key>`\n */\n deprecationByDataPath: Map<string, string>;\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 * The \"path\" in the context is a JQ-style path to the current value from\n * within the original object passed to filterByVisibility().\n * For example, \"field.list[2]\" would refer to:\n * \\{\n * field: [\n * \"foo\",\n * \"bar\",\n * \"baz\" -- this one\n * ]\n * \\}\n *\n * @public\n */\nexport type TransformFunc<T extends number | string | boolean> = (\n value: T,\n context: { visibility: ConfigVisibility; path: string },\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 * When set to `true`, any schema errors in the provided configuration will be ignored.\n */\n ignoreSchemaErrors?: boolean;\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 * Whether or not to include the `deprecatedKeys` property in the output `AppConfig`s.\n *\n * Default: `true`.\n */\n withDeprecatedKeys?: 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"],"names":[],"mappings":";;AAwCO,MAAM,mBAAA,GAAsB,CAAC,UAAA,EAAY,SAAA,EAAW,QAAQ;AAY5D,MAAM,yBAAA,GAA8C;;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs.js","sources":["../../src/schema/utils.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\n/**\n * AJV does some encoding to schema and instance paths. Revert those encodings to\n * easier to read and parse values.\n * See https://github.com/ajv-validator/ajv/blob/master/lib/compile/util.ts#L69.\n *\n * @param path Path from AJV.\n * @returns Updated path with correct characters.\n */\nexport function normalizeAjvPath(path: string) {\n return path\n .replace(/~1/g, '/')\n .replace(/\\['?(.*?)'?\\]/g, (_, segment) => `/${segment}`);\n}\n"],"names":[],"mappings":";;AAwBO,SAAS,iBAAiB,IAAc,EAAA;AAC7C,EAAA,OAAO,IACJ,CAAA,OAAA,CAAQ,KAAO,EAAA,GAAG,CAClB,CAAA,OAAA,CAAQ,gBAAkB,EAAA,CAAC,CAAG,EAAA,OAAA,KAAY,CAAI,CAAA,EAAA,OAAO,CAAE,CAAA,CAAA;AAC5D;;;;"}
1
+ {"version":3,"file":"utils.cjs.js","sources":["../../src/schema/utils.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\n/**\n * AJV does some encoding to schema and instance paths. Revert those encodings to\n * easier to read and parse values.\n * See https://github.com/ajv-validator/ajv/blob/master/lib/compile/util.ts#L69.\n *\n * @param path Path from AJV.\n * @returns Updated path with correct characters.\n */\nexport function normalizeAjvPath(path: string) {\n return path\n .replace(/~1/g, '/')\n .replace(/\\['?(.*?)'?\\]/g, (_, segment) => `/${segment}`);\n}\n"],"names":[],"mappings":";;AAwBO,SAAS,iBAAiB,IAAA,EAAc;AAC7C,EAAA,OAAO,IAAA,CACJ,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAClB,OAAA,CAAQ,gBAAA,EAAkB,CAAC,CAAA,EAAG,OAAA,KAAY,CAAA,CAAA,EAAI,OAAO,CAAA,CAAE,CAAA;AAC5D;;;;"}
@@ -76,6 +76,14 @@ class ConfigSources {
76
76
  if (argSources.length === 0) {
77
77
  const defaultPath = path.resolve(rootDir, "app-config.yaml");
78
78
  const localPath = path.resolve(rootDir, "app-config.local.yaml");
79
+ const envPath = path.resolve(
80
+ rootDir,
81
+ `app-config.${process.env.BACKSTAGE_ENV}.yaml`
82
+ );
83
+ const envLocalPath = path.resolve(
84
+ rootDir,
85
+ `app-config.${process.env.BACKSTAGE_ENV}.local.yaml`
86
+ );
79
87
  const alwaysIncludeDefaultConfigSource = !options.allowMissingDefaultConfig;
80
88
  if (alwaysIncludeDefaultConfigSource || fs__default.default.pathExistsSync(defaultPath)) {
81
89
  argSources.push(
@@ -86,6 +94,15 @@ class ConfigSources {
86
94
  })
87
95
  );
88
96
  }
97
+ if (process.env.BACKSTAGE_ENV && fs__default.default.pathExistsSync(envPath)) {
98
+ argSources.push(
99
+ FileConfigSource.FileConfigSource.create({
100
+ watch: options.watch,
101
+ path: envPath,
102
+ substitutionFunc: options.substitutionFunc
103
+ })
104
+ );
105
+ }
89
106
  if (fs__default.default.pathExistsSync(localPath)) {
90
107
  argSources.push(
91
108
  FileConfigSource.FileConfigSource.create({
@@ -95,6 +112,15 @@ class ConfigSources {
95
112
  })
96
113
  );
97
114
  }
115
+ if (process.env.BACKSTAGE_ENV && fs__default.default.pathExistsSync(envLocalPath)) {
116
+ argSources.push(
117
+ FileConfigSource.FileConfigSource.create({
118
+ watch: options.watch,
119
+ path: envLocalPath,
120
+ substitutionFunc: options.substitutionFunc
121
+ })
122
+ );
123
+ }
98
124
  }
99
125
  return this.merge(argSources);
100
126
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigSources.cjs.js","sources":["../../src/sources/ConfigSources.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { resolve as resolvePath } from 'path';\nimport fs from 'fs-extra';\nimport { Config, ConfigReader } from '@backstage/config';\nimport parseArgs from 'minimist';\nimport { EnvConfigSource } from './EnvConfigSource';\nimport { FileConfigSource } from './FileConfigSource';\nimport { MergedConfigSource } from './MergedConfigSource';\nimport {\n RemoteConfigSource,\n RemoteConfigSourceOptions,\n} from './RemoteConfigSource';\nimport { ConfigSource, SubstitutionFunc } from './types';\nimport { ObservableConfigProxy } from './ObservableConfigProxy';\nimport { findPaths } from '@backstage/cli-common';\n\n/**\n * A target to read configuration from.\n *\n * @public\n */\nexport type ConfigSourceTarget =\n | {\n type: 'path';\n target: string;\n }\n | {\n type: 'url';\n target: string;\n };\n\n/**\n * A config implementation that can be closed.\n *\n * @remarks\n *\n * Closing the configuration instance will stop the reading from the underlying source.\n *\n * @public\n */\nexport interface ClosableConfig extends Config {\n /**\n * Closes the configuration instance.\n *\n * @remarks\n *\n * The configuration instance will still be usable after closing, but it will\n * no longer be updated with new values from the underlying source.\n */\n close(): void;\n}\n\n/**\n * Common options for the default Backstage configuration sources.\n *\n * @public\n */\nexport interface BaseConfigSourcesOptions {\n watch?: boolean;\n rootDir?: string;\n remote?: Pick<RemoteConfigSourceOptions, 'reloadInterval'>;\n /**\n * Allow the default app-config.yaml to be missing, in which case the source\n * will not be created.\n */\n allowMissingDefaultConfig?: boolean;\n\n /**\n * A custom substitution function that overrides the default one.\n *\n * @remarks\n * The substitution function handles syntax like `${MY_ENV_VAR}` in configuration values.\n * The default substitution will read the value from the environment and trim whitespace.\n */\n substitutionFunc?: SubstitutionFunc;\n}\n\n/**\n * Options for {@link ConfigSources.defaultForTargets}.\n *\n * @public\n */\nexport interface ConfigSourcesDefaultForTargetsOptions\n extends BaseConfigSourcesOptions {\n targets: ConfigSourceTarget[];\n}\n\n/**\n * Options for {@link ConfigSources.default}.\n *\n * @public\n */\nexport interface ConfigSourcesDefaultOptions extends BaseConfigSourcesOptions {\n argv?: string[];\n env?: Record<string, string | undefined>;\n}\n\n/**\n * A collection of utilities for working with and creating {@link ConfigSource}s.\n *\n * @public\n */\nexport class ConfigSources {\n /**\n * Parses command line arguments and returns the config targets.\n *\n * @param argv - The command line arguments to parse. Defaults to `process.argv`\n * @returns A list of config targets\n */\n static parseArgs(argv: string[] = process.argv): ConfigSourceTarget[] {\n const args: string[] = [parseArgs(argv).config].flat().filter(Boolean);\n return args.map(target => {\n try {\n const url = new URL(target);\n\n // Some file paths are valid relative URLs, so check if the host is empty too\n if (!url.host) {\n return { type: 'path', target };\n }\n return { type: 'url', target };\n } catch {\n return { type: 'path', target };\n }\n });\n }\n\n /**\n * Creates the default config sources for the provided targets.\n *\n * @remarks\n *\n * This will create {@link FileConfigSource}s and {@link RemoteConfigSource}s\n * for the provided targets, and merge them together to a single source.\n * If no targets are provided it will fall back to `app-config.yaml` and\n * `app-config.local.yaml`.\n *\n * URL targets are only supported if the `remote` option is provided.\n *\n * @param options - Options\n * @returns A config source for the provided targets\n */\n static defaultForTargets(\n options: ConfigSourcesDefaultForTargetsOptions,\n ): ConfigSource {\n const rootDir = options.rootDir ?? findPaths(process.cwd()).targetRoot;\n\n const argSources = options.targets.map(arg => {\n if (arg.type === 'url') {\n if (!options.remote) {\n throw new Error(\n `Config argument \"${arg.target}\" looks like a URL but remote configuration is not enabled. Enable it by passing the \\`remote\\` option`,\n );\n }\n return RemoteConfigSource.create({\n url: arg.target,\n substitutionFunc: options.substitutionFunc,\n reloadInterval: options.remote.reloadInterval,\n });\n }\n return FileConfigSource.create({\n watch: options.watch,\n path: resolvePath(arg.target),\n substitutionFunc: options.substitutionFunc,\n });\n });\n\n if (argSources.length === 0) {\n const defaultPath = resolvePath(rootDir, 'app-config.yaml');\n const localPath = resolvePath(rootDir, 'app-config.local.yaml');\n const alwaysIncludeDefaultConfigSource =\n !options.allowMissingDefaultConfig;\n\n if (alwaysIncludeDefaultConfigSource || fs.pathExistsSync(defaultPath)) {\n argSources.push(\n FileConfigSource.create({\n watch: options.watch,\n path: defaultPath,\n substitutionFunc: options.substitutionFunc,\n }),\n );\n }\n\n if (fs.pathExistsSync(localPath)) {\n argSources.push(\n FileConfigSource.create({\n watch: options.watch,\n path: localPath,\n substitutionFunc: options.substitutionFunc,\n }),\n );\n }\n }\n\n return this.merge(argSources);\n }\n\n /**\n * Creates the default config source for Backstage.\n *\n * @remarks\n *\n * This will read from `app-config.yaml` and `app-config.local.yaml` by\n * default, as well as environment variables prefixed with `APP_CONFIG_`.\n * If `--config <path|url>` command line arguments are passed, these will\n * override the default configuration file paths. URLs are only supported\n * if the `remote` option is provided.\n *\n * @param options - Options\n * @returns The default Backstage config source\n */\n static default(options: ConfigSourcesDefaultOptions): ConfigSource {\n const argSource = this.defaultForTargets({\n ...options,\n targets: this.parseArgs(options.argv),\n });\n\n const envSource = EnvConfigSource.create({\n env: options.env,\n });\n\n return this.merge([argSource, envSource]);\n }\n\n /**\n * Merges multiple config sources into a single source that reads from all\n * sources and concatenates the result.\n *\n * @param sources - The config sources to merge\n * @returns A single config source that concatenates the data from the given sources\n */\n static merge(sources: ConfigSource[]): ConfigSource {\n return MergedConfigSource.from(sources);\n }\n\n /**\n * Creates an observable {@link @backstage/config#Config} implementation from a {@link ConfigSource}.\n *\n * @remarks\n *\n * If you only want to read the config once you can close the returned config immediately.\n *\n * @example\n *\n * ```ts\n * const sources = ConfigSources.default(...)\n * const config = await ConfigSources.toConfig(source)\n * config.close()\n * const example = config.getString(...)\n * ```\n *\n * @param source - The config source to read from\n * @returns A promise that resolves to a closable config\n */\n static toConfig(source: ConfigSource): Promise<ClosableConfig> {\n return new Promise(async (resolve, reject) => {\n let config: ObservableConfigProxy | undefined = undefined;\n try {\n const abortController = new AbortController();\n for await (const { configs } of source.readConfigData({\n signal: abortController.signal,\n })) {\n if (config) {\n config.setConfig(ConfigReader.fromConfigs(configs));\n } else {\n config = ObservableConfigProxy.create(abortController);\n config!.setConfig(ConfigReader.fromConfigs(configs));\n resolve(config);\n }\n }\n } catch (error) {\n reject(error);\n }\n });\n }\n}\n"],"names":["parseArgs","findPaths","RemoteConfigSource","FileConfigSource","resolvePath","fs","EnvConfigSource","MergedConfigSource","config","ConfigReader","ObservableConfigProxy"],"mappings":"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"}
1
+ {"version":3,"file":"ConfigSources.cjs.js","sources":["../../src/sources/ConfigSources.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { resolve as resolvePath } from 'path';\nimport fs from 'fs-extra';\nimport { Config, ConfigReader } from '@backstage/config';\nimport parseArgs from 'minimist';\nimport { EnvConfigSource } from './EnvConfigSource';\nimport { FileConfigSource } from './FileConfigSource';\nimport { MergedConfigSource } from './MergedConfigSource';\nimport {\n RemoteConfigSource,\n RemoteConfigSourceOptions,\n} from './RemoteConfigSource';\nimport { ConfigSource, SubstitutionFunc } from './types';\nimport { ObservableConfigProxy } from './ObservableConfigProxy';\nimport { findPaths } from '@backstage/cli-common';\n\n/**\n * A target to read configuration from.\n *\n * @public\n */\nexport type ConfigSourceTarget =\n | {\n type: 'path';\n target: string;\n }\n | {\n type: 'url';\n target: string;\n };\n\n/**\n * A config implementation that can be closed.\n *\n * @remarks\n *\n * Closing the configuration instance will stop the reading from the underlying source.\n *\n * @public\n */\nexport interface ClosableConfig extends Config {\n /**\n * Closes the configuration instance.\n *\n * @remarks\n *\n * The configuration instance will still be usable after closing, but it will\n * no longer be updated with new values from the underlying source.\n */\n close(): void;\n}\n\n/**\n * Common options for the default Backstage configuration sources.\n *\n * @public\n */\nexport interface BaseConfigSourcesOptions {\n watch?: boolean;\n rootDir?: string;\n remote?: Pick<RemoteConfigSourceOptions, 'reloadInterval'>;\n /**\n * Allow the default app-config.yaml to be missing, in which case the source\n * will not be created.\n */\n allowMissingDefaultConfig?: boolean;\n\n /**\n * A custom substitution function that overrides the default one.\n *\n * @remarks\n * The substitution function handles syntax like `${MY_ENV_VAR}` in configuration values.\n * The default substitution will read the value from the environment and trim whitespace.\n */\n substitutionFunc?: SubstitutionFunc;\n}\n\n/**\n * Options for {@link ConfigSources.defaultForTargets}.\n *\n * @public\n */\nexport interface ConfigSourcesDefaultForTargetsOptions\n extends BaseConfigSourcesOptions {\n targets: ConfigSourceTarget[];\n}\n\n/**\n * Options for {@link ConfigSources.default}.\n *\n * @public\n */\nexport interface ConfigSourcesDefaultOptions extends BaseConfigSourcesOptions {\n argv?: string[];\n env?: Record<string, string | undefined>;\n}\n\n/**\n * A collection of utilities for working with and creating {@link ConfigSource}s.\n *\n * @public\n */\nexport class ConfigSources {\n /**\n * Parses command line arguments and returns the config targets.\n *\n * @param argv - The command line arguments to parse. Defaults to `process.argv`\n * @returns A list of config targets\n */\n static parseArgs(argv: string[] = process.argv): ConfigSourceTarget[] {\n const args: string[] = [parseArgs(argv).config].flat().filter(Boolean);\n return args.map(target => {\n try {\n const url = new URL(target);\n\n // Some file paths are valid relative URLs, so check if the host is empty too\n if (!url.host) {\n return { type: 'path', target };\n }\n return { type: 'url', target };\n } catch {\n return { type: 'path', target };\n }\n });\n }\n\n /**\n * Creates the default config sources for the provided targets.\n *\n * @remarks\n *\n * This will create {@link FileConfigSource}s and {@link RemoteConfigSource}s\n * for the provided targets, and merge them together to a single source.\n * If no targets are provided it will fall back to `app-config.yaml` and\n * `app-config.local.yaml`.\n *\n * URL targets are only supported if the `remote` option is provided.\n *\n * @param options - Options\n * @returns A config source for the provided targets\n */\n static defaultForTargets(\n options: ConfigSourcesDefaultForTargetsOptions,\n ): ConfigSource {\n const rootDir = options.rootDir ?? findPaths(process.cwd()).targetRoot;\n\n const argSources = options.targets.map(arg => {\n if (arg.type === 'url') {\n if (!options.remote) {\n throw new Error(\n `Config argument \"${arg.target}\" looks like a URL but remote configuration is not enabled. Enable it by passing the \\`remote\\` option`,\n );\n }\n return RemoteConfigSource.create({\n url: arg.target,\n substitutionFunc: options.substitutionFunc,\n reloadInterval: options.remote.reloadInterval,\n });\n }\n return FileConfigSource.create({\n watch: options.watch,\n path: resolvePath(arg.target),\n substitutionFunc: options.substitutionFunc,\n });\n });\n\n if (argSources.length === 0) {\n const defaultPath = resolvePath(rootDir, 'app-config.yaml');\n const localPath = resolvePath(rootDir, 'app-config.local.yaml');\n const envPath = resolvePath(\n rootDir,\n `app-config.${process.env.BACKSTAGE_ENV}.yaml`,\n );\n const envLocalPath = resolvePath(\n rootDir,\n `app-config.${process.env.BACKSTAGE_ENV}.local.yaml`,\n );\n const alwaysIncludeDefaultConfigSource =\n !options.allowMissingDefaultConfig;\n\n if (alwaysIncludeDefaultConfigSource || fs.pathExistsSync(defaultPath)) {\n argSources.push(\n FileConfigSource.create({\n watch: options.watch,\n path: defaultPath,\n substitutionFunc: options.substitutionFunc,\n }),\n );\n }\n\n if (process.env.BACKSTAGE_ENV && fs.pathExistsSync(envPath)) {\n argSources.push(\n FileConfigSource.create({\n watch: options.watch,\n path: envPath,\n substitutionFunc: options.substitutionFunc,\n }),\n );\n }\n\n if (fs.pathExistsSync(localPath)) {\n argSources.push(\n FileConfigSource.create({\n watch: options.watch,\n path: localPath,\n substitutionFunc: options.substitutionFunc,\n }),\n );\n }\n\n if (process.env.BACKSTAGE_ENV && fs.pathExistsSync(envLocalPath)) {\n argSources.push(\n FileConfigSource.create({\n watch: options.watch,\n path: envLocalPath,\n substitutionFunc: options.substitutionFunc,\n }),\n );\n }\n }\n\n return this.merge(argSources);\n }\n\n /**\n * Creates the default config source for Backstage.\n *\n * @remarks\n *\n * This will read from `app-config.yaml` and `app-config.local.yaml` by\n * default, as well as environment variables prefixed with `APP_CONFIG_`.\n * If `--config <path|url>` command line arguments are passed, these will\n * override the default configuration file paths. URLs are only supported\n * if the `remote` option is provided.\n *\n * @param options - Options\n * @returns The default Backstage config source\n */\n static default(options: ConfigSourcesDefaultOptions): ConfigSource {\n const argSource = this.defaultForTargets({\n ...options,\n targets: this.parseArgs(options.argv),\n });\n\n const envSource = EnvConfigSource.create({\n env: options.env,\n });\n\n return this.merge([argSource, envSource]);\n }\n\n /**\n * Merges multiple config sources into a single source that reads from all\n * sources and concatenates the result.\n *\n * @param sources - The config sources to merge\n * @returns A single config source that concatenates the data from the given sources\n */\n static merge(sources: ConfigSource[]): ConfigSource {\n return MergedConfigSource.from(sources);\n }\n\n /**\n * Creates an observable {@link @backstage/config#Config} implementation from a {@link ConfigSource}.\n *\n * @remarks\n *\n * If you only want to read the config once you can close the returned config immediately.\n *\n * @example\n *\n * ```ts\n * const sources = ConfigSources.default(...)\n * const config = await ConfigSources.toConfig(source)\n * config.close()\n * const example = config.getString(...)\n * ```\n *\n * @param source - The config source to read from\n * @returns A promise that resolves to a closable config\n */\n static toConfig(source: ConfigSource): Promise<ClosableConfig> {\n return new Promise(async (resolve, reject) => {\n let config: ObservableConfigProxy | undefined = undefined;\n try {\n const abortController = new AbortController();\n for await (const { configs } of source.readConfigData({\n signal: abortController.signal,\n })) {\n if (config) {\n config.setConfig(ConfigReader.fromConfigs(configs));\n } else {\n config = ObservableConfigProxy.create(abortController);\n config!.setConfig(ConfigReader.fromConfigs(configs));\n resolve(config);\n }\n }\n } catch (error) {\n reject(error);\n }\n });\n }\n}\n"],"names":["parseArgs","findPaths","RemoteConfigSource","FileConfigSource","resolvePath","fs","EnvConfigSource","MergedConfigSource","config","ConfigReader","ObservableConfigProxy"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"EnvConfigSource.cjs.js","sources":["../../src/sources/EnvConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { assertError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { AsyncConfigSourceGenerator, ConfigSource } from './types';\n\n/**\n * Options for {@link EnvConfigSource.create}.\n *\n * @public\n */\nexport interface EnvConfigSourceOptions {\n /**\n * The environment variables to use, defaults to `process.env`.\n */\n env?: Record<string, string | undefined>;\n}\n\n/**\n * A config source that reads configuration from the environment.\n *\n * @remarks\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n */\nexport class EnvConfigSource implements ConfigSource {\n /**\n * Creates a new config source that reads from the environment.\n *\n * @param options - Options for the config source.\n * @returns A new config source that reads from the environment.\n */\n static create(options: EnvConfigSourceOptions): ConfigSource {\n return new EnvConfigSource(options?.env ?? process.env);\n }\n\n private constructor(\n private readonly env: { [name: string]: string | undefined },\n ) {}\n\n async *readConfigData(): AsyncConfigSourceGenerator {\n const configs = readEnvConfig(this.env);\n yield { configs };\n return;\n }\n\n toString() {\n const keys = Object.keys(this.env).filter(key =>\n key.startsWith('APP_CONFIG_'),\n );\n return `EnvConfigSource{count=${keys.length}}`;\n }\n}\n\nconst ENV_PREFIX = 'APP_CONFIG_';\n\n// Update the same pattern in config package if this is changed\nconst CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i;\n\n/**\n * Read runtime configuration from the environment.\n *\n * @remarks\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n * @deprecated Use {@link EnvConfigSource} instead\n */\nexport function readEnvConfig(env: {\n [name: string]: string | undefined;\n}): AppConfig[] {\n let data: JsonObject | undefined = undefined;\n\n for (const [name, value] of Object.entries(env)) {\n if (!value) {\n continue;\n }\n if (name.startsWith(ENV_PREFIX)) {\n const key = name.replace(ENV_PREFIX, '');\n const keyParts = key.split('_');\n\n let obj = (data = data ?? {});\n for (const [index, part] of keyParts.entries()) {\n if (!CONFIG_KEY_PART_PATTERN.test(part)) {\n throw new TypeError(`Invalid env config key '${key}'`);\n }\n if (index < keyParts.length - 1) {\n obj = (obj[part] = obj[part] ?? {}) as JsonObject;\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n const subKey = keyParts.slice(0, index + 1).join('_');\n throw new TypeError(\n `Could not nest config for key '${key}' under existing value '${subKey}'`,\n );\n }\n } else {\n if (part in obj) {\n throw new TypeError(\n `Refusing to override existing config at key '${key}'`,\n );\n }\n try {\n const [, parsedValue] = safeJsonParse(value);\n if (parsedValue === null) {\n throw new Error('value may not be null');\n }\n obj[part] = parsedValue;\n } catch (error) {\n throw new TypeError(\n `Failed to parse JSON-serialized config value for key '${key}', ${error}`,\n );\n }\n }\n }\n }\n }\n\n return data ? [{ data, context: 'env' }] : [];\n}\n\nfunction safeJsonParse(str: string): [Error | null, any] {\n try {\n return [null, JSON.parse(str)];\n } catch (err) {\n assertError(err);\n return [err, str];\n }\n}\n"],"names":["assertError"],"mappings":"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"}
1
+ {"version":3,"file":"EnvConfigSource.cjs.js","sources":["../../src/sources/EnvConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AppConfig } from '@backstage/config';\nimport { assertError } from '@backstage/errors';\nimport { JsonObject } from '@backstage/types';\nimport { AsyncConfigSourceGenerator, ConfigSource } from './types';\n\n/**\n * Options for {@link EnvConfigSource.create}.\n *\n * @public\n */\nexport interface EnvConfigSourceOptions {\n /**\n * The environment variables to use, defaults to `process.env`.\n */\n env?: Record<string, string | undefined>;\n}\n\n/**\n * A config source that reads configuration from the environment.\n *\n * @remarks\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n */\nexport class EnvConfigSource implements ConfigSource {\n /**\n * Creates a new config source that reads from the environment.\n *\n * @param options - Options for the config source.\n * @returns A new config source that reads from the environment.\n */\n static create(options: EnvConfigSourceOptions): ConfigSource {\n return new EnvConfigSource(options?.env ?? process.env);\n }\n\n private constructor(\n private readonly env: { [name: string]: string | undefined },\n ) {}\n\n async *readConfigData(): AsyncConfigSourceGenerator {\n const configs = readEnvConfig(this.env);\n yield { configs };\n return;\n }\n\n toString() {\n const keys = Object.keys(this.env).filter(key =>\n key.startsWith('APP_CONFIG_'),\n );\n return `EnvConfigSource{count=${keys.length}}`;\n }\n}\n\nconst ENV_PREFIX = 'APP_CONFIG_';\n\n// Update the same pattern in config package if this is changed\nconst CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/i;\n\n/**\n * Read runtime configuration from the environment.\n *\n * @remarks\n *\n * Only environment variables prefixed with APP_CONFIG_ will be considered.\n *\n * For each variable, the prefix will be removed, and rest of the key will\n * be split by '_'. Each part will then be used as keys to build up a nested\n * config object structure. The treatment of the entire environment variable\n * is case-sensitive.\n *\n * The value of the variable should be JSON serialized, as it will be parsed\n * and the type will be kept intact. For example \"true\" and true are treated\n * differently, as well as \"42\" and 42.\n *\n * For example, to set the config app.title to \"My Title\", use the following:\n *\n * APP_CONFIG_app_title='\"My Title\"'\n *\n * @public\n * @deprecated Use {@link EnvConfigSource} instead\n */\nexport function readEnvConfig(env: {\n [name: string]: string | undefined;\n}): AppConfig[] {\n let data: JsonObject | undefined = undefined;\n\n for (const [name, value] of Object.entries(env)) {\n if (!value) {\n continue;\n }\n if (name.startsWith(ENV_PREFIX)) {\n const key = name.replace(ENV_PREFIX, '');\n const keyParts = key.split('_');\n\n let obj = (data = data ?? {});\n for (const [index, part] of keyParts.entries()) {\n if (!CONFIG_KEY_PART_PATTERN.test(part)) {\n throw new TypeError(`Invalid env config key '${key}'`);\n }\n if (index < keyParts.length - 1) {\n obj = (obj[part] = obj[part] ?? {}) as JsonObject;\n if (typeof obj !== 'object' || Array.isArray(obj)) {\n const subKey = keyParts.slice(0, index + 1).join('_');\n throw new TypeError(\n `Could not nest config for key '${key}' under existing value '${subKey}'`,\n );\n }\n } else {\n if (part in obj) {\n throw new TypeError(\n `Refusing to override existing config at key '${key}'`,\n );\n }\n try {\n const [, parsedValue] = safeJsonParse(value);\n if (parsedValue === null) {\n throw new Error('value may not be null');\n }\n obj[part] = parsedValue;\n } catch (error) {\n throw new TypeError(\n `Failed to parse JSON-serialized config value for key '${key}', ${error}`,\n );\n }\n }\n }\n }\n }\n\n return data ? [{ data, context: 'env' }] : [];\n}\n\nfunction safeJsonParse(str: string): [Error | null, any] {\n try {\n return [null, JSON.parse(str)];\n } catch (err) {\n assertError(err);\n return [err, str];\n }\n}\n"],"names":["assertError"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"FileConfigSource.cjs.js","sources":["../../src/sources/FileConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport chokidar, { FSWatcher } from 'chokidar';\nimport fs from 'fs-extra';\nimport { basename, dirname, isAbsolute, resolve as resolvePath } from 'path';\nimport {\n AsyncConfigSourceGenerator,\n ConfigSource,\n ConfigSourceData,\n SubstitutionFunc,\n Parser,\n ReadConfigDataOptions,\n} from './types';\nimport { createConfigTransformer } from './transform';\nimport { NotFoundError } from '@backstage/errors';\nimport { parseYamlContent } from './utils';\n\n/**\n * Options for {@link FileConfigSource.create}.\n *\n * @public\n */\nexport interface FileConfigSourceOptions {\n /**\n * The path to the config file that should be loaded.\n */\n path: string;\n\n /**\n * Set to `false` to disable file watching, defaults to `true`.\n */\n watch?: boolean;\n\n /**\n * A substitution function to use instead of the default environment substitution.\n */\n substitutionFunc?: SubstitutionFunc;\n\n /**\n * A content parsing function to transform string content to configuration values.\n */\n parser?: Parser;\n}\n\nasync function readFile(path: string): Promise<string | undefined> {\n try {\n const content = await fs.readFile(path, 'utf8');\n // During watching we may sometimes read files too early before the file content has been written.\n // We never expect the writing to take a long time, but if we encounter an empty file then check\n // again after a short delay for safety.\n if (content === '') {\n await new Promise(resolve => setTimeout(resolve, 10));\n return await fs.readFile(path, 'utf8');\n }\n return content;\n } catch (error) {\n if (error.code === 'ENOENT') {\n return undefined;\n }\n throw error;\n }\n}\n\n/**\n * A config source that loads configuration from a local file.\n *\n * @public\n */\nexport class FileConfigSource implements ConfigSource {\n /**\n * Creates a new config source that loads configuration from the given path.\n *\n * @remarks\n *\n * The source will watch the file for changes, as well as any referenced files.\n *\n * @param options - Options for the config source.\n * @returns A new config source that loads from the given path.\n */\n static create(options: FileConfigSourceOptions): ConfigSource {\n if (!isAbsolute(options.path)) {\n throw new Error(`Config load path is not absolute: \"${options.path}\"`);\n }\n return new FileConfigSource(options);\n }\n\n readonly #path: string;\n readonly #substitutionFunc?: SubstitutionFunc;\n readonly #watch?: boolean;\n readonly #parser: Parser;\n\n private constructor(options: FileConfigSourceOptions) {\n this.#path = options.path;\n this.#substitutionFunc = options.substitutionFunc;\n this.#watch = options.watch ?? true;\n this.#parser = options.parser ?? parseYamlContent;\n }\n\n // Work is duplicated across each read, in practice that should not\n // have any impact since there won't be multiple consumers. If that\n // changes it might be worth refactoring this to avoid duplicate work.\n async *readConfigData(\n options?: ReadConfigDataOptions,\n ): AsyncConfigSourceGenerator {\n const signal = options?.signal;\n const configFileName = basename(this.#path);\n\n let watchedPaths: Array<string> | null = null;\n let watcher: FSWatcher | null = null;\n\n if (this.#watch) {\n // Keep track of watched paths, since this is simpler than resetting the watcher\n watchedPaths = new Array<string>();\n watcher = chokidar.watch(this.#path, {\n usePolling: process.env.NODE_ENV === 'test',\n });\n }\n\n const dir = dirname(this.#path);\n const transformer = createConfigTransformer({\n substitutionFunc: this.#substitutionFunc,\n readFile: async path => {\n const fullPath = resolvePath(dir, path);\n if (watcher && watchedPaths) {\n // Any files discovered while reading this config should be watched too\n watcher.add(fullPath);\n watchedPaths.push(fullPath);\n }\n\n const data = await readFile(fullPath);\n if (data === undefined) {\n throw new NotFoundError(\n `failed to include \"${fullPath}\", file does not exist`,\n );\n }\n return data;\n },\n });\n\n // This is the entry point for reading the file, called initially and on change\n const readConfigFile = async (): Promise<ConfigSourceData[]> => {\n if (watcher && watchedPaths) {\n // We clear the watched files every time we initiate a new read\n watcher.unwatch(watchedPaths);\n watchedPaths.length = 0;\n\n watcher.add(this.#path);\n watchedPaths.push(this.#path);\n }\n\n const contents = await readFile(this.#path);\n if (contents === undefined) {\n throw new NotFoundError(`Config file \"${this.#path}\" does not exist`);\n }\n const { result: parsed } = await this.#parser({ contents });\n if (parsed === undefined) {\n return [];\n }\n try {\n const data = await transformer(parsed, { dir });\n return [{ data, context: configFileName, path: this.#path }];\n } catch (error) {\n throw new Error(\n `Failed to read config file at \"${this.#path}\", ${error.message}`,\n );\n }\n };\n\n const onAbort = () => {\n signal?.removeEventListener('abort', onAbort);\n if (watcher) watcher.close();\n };\n signal?.addEventListener('abort', onAbort);\n\n yield { configs: await readConfigFile() };\n\n if (watcher) {\n for (;;) {\n const event = await this.#waitForEvent(watcher, signal);\n if (event === 'abort') {\n return;\n }\n yield { configs: await readConfigFile() };\n }\n }\n }\n\n toString() {\n return `FileConfigSource{path=\"${this.#path}\"}`;\n }\n\n #waitForEvent(\n watcher: FSWatcher,\n signal?: AbortSignal,\n ): Promise<'change' | 'abort'> {\n return new Promise(resolve => {\n function onChange() {\n resolve('change');\n onDone();\n }\n function onAbort() {\n resolve('abort');\n onDone();\n }\n function onDone() {\n watcher.removeListener('change', onChange);\n signal?.removeEventListener('abort', onAbort);\n }\n watcher.addListener('change', onChange);\n signal?.addEventListener('abort', onAbort);\n });\n }\n}\n"],"names":["fs","isAbsolute","parseYamlContent","basename","chokidar","dirname","createConfigTransformer","path","resolvePath","NotFoundError"],"mappings":"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"}
1
+ {"version":3,"file":"FileConfigSource.cjs.js","sources":["../../src/sources/FileConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport chokidar, { FSWatcher } from 'chokidar';\nimport fs from 'fs-extra';\nimport { basename, dirname, isAbsolute, resolve as resolvePath } from 'path';\nimport {\n AsyncConfigSourceGenerator,\n ConfigSource,\n ConfigSourceData,\n SubstitutionFunc,\n Parser,\n ReadConfigDataOptions,\n} from './types';\nimport { createConfigTransformer } from './transform';\nimport { NotFoundError } from '@backstage/errors';\nimport { parseYamlContent } from './utils';\n\n/**\n * Options for {@link FileConfigSource.create}.\n *\n * @public\n */\nexport interface FileConfigSourceOptions {\n /**\n * The path to the config file that should be loaded.\n */\n path: string;\n\n /**\n * Set to `false` to disable file watching, defaults to `true`.\n */\n watch?: boolean;\n\n /**\n * A substitution function to use instead of the default environment substitution.\n */\n substitutionFunc?: SubstitutionFunc;\n\n /**\n * A content parsing function to transform string content to configuration values.\n */\n parser?: Parser;\n}\n\nasync function readFile(path: string): Promise<string | undefined> {\n try {\n const content = await fs.readFile(path, 'utf8');\n // During watching we may sometimes read files too early before the file content has been written.\n // We never expect the writing to take a long time, but if we encounter an empty file then check\n // again after a short delay for safety.\n if (content === '') {\n await new Promise(resolve => setTimeout(resolve, 10));\n return await fs.readFile(path, 'utf8');\n }\n return content;\n } catch (error) {\n if (error.code === 'ENOENT') {\n return undefined;\n }\n throw error;\n }\n}\n\n/**\n * A config source that loads configuration from a local file.\n *\n * @public\n */\nexport class FileConfigSource implements ConfigSource {\n /**\n * Creates a new config source that loads configuration from the given path.\n *\n * @remarks\n *\n * The source will watch the file for changes, as well as any referenced files.\n *\n * @param options - Options for the config source.\n * @returns A new config source that loads from the given path.\n */\n static create(options: FileConfigSourceOptions): ConfigSource {\n if (!isAbsolute(options.path)) {\n throw new Error(`Config load path is not absolute: \"${options.path}\"`);\n }\n return new FileConfigSource(options);\n }\n\n readonly #path: string;\n readonly #substitutionFunc?: SubstitutionFunc;\n readonly #watch?: boolean;\n readonly #parser: Parser;\n\n private constructor(options: FileConfigSourceOptions) {\n this.#path = options.path;\n this.#substitutionFunc = options.substitutionFunc;\n this.#watch = options.watch ?? true;\n this.#parser = options.parser ?? parseYamlContent;\n }\n\n // Work is duplicated across each read, in practice that should not\n // have any impact since there won't be multiple consumers. If that\n // changes it might be worth refactoring this to avoid duplicate work.\n async *readConfigData(\n options?: ReadConfigDataOptions,\n ): AsyncConfigSourceGenerator {\n const signal = options?.signal;\n const configFileName = basename(this.#path);\n\n let watchedPaths: Array<string> | null = null;\n let watcher: FSWatcher | null = null;\n\n if (this.#watch) {\n // Keep track of watched paths, since this is simpler than resetting the watcher\n watchedPaths = new Array<string>();\n watcher = chokidar.watch(this.#path, {\n usePolling: process.env.NODE_ENV === 'test',\n });\n }\n\n const dir = dirname(this.#path);\n const transformer = createConfigTransformer({\n substitutionFunc: this.#substitutionFunc,\n readFile: async path => {\n const fullPath = resolvePath(dir, path);\n if (watcher && watchedPaths) {\n // Any files discovered while reading this config should be watched too\n watcher.add(fullPath);\n watchedPaths.push(fullPath);\n }\n\n const data = await readFile(fullPath);\n if (data === undefined) {\n throw new NotFoundError(\n `failed to include \"${fullPath}\", file does not exist`,\n );\n }\n return data;\n },\n });\n\n // This is the entry point for reading the file, called initially and on change\n const readConfigFile = async (): Promise<ConfigSourceData[]> => {\n if (watcher && watchedPaths) {\n // We clear the watched files every time we initiate a new read\n watcher.unwatch(watchedPaths);\n watchedPaths.length = 0;\n\n watcher.add(this.#path);\n watchedPaths.push(this.#path);\n }\n\n const contents = await readFile(this.#path);\n if (contents === undefined) {\n throw new NotFoundError(`Config file \"${this.#path}\" does not exist`);\n }\n const { result: parsed } = await this.#parser({ contents });\n if (parsed === undefined) {\n return [];\n }\n try {\n const data = await transformer(parsed, { dir });\n return [{ data, context: configFileName, path: this.#path }];\n } catch (error) {\n throw new Error(\n `Failed to read config file at \"${this.#path}\", ${error.message}`,\n );\n }\n };\n\n const onAbort = () => {\n signal?.removeEventListener('abort', onAbort);\n if (watcher) watcher.close();\n };\n signal?.addEventListener('abort', onAbort);\n\n yield { configs: await readConfigFile() };\n\n if (watcher) {\n for (;;) {\n const event = await this.#waitForEvent(watcher, signal);\n if (event === 'abort') {\n return;\n }\n yield { configs: await readConfigFile() };\n }\n }\n }\n\n toString() {\n return `FileConfigSource{path=\"${this.#path}\"}`;\n }\n\n #waitForEvent(\n watcher: FSWatcher,\n signal?: AbortSignal,\n ): Promise<'change' | 'abort'> {\n return new Promise(resolve => {\n function onChange() {\n resolve('change');\n onDone();\n }\n function onAbort() {\n resolve('abort');\n onDone();\n }\n function onDone() {\n watcher.removeListener('change', onChange);\n signal?.removeEventListener('abort', onAbort);\n }\n watcher.addListener('change', onChange);\n signal?.addEventListener('abort', onAbort);\n });\n }\n}\n"],"names":["fs","isAbsolute","parseYamlContent","basename","chokidar","dirname","createConfigTransformer","path","resolvePath","NotFoundError"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"MergedConfigSource.cjs.js","sources":["../../src/sources/MergedConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AsyncConfigSourceGenerator,\n ConfigSource,\n ConfigSourceData,\n ReadConfigDataOptions,\n} from './types';\n\nconst sourcesSymbol = Symbol.for(\n '@backstage/config-loader#MergedConfigSource.sources',\n);\n\n/** @internal */\nexport class MergedConfigSource implements ConfigSource {\n // An optimization to flatten nested merged sources to avid unnecessary microtasks\n static #flattenSources(sources: ConfigSource[]): ConfigSource[] {\n return sources.flatMap(source => {\n if (\n sourcesSymbol in source &&\n Array.isArray((source as any)[sourcesSymbol])\n ) {\n return this.#flattenSources(\n (source as any)[sourcesSymbol] as ConfigSource[],\n );\n }\n return source;\n });\n }\n\n static from(sources: ConfigSource[]): ConfigSource {\n return new MergedConfigSource(this.#flattenSources(sources));\n }\n\n [sourcesSymbol]: ConfigSource[];\n\n private constructor(private readonly sources: ConfigSource[]) {\n this[sourcesSymbol] = this.sources;\n }\n\n async *readConfigData(\n options?: ReadConfigDataOptions,\n ): AsyncConfigSourceGenerator {\n const its = this.sources.map(source => source.readConfigData(options));\n const initialResults = await Promise.all(its.map(it => it.next()));\n const configs = initialResults.map((result, i) => {\n if (result.done) {\n throw new Error(\n `Config source ${String(this.sources[i])} returned no data`,\n );\n }\n return result.value.configs;\n });\n\n yield { configs: configs.flat(1) };\n\n const results: Array<\n | Promise<\n readonly [\n number,\n IteratorResult<{ configs: ConfigSourceData[] }, void>,\n ]\n >\n | undefined\n > = its.map((it, i) => nextWithIndex(it, i));\n\n while (results.some(Boolean)) {\n try {\n const [i, result] = (await Promise.race(results.filter(Boolean)))!;\n if (result.done) {\n results[i] = undefined;\n } else {\n results[i] = nextWithIndex(its[i], i);\n configs[i] = result.value.configs;\n yield { configs: configs.flat(1) };\n }\n } catch (error) {\n const source = this.sources[error.index];\n if (source) {\n throw new Error(`Config source ${String(source)} failed: ${error}`);\n }\n throw error;\n }\n }\n }\n\n toString() {\n return `MergedConfigSource{${this.sources.map(String).join(', ')}}`;\n }\n}\n\n// Helper to wait for the next value of the iterator, while decorating the value\n// or error with the index of the iterator.\nfunction nextWithIndex<T>(\n iterator: AsyncIterator<T, void, void>,\n index: number,\n): Promise<readonly [index: number, result: IteratorResult<T, void>]> {\n return iterator.next().then(\n r => [index, r] as const,\n e => {\n throw Object.assign(e, { index });\n },\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MergedConfigSource.cjs.js","sources":["../../src/sources/MergedConfigSource.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AsyncConfigSourceGenerator,\n ConfigSource,\n ConfigSourceData,\n ReadConfigDataOptions,\n} from './types';\n\nconst sourcesSymbol = Symbol.for(\n '@backstage/config-loader#MergedConfigSource.sources',\n);\n\n/** @internal */\nexport class MergedConfigSource implements ConfigSource {\n // An optimization to flatten nested merged sources to avid unnecessary microtasks\n static #flattenSources(sources: ConfigSource[]): ConfigSource[] {\n return sources.flatMap(source => {\n if (\n sourcesSymbol in source &&\n Array.isArray((source as any)[sourcesSymbol])\n ) {\n return this.#flattenSources(\n (source as any)[sourcesSymbol] as ConfigSource[],\n );\n }\n return source;\n });\n }\n\n static from(sources: ConfigSource[]): ConfigSource {\n return new MergedConfigSource(this.#flattenSources(sources));\n }\n\n [sourcesSymbol]: ConfigSource[];\n\n private constructor(private readonly sources: ConfigSource[]) {\n this[sourcesSymbol] = this.sources;\n }\n\n async *readConfigData(\n options?: ReadConfigDataOptions,\n ): AsyncConfigSourceGenerator {\n const its = this.sources.map(source => source.readConfigData(options));\n const initialResults = await Promise.all(its.map(it => it.next()));\n const configs = initialResults.map((result, i) => {\n if (result.done) {\n throw new Error(\n `Config source ${String(this.sources[i])} returned no data`,\n );\n }\n return result.value.configs;\n });\n\n yield { configs: configs.flat(1) };\n\n const results: Array<\n | Promise<\n readonly [\n number,\n IteratorResult<{ configs: ConfigSourceData[] }, void>,\n ]\n >\n | undefined\n > = its.map((it, i) => nextWithIndex(it, i));\n\n while (results.some(Boolean)) {\n try {\n const [i, result] = (await Promise.race(results.filter(Boolean)))!;\n if (result.done) {\n results[i] = undefined;\n } else {\n results[i] = nextWithIndex(its[i], i);\n configs[i] = result.value.configs;\n yield { configs: configs.flat(1) };\n }\n } catch (error) {\n const source = this.sources[error.index];\n if (source) {\n throw new Error(`Config source ${String(source)} failed: ${error}`);\n }\n throw error;\n }\n }\n }\n\n toString() {\n return `MergedConfigSource{${this.sources.map(String).join(', ')}}`;\n }\n}\n\n// Helper to wait for the next value of the iterator, while decorating the value\n// or error with the index of the iterator.\nfunction nextWithIndex<T>(\n iterator: AsyncIterator<T, void, void>,\n index: number,\n): Promise<readonly [index: number, result: IteratorResult<T, void>]> {\n return iterator.next().then(\n r => [index, r] as const,\n e => {\n throw Object.assign(e, { index });\n },\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1 +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;;;;"}
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,mBAAA,CAA4C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOvD,OAAO,OAAO,OAAA,EAA2D;AACvE,IAAA,OAAO,IAAI,mBAAA;AAAA,MACT,SAAS,OAAA,IAAW,gBAAA;AAAA,MACpB,OAAA,EAAS;AAAA,KACX;AAAA,EACF;AAAA,EAEA,YAAA;AAAA,EACA,SAAA;AAAA,EACS,QAAA;AAAA,EACA,gBAAA,GAAmB,IAAI,eAAA,EAAgB;AAAA,EAExC,WAAA,CAAY,SAAiB,WAAA,EAA0B;AAC7D,IAAA,IAAA,CAAK,YAAA,GAAe,WAAA;AACpB,IAAA,IAAA,CAAK,QAAA,GAAW,OAAA;AAChB,IAAA,IAAA,CAAK,YAAYA,oBAAA,EAAe;AAAA,EAClC;AAAA,EAEA,OAAO,eACL,OAAA,EAC4B;AAC5B,IAAA,IAAI,kBAAkB,IAAA,CAAK,SAAA;AAE3B,IAAA,IAAI,IAAA,CAAK,iBAAiB,MAAA,EAAW;AACnC,MAAA,MAAM,EAAE,OAAA,EAAS,CAAC,EAAE,IAAA,EAAM,IAAA,CAAK,YAAA,EAAc,OAAA,EAAS,IAAA,CAAK,QAAA,EAAU,CAAA,EAAE;AAAA,IACzE;AAEA,IAAA,WAAS;AACP,MAAA,MAAM,CAAC,EAAE,CAAA,GAAI,MAAMC,kBAAY,eAAA,EAAiB;AAAA,QAC9C,OAAA,EAAS,MAAA;AAAA,QACT,KAAK,gBAAA,CAAiB;AAAA,OACvB,CAAA;AACD,MAAA,IAAI,CAAC,EAAA,EAAI;AACP,QAAA;AAAA,MACF;AACA,MAAA,eAAA,GAAkB,IAAA,CAAK,SAAA;AAEvB,MAAA,IAAI,IAAA,CAAK,iBAAiB,MAAA,EAAW;AACnC,QAAA,MAAM;AAAA,UACJ,OAAA,EAAS,CAAC,EAAE,IAAA,EAAM,KAAK,YAAA,EAAc,OAAA,EAAS,IAAA,CAAK,QAAA,EAAU;AAAA,SAC/D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAQ,IAAA,EAAwB;AAC9B,IAAA,IAAI,CAAC,IAAA,CAAK,gBAAA,CAAiB,MAAA,CAAO,OAAA,EAAS;AACzC,MAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AACpB,MAAA,MAAM,cAAc,IAAA,CAAK,SAAA;AACzB,MAAA,IAAA,CAAK,YAAYD,oBAAA,EAAe;AAChC,MAAA,WAAA,CAAY,OAAA,EAAQ;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,YAAA,GAAe,MAAA;AACpB,IAAA,IAAA,CAAK,iBAAiB,KAAA,EAAM;AAAA,EAC9B;AAAA,EAEA,QAAA,GAAW;AACT,IAAA,OAAO,CAAA,qBAAA,CAAA;AAAA,EACT;AACF;;;;"}
@@ -1 +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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"}
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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"}
@@ -1 +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 { 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 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 unparsable 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","ResponseError"],"mappings":"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"}
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 { 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 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 unparsable 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","ResponseError"],"mappings":"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"}
@@ -1 +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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"}
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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"}
@@ -1 +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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"}
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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"}
@@ -1 +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\nconst INCLUDE_KEYS = ['$file', '$env', '$include'];\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 =>\n INCLUDE_KEYS.includes(key),\n );\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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"}
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\nconst INCLUDE_KEYS = ['$file', '$env', '$include'];\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 =>\n INCLUDE_KEYS.includes(key),\n );\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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"}
@@ -1 +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;;;;"}
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,GAAA,EACe;AACf,EAAA,OAAO,OAAO,KAAA,KAAqB;AACjC,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,MAAA,OAAO,EAAE,SAAS,KAAA,EAAM;AAAA,IAC1B;AAEA,IAAA,MAAM,KAAA,GAAgC,KAAA,CAAM,KAAA,CAAM,mBAAmB,CAAA;AACrE,IAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,CAAA,EAAG;AACxC,MAAA,MAAM,IAAA,GAAO,MAAM,CAAC,CAAA;AACpB,MAAA,IAAI,IAAA,CAAK,UAAA,CAAW,IAAI,CAAA,EAAG;AACzB,QAAA,KAAA,CAAM,CAAC,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,CAAC,CAAA;AAAA,MACzB,CAAA,MAAO;AACL,QAAA,MAAM,wBAAA,GAA2B,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA;AAElD,QAAA,IAAI,2BAA2B,EAAA,EAAI;AACjC,UAAA,MAAM,cAAc,MAAM,GAAA;AAAA,YACxB,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,wBAAwB,EAAE,IAAA;AAAK,WAC/C;AACA,UAAA,MAAM,aAAA,GAAgB,KACnB,KAAA,CAAM,wBAAA,GAA2B,KAAK,MAAA,EAAQ,EAAE,EAChD,IAAA,EAAK;AAER,UAAA,KAAA,CAAM,CAAC,CAAA,GAAI,WAAA,IAAe,aAAA,IAAiB,MAAA;AAAA,QAC7C,CAAA,MAAO;AACL,UAAA,KAAA,CAAM,CAAC,CAAA,GAAI,MAAM,GAAA,CAAI,IAAA,CAAK,MAAM,CAAA,EAAG,EAAE,CAAA,CAAE,IAAA,EAAM,CAAA;AAAA,QAC/C;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI,KAAA,CAAM,IAAA,CAAK,CAAA,IAAA,KAAQ,IAAA,KAAS,MAAS,CAAA,EAAG;AAC1C,MAAA,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,KAAA,EAAO,MAAA,EAAU;AAAA,IAC3C;AACA,IAAA,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,OAAO,KAAA,CAAM,IAAA,CAAK,EAAE,CAAA,EAAE;AAAA,EAChD,CAAA;AACF;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs.js","sources":["../../../src/sources/transform/utils.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, 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"],"names":[],"mappings":";;AAkBO,SAAS,SAAS,GAA+C,EAAA;AACtE,EAAI,IAAA,OAAO,QAAQ,QAAU,EAAA;AAC3B,IAAO,OAAA,KAAA;AAAA,GACE,MAAA,IAAA,KAAA,CAAM,OAAQ,CAAA,GAAG,CAAG,EAAA;AAC7B,IAAO,OAAA,KAAA;AAAA;AAET,EAAA,OAAO,GAAQ,KAAA,IAAA;AACjB;;;;"}
1
+ {"version":3,"file":"utils.cjs.js","sources":["../../../src/sources/transform/utils.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, 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"],"names":[],"mappings":";;AAkBO,SAAS,SAAS,GAAA,EAA+C;AACtE,EAAA,IAAI,OAAO,QAAQ,QAAA,EAAU;AAC3B,IAAA,OAAO,KAAA;AAAA,EACT,CAAA,MAAA,IAAW,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,EAAG;AAC7B,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,GAAA,KAAQ,IAAA;AACjB;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs.js","sources":["../../src/sources/utils.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 { Parser } from './types';\nimport yaml from 'yaml';\n\n/** @internal */\nexport async function waitOrAbort<T>(\n promise: PromiseLike<T>,\n signal?: AbortSignal | Array<AbortSignal | undefined>,\n): Promise<[ok: true, value: T] | [ok: false]> {\n const signals = [signal].flat().filter((x): x is AbortSignal => !!x);\n return new Promise((resolve, reject) => {\n if (signals.some(s => s.aborted)) {\n resolve([false]);\n }\n const onAbort = () => {\n resolve([false]);\n };\n promise.then(\n value => {\n resolve([true, value]);\n signals.forEach(s => s.removeEventListener('abort', onAbort));\n },\n error => {\n reject(error);\n signals.forEach(s => s.removeEventListener('abort', onAbort));\n },\n );\n signals.forEach(s => s.addEventListener('abort', onAbort));\n });\n}\n\n/** @internal */\nexport const parseYamlContent: Parser = async ({ contents }) => {\n const parsed = yaml.parse(contents);\n return { result: parsed === null ? undefined : parsed };\n};\n"],"names":["yaml"],"mappings":";;;;;;;;AAoBsB,eAAA,WAAA,CACpB,SACA,MAC6C,EAAA;AAC7C,EAAM,MAAA,OAAA,GAAU,CAAC,MAAM,CAAE,CAAA,IAAA,EAAO,CAAA,MAAA,CAAO,CAAC,CAAA,KAAwB,CAAC,CAAC,CAAC,CAAA;AACnE,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAW,KAAA;AACtC,IAAA,IAAI,OAAQ,CAAA,IAAA,CAAK,CAAK,CAAA,KAAA,CAAA,CAAE,OAAO,CAAG,EAAA;AAChC,MAAQ,OAAA,CAAA,CAAC,KAAK,CAAC,CAAA;AAAA;AAEjB,IAAA,MAAM,UAAU,MAAM;AACpB,MAAQ,OAAA,CAAA,CAAC,KAAK,CAAC,CAAA;AAAA,KACjB;AACA,IAAQ,OAAA,CAAA,IAAA;AAAA,MACN,CAAS,KAAA,KAAA;AACP,QAAQ,OAAA,CAAA,CAAC,IAAM,EAAA,KAAK,CAAC,CAAA;AACrB,QAAA,OAAA,CAAQ,QAAQ,CAAK,CAAA,KAAA,CAAA,CAAE,mBAAoB,CAAA,OAAA,EAAS,OAAO,CAAC,CAAA;AAAA,OAC9D;AAAA,MACA,CAAS,KAAA,KAAA;AACP,QAAA,MAAA,CAAO,KAAK,CAAA;AACZ,QAAA,OAAA,CAAQ,QAAQ,CAAK,CAAA,KAAA,CAAA,CAAE,mBAAoB,CAAA,OAAA,EAAS,OAAO,CAAC,CAAA;AAAA;AAC9D,KACF;AACA,IAAA,OAAA,CAAQ,QAAQ,CAAK,CAAA,KAAA,CAAA,CAAE,gBAAiB,CAAA,OAAA,EAAS,OAAO,CAAC,CAAA;AAAA,GAC1D,CAAA;AACH;AAGO,MAAM,gBAA2B,GAAA,OAAO,EAAE,QAAA,EAAe,KAAA;AAC9D,EAAM,MAAA,MAAA,GAASA,qBAAK,CAAA,KAAA,CAAM,QAAQ,CAAA;AAClC,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAW,KAAA,IAAA,GAAO,SAAY,MAAO,EAAA;AACxD;;;;;"}
1
+ {"version":3,"file":"utils.cjs.js","sources":["../../src/sources/utils.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 { Parser } from './types';\nimport yaml from 'yaml';\n\n/** @internal */\nexport async function waitOrAbort<T>(\n promise: PromiseLike<T>,\n signal?: AbortSignal | Array<AbortSignal | undefined>,\n): Promise<[ok: true, value: T] | [ok: false]> {\n const signals = [signal].flat().filter((x): x is AbortSignal => !!x);\n return new Promise((resolve, reject) => {\n if (signals.some(s => s.aborted)) {\n resolve([false]);\n }\n const onAbort = () => {\n resolve([false]);\n };\n promise.then(\n value => {\n resolve([true, value]);\n signals.forEach(s => s.removeEventListener('abort', onAbort));\n },\n error => {\n reject(error);\n signals.forEach(s => s.removeEventListener('abort', onAbort));\n },\n );\n signals.forEach(s => s.addEventListener('abort', onAbort));\n });\n}\n\n/** @internal */\nexport const parseYamlContent: Parser = async ({ contents }) => {\n const parsed = yaml.parse(contents);\n return { result: parsed === null ? undefined : parsed };\n};\n"],"names":["yaml"],"mappings":";;;;;;;;AAoBA,eAAsB,WAAA,CACpB,SACA,MAAA,EAC6C;AAC7C,EAAA,MAAM,OAAA,GAAU,CAAC,MAAM,CAAA,CAAE,IAAA,EAAK,CAAE,MAAA,CAAO,CAAC,CAAA,KAAwB,CAAC,CAAC,CAAC,CAAA;AACnE,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,IAAI,OAAA,CAAQ,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,CAAA,EAAG;AAChC,MAAA,OAAA,CAAQ,CAAC,KAAK,CAAC,CAAA;AAAA,IACjB;AACA,IAAA,MAAM,UAAU,MAAM;AACpB,MAAA,OAAA,CAAQ,CAAC,KAAK,CAAC,CAAA;AAAA,IACjB,CAAA;AACA,IAAA,OAAA,CAAQ,IAAA;AAAA,MACN,CAAA,KAAA,KAAS;AACP,QAAA,OAAA,CAAQ,CAAC,IAAA,EAAM,KAAK,CAAC,CAAA;AACrB,QAAA,OAAA,CAAQ,QAAQ,CAAA,CAAA,KAAK,CAAA,CAAE,mBAAA,CAAoB,OAAA,EAAS,OAAO,CAAC,CAAA;AAAA,MAC9D,CAAA;AAAA,MACA,CAAA,KAAA,KAAS;AACP,QAAA,MAAA,CAAO,KAAK,CAAA;AACZ,QAAA,OAAA,CAAQ,QAAQ,CAAA,CAAA,KAAK,CAAA,CAAE,mBAAA,CAAoB,OAAA,EAAS,OAAO,CAAC,CAAA;AAAA,MAC9D;AAAA,KACF;AACA,IAAA,OAAA,CAAQ,QAAQ,CAAA,CAAA,KAAK,CAAA,CAAE,gBAAA,CAAiB,OAAA,EAAS,OAAO,CAAC,CAAA;AAAA,EAC3D,CAAC,CAAA;AACH;AAGO,MAAM,gBAAA,GAA2B,OAAO,EAAE,QAAA,EAAS,KAAM;AAC9D,EAAA,MAAM,MAAA,GAASA,qBAAA,CAAK,KAAA,CAAM,QAAQ,CAAA;AAClC,EAAA,OAAO,EAAE,MAAA,EAAQ,MAAA,KAAW,IAAA,GAAO,SAAY,MAAA,EAAO;AACxD;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/config-loader",
3
- "version": "1.10.2",
3
+ "version": "1.10.3-next.0",
4
4
  "description": "Config loading functionality used by Backstage backend, and CLI",
5
5
  "backstage": {
6
6
  "role": "node-library"
@@ -36,10 +36,10 @@
36
36
  "test": "backstage-cli package test"
37
37
  },
38
38
  "dependencies": {
39
- "@backstage/cli-common": "^0.1.15",
40
- "@backstage/config": "^1.3.3",
41
- "@backstage/errors": "^1.2.7",
42
- "@backstage/types": "^1.2.1",
39
+ "@backstage/cli-common": "0.1.15",
40
+ "@backstage/config": "1.3.3",
41
+ "@backstage/errors": "1.2.7",
42
+ "@backstage/types": "1.2.1",
43
43
  "@types/json-schema": "^7.0.6",
44
44
  "ajv": "^8.10.0",
45
45
  "chokidar": "^3.5.2",
@@ -53,8 +53,8 @@
53
53
  "yaml": "^2.0.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@backstage/backend-test-utils": "^1.7.0",
57
- "@backstage/cli": "^0.33.1",
56
+ "@backstage/backend-test-utils": "1.9.0-next.1",
57
+ "@backstage/cli": "0.34.2-next.2",
58
58
  "@types/json-schema-merge-allof": "^0.6.0",
59
59
  "@types/minimist": "^1.2.5",
60
60
  "msw": "^1.0.0",