@backstage/config-loader 1.9.1 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/index.cjs.js +22 -1650
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/loader.cjs.js +60 -0
  5. package/dist/loader.cjs.js.map +1 -0
  6. package/dist/schema/collect.cjs.js +169 -0
  7. package/dist/schema/collect.cjs.js.map +1 -0
  8. package/dist/schema/compile.cjs.js +185 -0
  9. package/dist/schema/compile.cjs.js.map +1 -0
  10. package/dist/schema/filtering.cjs.js +112 -0
  11. package/dist/schema/filtering.cjs.js.map +1 -0
  12. package/dist/schema/load.cjs.js +101 -0
  13. package/dist/schema/load.cjs.js.map +1 -0
  14. package/dist/schema/types.cjs.js +8 -0
  15. package/dist/schema/types.cjs.js.map +1 -0
  16. package/dist/schema/utils.cjs.js +8 -0
  17. package/dist/schema/utils.cjs.js.map +1 -0
  18. package/dist/sources/ConfigSources.cjs.js +178 -0
  19. package/dist/sources/ConfigSources.cjs.js.map +1 -0
  20. package/dist/sources/EnvConfigSource.cjs.js +88 -0
  21. package/dist/sources/EnvConfigSource.cjs.js.map +1 -0
  22. package/dist/sources/FileConfigSource.cjs.js +153 -0
  23. package/dist/sources/FileConfigSource.cjs.js.map +1 -0
  24. package/dist/sources/MergedConfigSource.cjs.js +72 -0
  25. package/dist/sources/MergedConfigSource.cjs.js.map +1 -0
  26. package/dist/sources/MutableConfigSource.cjs.js +75 -0
  27. package/dist/sources/MutableConfigSource.cjs.js.map +1 -0
  28. package/dist/sources/ObservableConfigProxy.cjs.js +123 -0
  29. package/dist/sources/ObservableConfigProxy.cjs.js.map +1 -0
  30. package/dist/sources/RemoteConfigSource.cjs.js +107 -0
  31. package/dist/sources/RemoteConfigSource.cjs.js.map +1 -0
  32. package/dist/sources/StaticConfigSource.cjs.js +95 -0
  33. package/dist/sources/StaticConfigSource.cjs.js.map +1 -0
  34. package/dist/sources/transform/apply.cjs.js +79 -0
  35. package/dist/sources/transform/apply.cjs.js.map +1 -0
  36. package/dist/sources/transform/include.cjs.js +107 -0
  37. package/dist/sources/transform/include.cjs.js.map +1 -0
  38. package/dist/sources/transform/substitution.cjs.js +34 -0
  39. package/dist/sources/transform/substitution.cjs.js.map +1 -0
  40. package/dist/sources/transform/utils.cjs.js +13 -0
  41. package/dist/sources/transform/utils.cjs.js.map +1 -0
  42. package/dist/sources/utils.cjs.js +38 -0
  43. package/dist/sources/utils.cjs.js.map +1 -0
  44. package/package.json +14 -7
@@ -0,0 +1 @@
1
+ {"version":3,"file":"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<string>();\n const visitedPackageVersions = new Map<string, Set<string>>(); // pkgName: [versions...]\n\n const currentDir = await fs.realpath(process.cwd());\n\n async function processItem(item: Item) {\n let pkgPath = item.packagePath;\n\n if (pkgPath) {\n const pkgExists = await fs.pathExists(pkgPath);\n if (!pkgExists) {\n return;\n }\n } else if (item.name) {\n const { name, parentPath } = item;\n\n try {\n pkgPath = req.resolve(\n `${name}/package.json`,\n parentPath && {\n paths: [parentPath],\n },\n );\n } catch {\n // We can somewhat safely ignore packages that don't export package.json,\n // as they are likely not part of the Backstage ecosystem anyway.\n }\n }\n if (!pkgPath) {\n return;\n }\n\n const pkg = await fs.readJson(pkgPath);\n\n // Ensures that we only process the same version of each package once.\n let versions = visitedPackageVersions.get(pkg.name);\n if (versions?.has(pkg.version)) {\n return;\n }\n if (!versions) {\n versions = new Set();\n visitedPackageVersions.set(pkg.name, versions);\n }\n versions.add(pkg.version);\n\n const depNames = [\n ...Object.keys(pkg.dependencies ?? {}),\n ...Object.keys(pkg.devDependencies ?? {}),\n ...Object.keys(pkg.optionalDependencies ?? {}),\n ...Object.keys(pkg.peerDependencies ?? {}),\n ];\n\n // TODO(Rugvip): Trying this out to avoid having to traverse the full dependency graph,\n // since that's pretty slow. We probably need a better way to determine when\n // we've left the Backstage ecosystem, but this will do for now.\n const hasSchema = 'configSchema' in pkg;\n const hasBackstageDep = depNames.some(_ => _.startsWith('@backstage/'));\n if (!hasSchema && !hasBackstageDep) {\n return;\n }\n if (hasSchema) {\n if (typeof pkg.configSchema === 'string') {\n const isJson = pkg.configSchema.endsWith('.json');\n const isDts = pkg.configSchema.endsWith('.d.ts');\n if (!isJson && !isDts) {\n throw new Error(\n `Config schema files must be .json or .d.ts, got ${pkg.configSchema}`,\n );\n }\n if (isDts) {\n tsSchemaPaths.push(\n relativePath(\n currentDir,\n resolvePath(dirname(pkgPath), pkg.configSchema),\n ),\n );\n } else {\n const path = resolvePath(dirname(pkgPath), pkg.configSchema);\n const value = await fs.readJson(path);\n schemas.push({\n value,\n path: relativePath(currentDir, path),\n });\n }\n } else {\n schemas.push({\n value: pkg.configSchema,\n path: relativePath(currentDir, pkgPath),\n });\n }\n }\n\n await Promise.all(\n depNames.map(depName =>\n processItem({ name: depName, parentPath: pkgPath }),\n ),\n );\n }\n\n await Promise.all([\n ...packageNames.map(name => processItem({ name, parentPath: currentDir })),\n ...packagePaths.map(path => processItem({ name: path, packagePath: path })),\n ]);\n\n const tsSchemas = await compileTsSchemas(tsSchemaPaths);\n\n return schemas.concat(tsSchemas);\n}\n\n// This handles the support of TypeScript .d.ts config schema declarations.\n// We collect all typescript schema definition and compile them all in one go.\n// This is much faster than compiling them separately.\nasync function compileTsSchemas(paths: string[]) {\n if (paths.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 } = await import(\n 'typescript-json-schema'\n );\n\n const program = getProgramFromFiles(paths, {\n incremental: false,\n isolatedModules: true,\n lib: ['ES5'], // Skipping most libs speeds processing up a lot, we just need the primitive types anyway\n noEmit: true,\n noResolve: true,\n skipLibCheck: true, // Skipping lib checks speeds things up\n skipDefaultLibCheck: true,\n strict: true,\n typeRoots: [], // Do not include any additional types\n types: [],\n });\n\n const tsSchemas = paths.map(path => {\n let value;\n try {\n 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 };\n });\n\n return tsSchemas;\n}\n"],"names":["fs","relativePath","resolvePath","dirname","path","sep","EOL","assertError"],"mappings":"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"}
@@ -0,0 +1,185 @@
1
+ 'use strict';
2
+
3
+ var Ajv = require('ajv');
4
+ var mergeAllOf = require('json-schema-merge-allof');
5
+ var traverse = require('json-schema-traverse');
6
+ var config = require('@backstage/config');
7
+ var types = require('./types.cjs.js');
8
+ var utils = require('./utils.cjs.js');
9
+
10
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
11
+
12
+ var Ajv__default = /*#__PURE__*/_interopDefaultCompat(Ajv);
13
+ var mergeAllOf__default = /*#__PURE__*/_interopDefaultCompat(mergeAllOf);
14
+ var traverse__default = /*#__PURE__*/_interopDefaultCompat(traverse);
15
+
16
+ const inheritedVisibility = Symbol("inherited-visibility");
17
+ function compileConfigSchemas(schemas, options) {
18
+ const visibilityByDataPath = /* @__PURE__ */ new Map();
19
+ const deepVisibilityByDataPath = /* @__PURE__ */ new Map();
20
+ const deprecationByDataPath = /* @__PURE__ */ new Map();
21
+ const ajv = new Ajv__default.default({
22
+ allErrors: true,
23
+ allowUnionTypes: true,
24
+ coerceTypes: true,
25
+ schemas: {
26
+ "https://backstage.io/schema/config-v1": true
27
+ }
28
+ }).addKeyword({
29
+ keyword: "visibility",
30
+ metaSchema: {
31
+ type: "string",
32
+ enum: types.CONFIG_VISIBILITIES
33
+ },
34
+ compile(visibility) {
35
+ return (_data, context) => {
36
+ if (context?.instancePath === void 0) {
37
+ return false;
38
+ }
39
+ if (visibility && visibility !== "backend") {
40
+ const normalizedPath = utils.normalizeAjvPath(context.instancePath);
41
+ visibilityByDataPath.set(normalizedPath, visibility);
42
+ }
43
+ return true;
44
+ };
45
+ }
46
+ }).addKeyword({
47
+ keyword: "deepVisibility",
48
+ metaSchema: {
49
+ type: "string",
50
+ /**
51
+ * Disallow 'backend' deepVisibility to prevent cases of permission escaping.
52
+ *
53
+ * Something like:
54
+ * - deepVisibility secret -> backend -> frontend.
55
+ * - deepVisibility secret -> backend -> visibility frontend.
56
+ */
57
+ enum: ["frontend", "secret"]
58
+ },
59
+ compile(visibility) {
60
+ return (_data, context) => {
61
+ if (context?.instancePath === void 0) {
62
+ return false;
63
+ }
64
+ if (visibility) {
65
+ const normalizedPath = utils.normalizeAjvPath(context.instancePath);
66
+ deepVisibilityByDataPath.set(normalizedPath, visibility);
67
+ }
68
+ return true;
69
+ };
70
+ }
71
+ }).removeKeyword("deprecated").addKeyword({
72
+ keyword: "deprecated",
73
+ metaSchema: { type: "string" },
74
+ compile(deprecationDescription) {
75
+ return (_data, context) => {
76
+ if (context?.instancePath === void 0) {
77
+ return false;
78
+ }
79
+ const normalizedPath = utils.normalizeAjvPath(context.instancePath);
80
+ deprecationByDataPath.set(normalizedPath, deprecationDescription);
81
+ return true;
82
+ };
83
+ }
84
+ });
85
+ for (const schema of schemas) {
86
+ try {
87
+ ajv.compile(schema.value);
88
+ } catch (error) {
89
+ throw new Error(`Schema at ${schema.path} is invalid, ${error}`);
90
+ }
91
+ }
92
+ const merged = mergeConfigSchemas(schemas.map((_) => _.value));
93
+ traverse__default.default(
94
+ merged,
95
+ (schema, jsonPtr, _1, _2, _3, parentSchema) => {
96
+ schema[inheritedVisibility] ??= schema?.deepVisibility ?? parentSchema?.[inheritedVisibility];
97
+ if (schema[inheritedVisibility]) {
98
+ const values = [
99
+ schema.visibility,
100
+ schema[inheritedVisibility],
101
+ parentSchema?.[inheritedVisibility]
102
+ ];
103
+ const hasFrontend = values.some((e) => e === "frontend");
104
+ const hasSecret = values.some((e) => e === "secret");
105
+ if (hasFrontend && hasSecret) {
106
+ throw new Error(
107
+ `Config schema visibility is both 'frontend' and 'secret' for ${jsonPtr}`
108
+ );
109
+ }
110
+ }
111
+ if (options?.noUndeclaredProperties) {
112
+ if (schema?.type === "object") {
113
+ schema.additionalProperties ||= false;
114
+ }
115
+ }
116
+ }
117
+ );
118
+ const validate = ajv.compile(merged);
119
+ const visibilityBySchemaPath = /* @__PURE__ */ new Map();
120
+ traverse__default.default(merged, (schema, path) => {
121
+ if (schema.visibility && schema.visibility !== "backend") {
122
+ visibilityBySchemaPath.set(utils.normalizeAjvPath(path), schema.visibility);
123
+ }
124
+ if (schema.deepVisibility) {
125
+ visibilityBySchemaPath.set(utils.normalizeAjvPath(path), schema.deepVisibility);
126
+ }
127
+ });
128
+ return (configs) => {
129
+ const config$1 = config.ConfigReader.fromConfigs(configs).getOptional();
130
+ visibilityByDataPath.clear();
131
+ deepVisibilityByDataPath.clear();
132
+ const valid = validate(config$1);
133
+ if (!valid) {
134
+ return {
135
+ errors: validate.errors ?? [],
136
+ visibilityByDataPath: new Map(visibilityByDataPath),
137
+ deepVisibilityByDataPath: new Map(deepVisibilityByDataPath),
138
+ visibilityBySchemaPath,
139
+ deprecationByDataPath
140
+ };
141
+ }
142
+ return {
143
+ visibilityByDataPath: new Map(visibilityByDataPath),
144
+ deepVisibilityByDataPath: new Map(deepVisibilityByDataPath),
145
+ visibilityBySchemaPath,
146
+ deprecationByDataPath
147
+ };
148
+ };
149
+ }
150
+ function mergeConfigSchemas(schemas) {
151
+ const merged = mergeAllOf__default.default(
152
+ { allOf: schemas },
153
+ {
154
+ // JSONSchema is typically subtractive, as in it always reduces the set of allowed
155
+ // inputs through constraints. This changes the object property merging to be additive
156
+ // rather than subtractive.
157
+ ignoreAdditionalProperties: true,
158
+ resolvers: {
159
+ // This ensures that the visibilities across different schemas are sound, and
160
+ // selects the most specific visibility for each path.
161
+ visibility(values, path) {
162
+ const hasFrontend = values.some((_) => _ === "frontend");
163
+ const hasSecret = values.some((_) => _ === "secret");
164
+ if (hasFrontend && hasSecret) {
165
+ throw new Error(
166
+ `Config schema visibility is both 'frontend' and 'secret' for ${path.join(
167
+ "/"
168
+ )}`
169
+ );
170
+ } else if (hasFrontend) {
171
+ return "frontend";
172
+ } else if (hasSecret) {
173
+ return "secret";
174
+ }
175
+ return "backend";
176
+ }
177
+ }
178
+ }
179
+ );
180
+ return merged;
181
+ }
182
+
183
+ exports.compileConfigSchemas = compileConfigSchemas;
184
+ exports.mergeConfigSchemas = mergeConfigSchemas;
185
+ //# sourceMappingURL=compile.cjs.js.map
@@ -0,0 +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;;;;;"}
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ var types = require('./types.cjs.js');
4
+ var utils = require('./utils.cjs.js');
5
+
6
+ function filterByVisibility(data, includeVisibilities, visibilityByDataPath, deepVisibilityByDataPath, deprecationByDataPath, transformFunc, withFilteredKeys, withDeprecatedKeys) {
7
+ const filteredKeys = new Array();
8
+ const deprecatedKeys = new Array();
9
+ function transform(jsonVal, visibilityPath, filterPath, inheritedVisibility) {
10
+ const visibility = visibilityByDataPath.get(visibilityPath) ?? inheritedVisibility;
11
+ const isVisible = includeVisibilities.includes(visibility);
12
+ const newInheritedVisibility = deepVisibilityByDataPath.get(visibilityPath) ?? inheritedVisibility;
13
+ const deprecation = deprecationByDataPath.get(visibilityPath);
14
+ if (deprecation) {
15
+ deprecatedKeys.push({ key: filterPath, description: deprecation });
16
+ }
17
+ if (typeof jsonVal !== "object") {
18
+ if (isVisible) {
19
+ if (transformFunc) {
20
+ return transformFunc(jsonVal, { visibility, path: filterPath });
21
+ }
22
+ return jsonVal;
23
+ }
24
+ if (withFilteredKeys) {
25
+ filteredKeys.push(filterPath);
26
+ }
27
+ return void 0;
28
+ } else if (jsonVal === null) {
29
+ return void 0;
30
+ } else if (Array.isArray(jsonVal)) {
31
+ const arr = new Array();
32
+ for (const [index, value] of jsonVal.entries()) {
33
+ let path = visibilityPath;
34
+ const hasVisibilityInIndex = visibilityByDataPath.get(
35
+ `${visibilityPath}/${index}`
36
+ );
37
+ if (hasVisibilityInIndex || typeof value === "object") {
38
+ path = `${visibilityPath}/${index}`;
39
+ }
40
+ const out = transform(
41
+ value,
42
+ path,
43
+ `${filterPath}[${index}]`,
44
+ newInheritedVisibility
45
+ );
46
+ if (out !== void 0) {
47
+ arr.push(out);
48
+ }
49
+ }
50
+ if (arr.length > 0 || isVisible) {
51
+ return arr;
52
+ }
53
+ return void 0;
54
+ }
55
+ const outObj = {};
56
+ let hasOutput = false;
57
+ for (const [key, value] of Object.entries(jsonVal)) {
58
+ if (value === void 0) {
59
+ continue;
60
+ }
61
+ const out = transform(
62
+ value,
63
+ `${visibilityPath}/${key}`,
64
+ filterPath ? `${filterPath}.${key}` : key,
65
+ newInheritedVisibility
66
+ );
67
+ if (out !== void 0) {
68
+ outObj[key] = out;
69
+ hasOutput = true;
70
+ }
71
+ }
72
+ if (hasOutput || isVisible) {
73
+ return outObj;
74
+ }
75
+ return void 0;
76
+ }
77
+ return {
78
+ filteredKeys: withFilteredKeys ? filteredKeys : void 0,
79
+ deprecatedKeys: withDeprecatedKeys ? deprecatedKeys : void 0,
80
+ data: transform(data, "", "", types.DEFAULT_CONFIG_VISIBILITY) ?? {}
81
+ };
82
+ }
83
+ function filterErrorsByVisibility(errors, includeVisibilities, visibilityByDataPath, visibilityBySchemaPath) {
84
+ if (!errors) {
85
+ return [];
86
+ }
87
+ if (!includeVisibilities) {
88
+ return errors;
89
+ }
90
+ const visibleSchemaPaths = Array.from(visibilityBySchemaPath).filter(([, v]) => includeVisibilities.includes(v)).map(([k]) => k);
91
+ return errors.filter((error) => {
92
+ if (error.keyword === "type" && ["object", "array"].includes(error.params.type)) {
93
+ return true;
94
+ }
95
+ if (error.keyword === "required") {
96
+ const trimmedPath = utils.normalizeAjvPath(error.schemaPath).slice(
97
+ 1,
98
+ -"/required".length
99
+ );
100
+ const fullPath = `${trimmedPath}/properties/${error.params.missingProperty}`;
101
+ if (visibleSchemaPaths.some((visiblePath) => visiblePath.startsWith(fullPath))) {
102
+ return true;
103
+ }
104
+ }
105
+ const vis = visibilityByDataPath.get(utils.normalizeAjvPath(error.instancePath)) ?? types.DEFAULT_CONFIG_VISIBILITY;
106
+ return vis && includeVisibilities.includes(vis);
107
+ });
108
+ }
109
+
110
+ exports.filterByVisibility = filterByVisibility;
111
+ exports.filterErrorsByVisibility = filterErrorsByVisibility;
112
+ //# sourceMappingURL=filtering.cjs.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ var compile = require('./compile.cjs.js');
4
+ var collect = require('./collect.cjs.js');
5
+ var filtering = require('./filtering.cjs.js');
6
+ var types = require('./types.cjs.js');
7
+ var utils = require('./utils.cjs.js');
8
+
9
+ function errorsToError(errors) {
10
+ const messages = errors.map(({ instancePath, message, params }) => {
11
+ const paramStr = Object.entries(params).map(([name, value]) => `${name}=${value}`).join(" ");
12
+ return `Config ${message || ""} { ${paramStr} } at ${utils.normalizeAjvPath(
13
+ instancePath
14
+ )}`;
15
+ });
16
+ const error = new Error(`Config validation failed, ${messages.join("; ")}`);
17
+ error.messages = messages;
18
+ return error;
19
+ }
20
+ async function loadConfigSchema(options) {
21
+ let schemas;
22
+ if ("dependencies" in options) {
23
+ schemas = await collect.collectConfigSchemas(
24
+ options.dependencies,
25
+ options.packagePaths ?? []
26
+ );
27
+ } else {
28
+ const { serialized } = options;
29
+ if (serialized?.backstageConfigSchemaVersion !== 1) {
30
+ throw new Error(
31
+ "Serialized configuration schema is invalid or has an invalid version number"
32
+ );
33
+ }
34
+ schemas = serialized.schemas;
35
+ }
36
+ const validate = compile.compileConfigSchemas(schemas, {
37
+ noUndeclaredProperties: options.noUndeclaredProperties
38
+ });
39
+ return {
40
+ process(configs, {
41
+ visibility,
42
+ valueTransform,
43
+ withFilteredKeys,
44
+ withDeprecatedKeys,
45
+ ignoreSchemaErrors
46
+ } = {}) {
47
+ const result = validate(configs);
48
+ if (!ignoreSchemaErrors) {
49
+ const visibleErrors = filtering.filterErrorsByVisibility(
50
+ result.errors,
51
+ visibility,
52
+ result.visibilityByDataPath,
53
+ result.visibilityBySchemaPath
54
+ );
55
+ if (visibleErrors.length > 0) {
56
+ throw errorsToError(visibleErrors);
57
+ }
58
+ }
59
+ let processedConfigs = configs;
60
+ if (visibility) {
61
+ processedConfigs = processedConfigs.map(({ data, context }) => ({
62
+ context,
63
+ ...filtering.filterByVisibility(
64
+ data,
65
+ visibility,
66
+ result.visibilityByDataPath,
67
+ result.deepVisibilityByDataPath,
68
+ result.deprecationByDataPath,
69
+ valueTransform,
70
+ withFilteredKeys,
71
+ withDeprecatedKeys
72
+ )
73
+ }));
74
+ } else if (valueTransform) {
75
+ processedConfigs = processedConfigs.map(({ data, context }) => ({
76
+ context,
77
+ ...filtering.filterByVisibility(
78
+ data,
79
+ Array.from(types.CONFIG_VISIBILITIES),
80
+ result.visibilityByDataPath,
81
+ result.deepVisibilityByDataPath,
82
+ result.deprecationByDataPath,
83
+ valueTransform,
84
+ withFilteredKeys,
85
+ withDeprecatedKeys
86
+ )
87
+ }));
88
+ }
89
+ return processedConfigs;
90
+ },
91
+ serialize() {
92
+ return {
93
+ schemas,
94
+ backstageConfigSchemaVersion: 1
95
+ };
96
+ }
97
+ };
98
+ }
99
+
100
+ exports.loadConfigSchema = loadConfigSchema;
101
+ //# sourceMappingURL=load.cjs.js.map
@@ -0,0 +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":";;;;;;;;AA+CA,SAAS,cAAc,MAAkC,EAAA;AACvD,EAAM,MAAA,QAAA,GAAW,OAAO,GAAI,CAAA,CAAC,EAAE,YAAc,EAAA,OAAA,EAAS,QAAa,KAAA;AACjE,IAAA,MAAM,WAAW,MAAO,CAAA,OAAA,CAAQ,MAAM,CACnC,CAAA,GAAA,CAAI,CAAC,CAAC,IAAA,EAAM,KAAK,CAAA,KAAM,GAAG,IAAI,CAAA,CAAA,EAAI,KAAK,CAAE,CAAA,CAAA,CACzC,KAAK,GAAG,CAAA;AACX,IAAA,OAAO,CAAU,OAAA,EAAA,OAAA,IAAW,EAAE,CAAA,GAAA,EAAM,QAAQ,CAAS,MAAA,EAAAA,sBAAA;AAAA,MACnD;AAAA,KACD,CAAA,CAAA;AAAA,GACF,CAAA;AACD,EAAM,MAAA,KAAA,GAAQ,IAAI,KAAM,CAAA,CAAA,0BAAA,EAA6B,SAAS,IAAK,CAAA,IAAI,CAAC,CAAE,CAAA,CAAA;AAC1E,EAAC,MAAc,QAAW,GAAA,QAAA;AAC1B,EAAO,OAAA,KAAA;AACT;AAOA,eAAsB,iBACpB,OACuB,EAAA;AACvB,EAAI,IAAA,OAAA;AAEJ,EAAA,IAAI,kBAAkB,OAAS,EAAA;AAC7B,IAAA,OAAA,GAAU,MAAMC,4BAAA;AAAA,MACd,OAAQ,CAAA,YAAA;AAAA,MACR,OAAA,CAAQ,gBAAgB;AAAC,KAC3B;AAAA,GACK,MAAA;AACL,IAAM,MAAA,EAAE,YAAe,GAAA,OAAA;AACvB,IAAI,IAAA,UAAA,EAAY,iCAAiC,CAAG,EAAA;AAClD,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA;AAEF,IAAA,OAAA,GAAU,UAAW,CAAA,OAAA;AAAA;AAGvB,EAAM,MAAA,QAAA,GAAWC,6BAAqB,OAAS,EAAA;AAAA,IAC7C,wBAAwB,OAAQ,CAAA;AAAA,GACjC,CAAA;AAED,EAAO,OAAA;AAAA,IACL,QACE,OACA,EAAA;AAAA,MACE,UAAA;AAAA,MACA,cAAA;AAAA,MACA,gBAAA;AAAA,MACA,kBAAA;AAAA,MACA;AAAA,KACF,GAAI,EACS,EAAA;AACb,MAAM,MAAA,MAAA,GAAS,SAAS,OAAO,CAAA;AAE/B,MAAA,IAAI,CAAC,kBAAoB,EAAA;AACvB,QAAA,MAAM,aAAgB,GAAAC,kCAAA;AAAA,UACpB,MAAO,CAAA,MAAA;AAAA,UACP,UAAA;AAAA,UACA,MAAO,CAAA,oBAAA;AAAA,UACP,MAAO,CAAA;AAAA,SACT;AACA,QAAI,IAAA,aAAA,CAAc,SAAS,CAAG,EAAA;AAC5B,UAAA,MAAM,cAAc,aAAa,CAAA;AAAA;AACnC;AAGF,MAAA,IAAI,gBAAmB,GAAA,OAAA;AAEvB,MAAA,IAAI,UAAY,EAAA;AACd,QAAA,gBAAA,GAAmB,iBAAiB,GAAI,CAAA,CAAC,EAAE,IAAA,EAAM,SAAe,MAAA;AAAA,UAC9D,OAAA;AAAA,UACA,GAAGC,4BAAA;AAAA,YACD,IAAA;AAAA,YACA,UAAA;AAAA,YACA,MAAO,CAAA,oBAAA;AAAA,YACP,MAAO,CAAA,wBAAA;AAAA,YACP,MAAO,CAAA,qBAAA;AAAA,YACP,cAAA;AAAA,YACA,gBAAA;AAAA,YACA;AAAA;AACF,SACA,CAAA,CAAA;AAAA,iBACO,cAAgB,EAAA;AACzB,QAAA,gBAAA,GAAmB,iBAAiB,GAAI,CAAA,CAAC,EAAE,IAAA,EAAM,SAAe,MAAA;AAAA,UAC9D,OAAA;AAAA,UACA,GAAGA,4BAAA;AAAA,YACD,IAAA;AAAA,YACA,KAAA,CAAM,KAAKC,yBAAmB,CAAA;AAAA,YAC9B,MAAO,CAAA,oBAAA;AAAA,YACP,MAAO,CAAA,wBAAA;AAAA,YACP,MAAO,CAAA,qBAAA;AAAA,YACP,cAAA;AAAA,YACA,gBAAA;AAAA,YACA;AAAA;AACF,SACA,CAAA,CAAA;AAAA;AAGJ,MAAO,OAAA,gBAAA;AAAA,KACT;AAAA,IACA,SAAwB,GAAA;AACtB,MAAO,OAAA;AAAA,QACL,OAAA;AAAA,QACA,4BAA8B,EAAA;AAAA,OAChC;AAAA;AACF,GACF;AACF;;;;"}
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ const CONFIG_VISIBILITIES = ["frontend", "backend", "secret"];
4
+ const DEFAULT_CONFIG_VISIBILITY = "backend";
5
+
6
+ exports.CONFIG_VISIBILITIES = CONFIG_VISIBILITIES;
7
+ exports.DEFAULT_CONFIG_VISIBILITY = DEFAULT_CONFIG_VISIBILITY;
8
+ //# sourceMappingURL=types.cjs.js.map
@@ -0,0 +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\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":";;AAoCO,MAAM,mBAAsB,GAAA,CAAC,UAAY,EAAA,SAAA,EAAW,QAAQ;AAY5D,MAAM,yBAA8C,GAAA;;;;;"}
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ function normalizeAjvPath(path) {
4
+ return path.replace(/~1/g, "/").replace(/\['?(.*?)'?\]/g, (_, segment) => `/${segment}`);
5
+ }
6
+
7
+ exports.normalizeAjvPath = normalizeAjvPath;
8
+ //# sourceMappingURL=utils.cjs.js.map
@@ -0,0 +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;;;;"}