@backstage/config-loader
Advanced tools
Comparing version 0.0.0-nightly-20218921925 to 0.0.0-nightly-202191622432
# @backstage/config-loader | ||
## 0.0.0-nightly-20218921925 | ||
## 0.0.0-nightly-202191622432 | ||
### Patch Changes | ||
- 223e8de6b4: Configuration schema errors are now filtered using the provided visibility option. This means that schema errors due to missing backend configuration will no longer break frontend builds. | ||
## 0.6.10 | ||
### Patch Changes | ||
- 957e4b3351: Updated dependencies | ||
- Updated dependencies | ||
- @backstage/cli-common@0.1.4 | ||
## 0.6.9 | ||
### Patch Changes | ||
- ee7a1a4b64: Add option to collect configuration schemas from explicit package paths in addition to by package name. | ||
- e68bd978e2: Allow collection of configuration schemas from multiple versions of the same package. | ||
## 0.6.8 | ||
### Patch Changes | ||
- d1da88a19: Properly export all used types. | ||
- Updated dependencies | ||
- @backstage/cli-common@0.0.0-nightly-20218921925 | ||
- @backstage/config@0.0.0-nightly-20218921925 | ||
- @backstage/cli-common@0.1.3 | ||
- @backstage/config@0.1.9 | ||
@@ -12,0 +33,0 @@ ## 0.6.7 |
@@ -9,2 +9,3 @@ 'use strict'; | ||
var mergeAllOf = require('json-schema-merge-allof'); | ||
var traverse = require('json-schema-traverse'); | ||
var config = require('@backstage/config'); | ||
@@ -20,2 +21,3 @@ var fs = require('fs-extra'); | ||
var mergeAllOf__default = /*#__PURE__*/_interopDefaultLegacy(mergeAllOf); | ||
var traverse__default = /*#__PURE__*/_interopDefaultLegacy(traverse); | ||
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); | ||
@@ -237,3 +239,3 @@ var chokidar__default = /*#__PURE__*/_interopDefaultLegacy(chokidar); | ||
function compileConfigSchemas(schemas) { | ||
const visibilityByPath = new Map(); | ||
const visibilityByDataPath = new Map(); | ||
const ajv = new Ajv__default['default']({ | ||
@@ -258,3 +260,3 @@ allErrors: true, | ||
const normalizedPath = context.dataPath.replace(/\['?(.*?)'?\]/g, (_, segment) => `/${segment}`); | ||
visibilityByPath.set(normalizedPath, visibility); | ||
visibilityByDataPath.set(normalizedPath, visibility); | ||
} | ||
@@ -274,19 +276,23 @@ return true; | ||
const validate = ajv.compile(merged); | ||
const visibilityBySchemaPath = new Map(); | ||
traverse__default['default'](merged, (schema, path) => { | ||
if (schema.visibility && schema.visibility !== "backend") { | ||
visibilityBySchemaPath.set(path, schema.visibility); | ||
} | ||
}); | ||
return (configs) => { | ||
var _a; | ||
const config$1 = config.ConfigReader.fromConfigs(configs).get(); | ||
visibilityByPath.clear(); | ||
visibilityByDataPath.clear(); | ||
const valid = validate(config$1); | ||
if (!valid) { | ||
const errors = (_a = validate.errors) != null ? _a : []; | ||
return { | ||
errors: errors.map(({dataPath, message, params}) => { | ||
const paramStr = Object.entries(params).map(([name, value]) => `${name}=${value}`).join(" "); | ||
return `Config ${message || ""} { ${paramStr} } at ${dataPath}`; | ||
}), | ||
visibilityByPath: new Map() | ||
errors: (_a = validate.errors) != null ? _a : [], | ||
visibilityByDataPath: new Map(visibilityByDataPath), | ||
visibilityBySchemaPath | ||
}; | ||
} | ||
return { | ||
visibilityByPath: new Map(visibilityByPath) | ||
visibilityByDataPath: new Map(visibilityByDataPath), | ||
visibilityBySchemaPath | ||
}; | ||
@@ -317,22 +323,37 @@ }; | ||
const req = typeof __non_webpack_require__ === "undefined" ? require : __non_webpack_require__; | ||
async function collectConfigSchemas(packageNames) { | ||
const visitedPackages = new Set(); | ||
const schemas = Array(); | ||
const tsSchemaPaths = Array(); | ||
async function collectConfigSchemas(packageNames, packagePaths) { | ||
const schemas = new Array(); | ||
const tsSchemaPaths = new Array(); | ||
const visitedPackageVersions = new Map(); | ||
const currentDir = await fs__default['default'].realpath(process.cwd()); | ||
async function processItem({name, parentPath}) { | ||
async function processItem(item) { | ||
var _a, _b, _c, _d; | ||
if (visitedPackages.has(name)) { | ||
let pkgPath = item.packagePath; | ||
if (pkgPath) { | ||
const pkgExists = await fs__default['default'].pathExists(pkgPath); | ||
if (!pkgExists) { | ||
return; | ||
} | ||
} else if (item.name) { | ||
const {name, parentPath} = item; | ||
try { | ||
pkgPath = req.resolve(`${name}/package.json`, parentPath && { | ||
paths: [parentPath] | ||
}); | ||
} catch { | ||
} | ||
} | ||
if (!pkgPath) { | ||
return; | ||
} | ||
visitedPackages.add(name); | ||
let pkgPath; | ||
try { | ||
pkgPath = req.resolve(`${name}/package.json`, parentPath && { | ||
paths: [parentPath] | ||
}); | ||
} catch { | ||
const pkg = await fs__default['default'].readJson(pkgPath); | ||
let versions = visitedPackageVersions.get(pkg.name); | ||
if (versions == null ? void 0 : versions.has(pkg.version)) { | ||
return; | ||
} | ||
const pkg = await fs__default['default'].readJson(pkgPath); | ||
if (!versions) { | ||
versions = new Set(); | ||
visitedPackageVersions.set(pkg.name, versions); | ||
} | ||
versions.add(pkg.version); | ||
const depNames = [ | ||
@@ -375,3 +396,6 @@ ...Object.keys((_a = pkg.dependencies) != null ? _a : {}), | ||
} | ||
await Promise.all(packageNames.map((name) => processItem({name, parentPath: currentDir}))); | ||
await Promise.all([ | ||
...packageNames.map((name) => processItem({name, parentPath: currentDir})), | ||
...packagePaths.map((path) => processItem({name: path, packagePath: path})) | ||
]); | ||
const tsSchemas = compileTsSchemas(tsSchemaPaths); | ||
@@ -416,3 +440,3 @@ return schemas.concat(tsSchemas); | ||
function filterByVisibility(data, includeVisibilities, visibilityByPath, transformFunc, withFilteredKeys) { | ||
function filterByVisibility(data, includeVisibilities, visibilityByDataPath, transformFunc, withFilteredKeys) { | ||
var _a; | ||
@@ -422,3 +446,3 @@ const filteredKeys = new Array(); | ||
var _a2; | ||
const visibility = (_a2 = visibilityByPath.get(visibilityPath)) != null ? _a2 : DEFAULT_CONFIG_VISIBILITY; | ||
const visibility = (_a2 = visibilityByDataPath.get(visibilityPath)) != null ? _a2 : DEFAULT_CONFIG_VISIBILITY; | ||
const isVisible = includeVisibilities.includes(visibility); | ||
@@ -473,7 +497,41 @@ if (typeof jsonVal !== "object") { | ||
} | ||
function filterErrorsByVisibility(errors, includeVisibilities, visibilityByDataPath, visibilityBySchemaPath) { | ||
if (!errors) { | ||
return []; | ||
} | ||
if (!includeVisibilities) { | ||
return errors; | ||
} | ||
const visibleSchemaPaths = Array.from(visibilityBySchemaPath).filter(([, v]) => includeVisibilities.includes(v)).map(([k]) => k); | ||
return errors.filter((error) => { | ||
var _a; | ||
if (error.keyword === "type" && ["object", "array"].includes(error.params.type)) { | ||
return true; | ||
} | ||
if (error.keyword === "required") { | ||
const trimmedPath = error.schemaPath.slice(1, -"/required".length); | ||
const fullPath = `${trimmedPath}/properties/${error.params.missingProperty}`; | ||
if (visibleSchemaPaths.some((visiblePath) => visiblePath.startsWith(fullPath))) { | ||
return true; | ||
} | ||
} | ||
const vis = (_a = visibilityByDataPath.get(error.dataPath)) != null ? _a : DEFAULT_CONFIG_VISIBILITY; | ||
return vis && includeVisibilities.includes(vis); | ||
}); | ||
} | ||
function errorsToError(errors) { | ||
const messages = errors.map(({dataPath, message, params}) => { | ||
const paramStr = Object.entries(params).map(([name, value]) => `${name}=${value}`).join(" "); | ||
return `Config ${message || ""} { ${paramStr} } at ${dataPath}`; | ||
}); | ||
const error = new Error(`Config validation failed, ${messages.join("; ")}`); | ||
error.messages = messages; | ||
return error; | ||
} | ||
async function loadConfigSchema(options) { | ||
var _a; | ||
let schemas; | ||
if ("dependencies" in options) { | ||
schemas = await collectConfigSchemas(options.dependencies); | ||
schemas = await collectConfigSchemas(options.dependencies, (_a = options.packagePaths) != null ? _a : []); | ||
} else { | ||
@@ -490,6 +548,5 @@ const {serialized} = options; | ||
const result = validate(configs); | ||
if (result.errors) { | ||
const error = new Error(`Config validation failed, ${result.errors.join("; ")}`); | ||
error.messages = result.errors; | ||
throw error; | ||
const visibleErrors = filterErrorsByVisibility(result.errors, visibility, result.visibilityByDataPath, result.visibilityBySchemaPath); | ||
if (visibleErrors.length > 0) { | ||
throw errorsToError(visibleErrors); | ||
} | ||
@@ -500,3 +557,3 @@ let processedConfigs = configs; | ||
context, | ||
...filterByVisibility(data, visibility, result.visibilityByPath, valueTransform, withFilteredKeys) | ||
...filterByVisibility(data, visibility, result.visibilityByDataPath, valueTransform, withFilteredKeys) | ||
})); | ||
@@ -506,3 +563,3 @@ } else if (valueTransform) { | ||
context, | ||
...filterByVisibility(data, Array.from(CONFIG_VISIBILITIES), result.visibilityByPath, valueTransform, withFilteredKeys) | ||
...filterByVisibility(data, Array.from(CONFIG_VISIBILITIES), result.visibilityByDataPath, valueTransform, withFilteredKeys) | ||
})); | ||
@@ -509,0 +566,0 @@ } |
@@ -91,2 +91,3 @@ import { AppConfig, JsonObject } from '@backstage/config'; | ||
dependencies: string[]; | ||
packagePaths?: string[]; | ||
} | { | ||
@@ -93,0 +94,0 @@ serialized: JsonObject; |
@@ -5,2 +5,3 @@ import yaml from 'yaml'; | ||
import mergeAllOf from 'json-schema-merge-allof'; | ||
import traverse from 'json-schema-traverse'; | ||
import { ConfigReader } from '@backstage/config'; | ||
@@ -224,3 +225,3 @@ import fs from 'fs-extra'; | ||
function compileConfigSchemas(schemas) { | ||
const visibilityByPath = new Map(); | ||
const visibilityByDataPath = new Map(); | ||
const ajv = new Ajv({ | ||
@@ -245,3 +246,3 @@ allErrors: true, | ||
const normalizedPath = context.dataPath.replace(/\['?(.*?)'?\]/g, (_, segment) => `/${segment}`); | ||
visibilityByPath.set(normalizedPath, visibility); | ||
visibilityByDataPath.set(normalizedPath, visibility); | ||
} | ||
@@ -261,19 +262,23 @@ return true; | ||
const validate = ajv.compile(merged); | ||
const visibilityBySchemaPath = new Map(); | ||
traverse(merged, (schema, path) => { | ||
if (schema.visibility && schema.visibility !== "backend") { | ||
visibilityBySchemaPath.set(path, schema.visibility); | ||
} | ||
}); | ||
return (configs) => { | ||
var _a; | ||
const config = ConfigReader.fromConfigs(configs).get(); | ||
visibilityByPath.clear(); | ||
visibilityByDataPath.clear(); | ||
const valid = validate(config); | ||
if (!valid) { | ||
const errors = (_a = validate.errors) != null ? _a : []; | ||
return { | ||
errors: errors.map(({dataPath, message, params}) => { | ||
const paramStr = Object.entries(params).map(([name, value]) => `${name}=${value}`).join(" "); | ||
return `Config ${message || ""} { ${paramStr} } at ${dataPath}`; | ||
}), | ||
visibilityByPath: new Map() | ||
errors: (_a = validate.errors) != null ? _a : [], | ||
visibilityByDataPath: new Map(visibilityByDataPath), | ||
visibilityBySchemaPath | ||
}; | ||
} | ||
return { | ||
visibilityByPath: new Map(visibilityByPath) | ||
visibilityByDataPath: new Map(visibilityByDataPath), | ||
visibilityBySchemaPath | ||
}; | ||
@@ -304,22 +309,37 @@ }; | ||
const req = typeof __non_webpack_require__ === "undefined" ? require : __non_webpack_require__; | ||
async function collectConfigSchemas(packageNames) { | ||
const visitedPackages = new Set(); | ||
const schemas = Array(); | ||
const tsSchemaPaths = Array(); | ||
async function collectConfigSchemas(packageNames, packagePaths) { | ||
const schemas = new Array(); | ||
const tsSchemaPaths = new Array(); | ||
const visitedPackageVersions = new Map(); | ||
const currentDir = await fs.realpath(process.cwd()); | ||
async function processItem({name, parentPath}) { | ||
async function processItem(item) { | ||
var _a, _b, _c, _d; | ||
if (visitedPackages.has(name)) { | ||
let pkgPath = item.packagePath; | ||
if (pkgPath) { | ||
const pkgExists = await fs.pathExists(pkgPath); | ||
if (!pkgExists) { | ||
return; | ||
} | ||
} else if (item.name) { | ||
const {name, parentPath} = item; | ||
try { | ||
pkgPath = req.resolve(`${name}/package.json`, parentPath && { | ||
paths: [parentPath] | ||
}); | ||
} catch { | ||
} | ||
} | ||
if (!pkgPath) { | ||
return; | ||
} | ||
visitedPackages.add(name); | ||
let pkgPath; | ||
try { | ||
pkgPath = req.resolve(`${name}/package.json`, parentPath && { | ||
paths: [parentPath] | ||
}); | ||
} catch { | ||
const pkg = await fs.readJson(pkgPath); | ||
let versions = visitedPackageVersions.get(pkg.name); | ||
if (versions == null ? void 0 : versions.has(pkg.version)) { | ||
return; | ||
} | ||
const pkg = await fs.readJson(pkgPath); | ||
if (!versions) { | ||
versions = new Set(); | ||
visitedPackageVersions.set(pkg.name, versions); | ||
} | ||
versions.add(pkg.version); | ||
const depNames = [ | ||
@@ -362,3 +382,6 @@ ...Object.keys((_a = pkg.dependencies) != null ? _a : {}), | ||
} | ||
await Promise.all(packageNames.map((name) => processItem({name, parentPath: currentDir}))); | ||
await Promise.all([ | ||
...packageNames.map((name) => processItem({name, parentPath: currentDir})), | ||
...packagePaths.map((path) => processItem({name: path, packagePath: path})) | ||
]); | ||
const tsSchemas = compileTsSchemas(tsSchemaPaths); | ||
@@ -403,3 +426,3 @@ return schemas.concat(tsSchemas); | ||
function filterByVisibility(data, includeVisibilities, visibilityByPath, transformFunc, withFilteredKeys) { | ||
function filterByVisibility(data, includeVisibilities, visibilityByDataPath, transformFunc, withFilteredKeys) { | ||
var _a; | ||
@@ -409,3 +432,3 @@ const filteredKeys = new Array(); | ||
var _a2; | ||
const visibility = (_a2 = visibilityByPath.get(visibilityPath)) != null ? _a2 : DEFAULT_CONFIG_VISIBILITY; | ||
const visibility = (_a2 = visibilityByDataPath.get(visibilityPath)) != null ? _a2 : DEFAULT_CONFIG_VISIBILITY; | ||
const isVisible = includeVisibilities.includes(visibility); | ||
@@ -460,7 +483,41 @@ if (typeof jsonVal !== "object") { | ||
} | ||
function filterErrorsByVisibility(errors, includeVisibilities, visibilityByDataPath, visibilityBySchemaPath) { | ||
if (!errors) { | ||
return []; | ||
} | ||
if (!includeVisibilities) { | ||
return errors; | ||
} | ||
const visibleSchemaPaths = Array.from(visibilityBySchemaPath).filter(([, v]) => includeVisibilities.includes(v)).map(([k]) => k); | ||
return errors.filter((error) => { | ||
var _a; | ||
if (error.keyword === "type" && ["object", "array"].includes(error.params.type)) { | ||
return true; | ||
} | ||
if (error.keyword === "required") { | ||
const trimmedPath = error.schemaPath.slice(1, -"/required".length); | ||
const fullPath = `${trimmedPath}/properties/${error.params.missingProperty}`; | ||
if (visibleSchemaPaths.some((visiblePath) => visiblePath.startsWith(fullPath))) { | ||
return true; | ||
} | ||
} | ||
const vis = (_a = visibilityByDataPath.get(error.dataPath)) != null ? _a : DEFAULT_CONFIG_VISIBILITY; | ||
return vis && includeVisibilities.includes(vis); | ||
}); | ||
} | ||
function errorsToError(errors) { | ||
const messages = errors.map(({dataPath, message, params}) => { | ||
const paramStr = Object.entries(params).map(([name, value]) => `${name}=${value}`).join(" "); | ||
return `Config ${message || ""} { ${paramStr} } at ${dataPath}`; | ||
}); | ||
const error = new Error(`Config validation failed, ${messages.join("; ")}`); | ||
error.messages = messages; | ||
return error; | ||
} | ||
async function loadConfigSchema(options) { | ||
var _a; | ||
let schemas; | ||
if ("dependencies" in options) { | ||
schemas = await collectConfigSchemas(options.dependencies); | ||
schemas = await collectConfigSchemas(options.dependencies, (_a = options.packagePaths) != null ? _a : []); | ||
} else { | ||
@@ -477,6 +534,5 @@ const {serialized} = options; | ||
const result = validate(configs); | ||
if (result.errors) { | ||
const error = new Error(`Config validation failed, ${result.errors.join("; ")}`); | ||
error.messages = result.errors; | ||
throw error; | ||
const visibleErrors = filterErrorsByVisibility(result.errors, visibility, result.visibilityByDataPath, result.visibilityBySchemaPath); | ||
if (visibleErrors.length > 0) { | ||
throw errorsToError(visibleErrors); | ||
} | ||
@@ -487,3 +543,3 @@ let processedConfigs = configs; | ||
context, | ||
...filterByVisibility(data, visibility, result.visibilityByPath, valueTransform, withFilteredKeys) | ||
...filterByVisibility(data, visibility, result.visibilityByDataPath, valueTransform, withFilteredKeys) | ||
})); | ||
@@ -493,3 +549,3 @@ } else if (valueTransform) { | ||
context, | ||
...filterByVisibility(data, Array.from(CONFIG_VISIBILITIES), result.visibilityByPath, valueTransform, withFilteredKeys) | ||
...filterByVisibility(data, Array.from(CONFIG_VISIBILITIES), result.visibilityByDataPath, valueTransform, withFilteredKeys) | ||
})); | ||
@@ -496,0 +552,0 @@ } |
{ | ||
"name": "@backstage/config-loader", | ||
"description": "Config loading functionality used by Backstage backend, and CLI", | ||
"version": "0.0.0-nightly-20218921925", | ||
"version": "0.0.0-nightly-202191622432", | ||
"private": false, | ||
@@ -33,4 +33,4 @@ "publishConfig": { | ||
"dependencies": { | ||
"@backstage/cli-common": "^0.0.0-nightly-20218921925", | ||
"@backstage/config": "^0.0.0-nightly-20218921925", | ||
"@backstage/cli-common": "^0.1.4", | ||
"@backstage/config": "^0.1.9", | ||
"@types/json-schema": "^7.0.6", | ||
@@ -42,5 +42,6 @@ "ajv": "^7.0.3", | ||
"json-schema-merge-allof": "^0.8.1", | ||
"json-schema-traverse": "^1.0.0", | ||
"typescript-json-schema": "^0.50.1", | ||
"yaml": "^1.9.2", | ||
"yup": "^0.29.3" | ||
"yup": "^0.32.9" | ||
}, | ||
@@ -52,4 +53,4 @@ "devDependencies": { | ||
"@types/node": "^14.14.32", | ||
"@types/yup": "^0.29.8", | ||
"mock-fs": "^4.13.0" | ||
"@types/yup": "^0.29.13", | ||
"mock-fs": "^5.1.0" | ||
}, | ||
@@ -56,0 +57,0 @@ "files": [ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
162842
139545
1348
12
18
48
5
187
+ Addedjson-schema-traverse@^1.0.0
+ Added@backstage/cli-common@0.1.15(transitive)
+ Added@backstage/config@0.1.15(transitive)
+ Added@backstage/types@0.1.3(transitive)
+ Added@types/lodash@4.17.13(transitive)
+ Addedjson-stable-stringify@1.2.0(transitive)
+ Addednanoclone@0.2.1(transitive)
+ Addedyup@0.32.11(transitive)
- Removed@backstage/cli-common@0.0.0-nightly-20241119023621(transitive)
- Removed@backstage/config@0.0.0-nightly-20241217023754(transitive)
- Removed@backstage/errors@0.0.0-nightly-20241217023754(transitive)
- Removed@backstage/types@1.2.0(transitive)
- Removedfn-name@3.0.0(transitive)
- Removedjson-stable-stringify@1.2.1(transitive)
- Removedms@2.1.3(transitive)
- Removedserialize-error@8.1.0(transitive)
- Removedsynchronous-promise@2.0.17(transitive)
- Removedtype-fest@0.20.2(transitive)
- Removedyup@0.29.3(transitive)
Updated@backstage/cli-common@^0.1.4
Updated@backstage/config@^0.1.9
Updatedyup@^0.32.9