Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@backstage/config-loader

Package Overview
Dependencies
Maintainers
4
Versions
870
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@backstage/config-loader - npm Package Compare versions

Comparing version 0.0.0-nightly-20218921925 to 0.0.0-nightly-202191622432

27

CHANGELOG.md
# @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

125

dist/index.cjs.js

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc