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
864
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.5.1 to 0.6.0

LICENSE

16

CHANGELOG.md
# @backstage/config-loader
## 0.6.0
### Minor Changes
- 82c66b8cd: Fix bug where `${...}` was not being escaped to `${...}`
Add support for environment variable substitution in `$include`, `$file` and
`$env` transform values.
- This change allows for including dynamic paths, such as environment specific
secrets by using the same environment variable substitution (`${..}`) already
supported outside of the various include transforms.
- If you are currently using the syntax `${...}` in your include transform values,
you will need to escape the substitution by using `${...}` instead to maintain
the same behavior.
## 0.5.1

@@ -4,0 +20,0 @@

106

dist/index.cjs.js

@@ -5,3 +5,3 @@ 'use strict';

var yaml2 = require('yaml');
var yaml = require('yaml');
var path = require('path');

@@ -16,3 +16,3 @@ var Ajv = require('ajv');

var yaml2__default = /*#__PURE__*/_interopDefaultLegacy(yaml2);
var yaml__default = /*#__PURE__*/_interopDefaultLegacy(yaml);
var Ajv__default = /*#__PURE__*/_interopDefaultLegacy(Ajv);

@@ -135,6 +135,6 @@ var mergeAllOf__default = /*#__PURE__*/_interopDefaultLegacy(mergeAllOf);

".json": async (content) => JSON.parse(content),
".yaml": async (content) => yaml2__default['default'].parse(content),
".yml": async (content) => yaml2__default['default'].parse(content)
".yaml": async (content) => yaml__default['default'].parse(content),
".yml": async (content) => yaml__default['default'].parse(content)
};
function createIncludeTransform(env, readFile) {
function createIncludeTransform(env, readFile, substitute) {
return async (input, baseDir) => {

@@ -152,6 +152,11 @@ if (!isObject(input)) {

}
const includeValue = input[includeKey];
if (typeof includeValue !== "string") {
const rawIncludedValue = input[includeKey];
if (typeof rawIncludedValue !== "string") {
throw new Error(`${includeKey} include value is not a string`);
}
const substituteResults = await substitute(rawIncludedValue, baseDir);
const includeValue = substituteResults.applied ? substituteResults.value : rawIncludedValue;
if (includeValue === void 0 || typeof includeValue !== "string") {
throw new Error(`${includeKey} substitution value was undefined`);
}
switch (includeKey) {

@@ -178,5 +183,5 @@ case "$file":

}
const path2 = path.resolve(baseDir, filePath);
const content = await readFile(path2);
const newBaseDir = path.dirname(path2);
const path$1 = path.resolve(baseDir, filePath);
const content = await readFile(path$1);
const newBaseDir = path.dirname(path$1);
const parts = dataPath ? dataPath.split(".") : [];

@@ -213,5 +218,10 @@ let value;

}
const parts = input.split(/(?<!\$)\$\{([^{}]+)\}/);
const parts = input.split(/(\$?\$\{[^{}]*\})/);
for (let i = 1; i < parts.length; i += 2) {
parts[i] = await env(parts[i].trim());
const part = parts[i];
if (part.startsWith("$$")) {
parts[i] = part.slice(1);
} else {
parts[i] = await env(part.slice(2, -1).trim());
}
}

@@ -230,3 +240,3 @@ if (parts.some((part) => part === void 0)) {

const visibilityByPath = new Map();
const ajv2 = new Ajv__default['default']({
const ajv = new Ajv__default['default']({
allErrors: true,

@@ -258,3 +268,3 @@ allowUnionTypes: true,

try {
ajv2.compile(schema.value);
ajv.compile(schema.value);
} catch (error) {

@@ -264,25 +274,9 @@ throw new Error(`Schema at ${schema.path} is invalid, ${error}`);

}
const merged = mergeAllOf__default['default']({allOf: schemas.map((_) => _.value)}, {
ignoreAdditionalProperties: true,
resolvers: {
visibility(values, path) {
const hasFrontend = values.some((_) => _ === "frontend");
const hasSecret = values.some((_) => _ === "secret");
if (hasFrontend && hasSecret) {
throw new Error(`Config schema visibility is both 'frontend' and 'secret' for ${path.join("/")}`);
} else if (hasFrontend) {
return "frontend";
} else if (hasSecret) {
return "secret";
}
return "backend";
}
}
});
const validate = ajv2.compile(merged);
const merged = mergeConfigSchemas(schemas.map((_) => _.value));
const validate = ajv.compile(merged);
return (configs) => {
var _a;
const config2 = config.ConfigReader.fromConfigs(configs).get();
const config$1 = config.ConfigReader.fromConfigs(configs).get();
visibilityByPath.clear();
const valid = validate(config2);
const valid = validate(config$1);
if (!valid) {

@@ -303,2 +297,22 @@ const errors = (_a = validate.errors) != null ? _a : [];

}
function mergeConfigSchemas(schemas) {
const merged = mergeAllOf__default['default']({allOf: schemas}, {
ignoreAdditionalProperties: true,
resolvers: {
visibility(values, path) {
const hasFrontend = values.some((_) => _ === "frontend");
const hasSecret = values.some((_) => _ === "secret");
if (hasFrontend && hasSecret) {
throw new Error(`Config schema visibility is both 'frontend' and 'secret' for ${path.join("/")}`);
} else if (hasFrontend) {
return "frontend";
} else if (hasSecret) {
return "secret";
}
return "backend";
}
}
});
return merged;
}

@@ -345,7 +359,7 @@ const req = typeof __non_webpack_require__ === "undefined" ? require : __non_webpack_require__;

} else {
const path2 = path.resolve(path.dirname(pkgPath), pkg.configSchema);
const value = await fs__default['default'].readJson(path2);
const path$1 = path.resolve(path.dirname(pkgPath), pkg.configSchema);
const value = await fs__default['default'].readJson(path$1);
schemas.push({
value,
path: path.relative(currentDir, path2)
path: path.relative(currentDir, path$1)
});

@@ -382,3 +396,3 @@ }

});
const tsSchemas = paths.map((path2) => {
const tsSchemas = paths.map((path$1) => {
let value;

@@ -389,3 +403,3 @@ try {

validationKeywords: ["visibility"]
}, [path2.split(path.sep).join("/")]);
}, [path$1.split(path.sep).join("/")]);
} catch (error) {

@@ -397,5 +411,5 @@ if (error.message !== "type Config not found") {

if (!value) {
throw new Error(`Invalid schema in ${path2}, missing Config export`);
throw new Error(`Invalid schema in ${path$1}, missing Config export`);
}
return {path: path2, value};
return {path: path$1, value};
});

@@ -515,7 +529,8 @@ return tsSchemas;

const dir = path.dirname(configPath);
const readFile = (path2) => fs__default['default'].readFile(path.resolve(dir, path2), "utf8");
const input = yaml2__default['default'].parse(await readFile(configPath));
const readFile = (path$1) => fs__default['default'].readFile(path.resolve(dir, path$1), "utf8");
const input = yaml__default['default'].parse(await readFile(configPath));
const substitutionTransform = createSubstitutionTransform(env);
const data = await applyConfigTransforms(dir, input, [
createIncludeTransform(env, readFile),
createSubstitutionTransform(env)
createIncludeTransform(env, readFile, substitutionTransform),
substitutionTransform
]);

@@ -533,3 +548,4 @@ configs.push({data, context: path.basename(configPath)});

exports.loadConfigSchema = loadConfigSchema;
exports.mergeConfigSchemas = mergeConfigSchemas;
exports.readEnvConfig = readEnvConfig;
//# sourceMappingURL=index.cjs.js.map
import { AppConfig, JsonObject } from '@backstage/config';
import { JSONSchema7 } from 'json-schema';

@@ -66,2 +67,8 @@ /**

/**
* Given a list of configuration schemas from packages, merge them
* into a single json schema.
*/
declare function mergeConfigSchemas(schemas: JSONSchema7[]): JSONSchema7;
declare type Options = {

@@ -91,2 +98,2 @@ dependencies: string[];

export { ConfigSchema, ConfigVisibility, LoadConfigOptions, loadConfig, loadConfigSchema, readEnvConfig };
export { ConfigSchema, ConfigVisibility, LoadConfigOptions, loadConfig, loadConfigSchema, mergeConfigSchemas, readEnvConfig };

@@ -1,2 +0,2 @@

import yaml2 from 'yaml';
import yaml from 'yaml';
import { extname, resolve, dirname, sep, relative, isAbsolute, basename } from 'path';

@@ -122,6 +122,6 @@ import Ajv from 'ajv';

".json": async (content) => JSON.parse(content),
".yaml": async (content) => yaml2.parse(content),
".yml": async (content) => yaml2.parse(content)
".yaml": async (content) => yaml.parse(content),
".yml": async (content) => yaml.parse(content)
};
function createIncludeTransform(env, readFile) {
function createIncludeTransform(env, readFile, substitute) {
return async (input, baseDir) => {

@@ -139,6 +139,11 @@ if (!isObject(input)) {

}
const includeValue = input[includeKey];
if (typeof includeValue !== "string") {
const rawIncludedValue = input[includeKey];
if (typeof rawIncludedValue !== "string") {
throw new Error(`${includeKey} include value is not a string`);
}
const substituteResults = await substitute(rawIncludedValue, baseDir);
const includeValue = substituteResults.applied ? substituteResults.value : rawIncludedValue;
if (includeValue === void 0 || typeof includeValue !== "string") {
throw new Error(`${includeKey} substitution value was undefined`);
}
switch (includeKey) {

@@ -165,5 +170,5 @@ case "$file":

}
const path2 = resolve(baseDir, filePath);
const content = await readFile(path2);
const newBaseDir = dirname(path2);
const path = resolve(baseDir, filePath);
const content = await readFile(path);
const newBaseDir = dirname(path);
const parts = dataPath ? dataPath.split(".") : [];

@@ -200,5 +205,10 @@ let value;

}
const parts = input.split(/(?<!\$)\$\{([^{}]+)\}/);
const parts = input.split(/(\$?\$\{[^{}]*\})/);
for (let i = 1; i < parts.length; i += 2) {
parts[i] = await env(parts[i].trim());
const part = parts[i];
if (part.startsWith("$$")) {
parts[i] = part.slice(1);
} else {
parts[i] = await env(part.slice(2, -1).trim());
}
}

@@ -217,3 +227,3 @@ if (parts.some((part) => part === void 0)) {

const visibilityByPath = new Map();
const ajv2 = new Ajv({
const ajv = new Ajv({
allErrors: true,

@@ -245,3 +255,3 @@ allowUnionTypes: true,

try {
ajv2.compile(schema.value);
ajv.compile(schema.value);
} catch (error) {

@@ -251,25 +261,9 @@ throw new Error(`Schema at ${schema.path} is invalid, ${error}`);

}
const merged = mergeAllOf({allOf: schemas.map((_) => _.value)}, {
ignoreAdditionalProperties: true,
resolvers: {
visibility(values, path) {
const hasFrontend = values.some((_) => _ === "frontend");
const hasSecret = values.some((_) => _ === "secret");
if (hasFrontend && hasSecret) {
throw new Error(`Config schema visibility is both 'frontend' and 'secret' for ${path.join("/")}`);
} else if (hasFrontend) {
return "frontend";
} else if (hasSecret) {
return "secret";
}
return "backend";
}
}
});
const validate = ajv2.compile(merged);
const merged = mergeConfigSchemas(schemas.map((_) => _.value));
const validate = ajv.compile(merged);
return (configs) => {
var _a;
const config2 = ConfigReader.fromConfigs(configs).get();
const config = ConfigReader.fromConfigs(configs).get();
visibilityByPath.clear();
const valid = validate(config2);
const valid = validate(config);
if (!valid) {

@@ -290,2 +284,22 @@ const errors = (_a = validate.errors) != null ? _a : [];

}
function mergeConfigSchemas(schemas) {
const merged = mergeAllOf({allOf: schemas}, {
ignoreAdditionalProperties: true,
resolvers: {
visibility(values, path) {
const hasFrontend = values.some((_) => _ === "frontend");
const hasSecret = values.some((_) => _ === "secret");
if (hasFrontend && hasSecret) {
throw new Error(`Config schema visibility is both 'frontend' and 'secret' for ${path.join("/")}`);
} else if (hasFrontend) {
return "frontend";
} else if (hasSecret) {
return "secret";
}
return "backend";
}
}
});
return merged;
}

@@ -332,7 +346,7 @@ const req = typeof __non_webpack_require__ === "undefined" ? require : __non_webpack_require__;

} else {
const path2 = resolve(dirname(pkgPath), pkg.configSchema);
const value = await fs.readJson(path2);
const path = resolve(dirname(pkgPath), pkg.configSchema);
const value = await fs.readJson(path);
schemas.push({
value,
path: relative(currentDir, path2)
path: relative(currentDir, path)
});

@@ -369,3 +383,3 @@ }

});
const tsSchemas = paths.map((path2) => {
const tsSchemas = paths.map((path) => {
let value;

@@ -376,3 +390,3 @@ try {

validationKeywords: ["visibility"]
}, [path2.split(sep).join("/")]);
}, [path.split(sep).join("/")]);
} catch (error) {

@@ -384,5 +398,5 @@ if (error.message !== "type Config not found") {

if (!value) {
throw new Error(`Invalid schema in ${path2}, missing Config export`);
throw new Error(`Invalid schema in ${path}, missing Config export`);
}
return {path: path2, value};
return {path, value};
});

@@ -502,7 +516,8 @@ return tsSchemas;

const dir = dirname(configPath);
const readFile = (path2) => fs.readFile(resolve(dir, path2), "utf8");
const input = yaml2.parse(await readFile(configPath));
const readFile = (path) => fs.readFile(resolve(dir, path), "utf8");
const input = yaml.parse(await readFile(configPath));
const substitutionTransform = createSubstitutionTransform(env);
const data = await applyConfigTransforms(dir, input, [
createIncludeTransform(env, readFile),
createSubstitutionTransform(env)
createIncludeTransform(env, readFile, substitutionTransform),
substitutionTransform
]);

@@ -518,3 +533,3 @@ configs.push({data, context: basename(configPath)});

export { loadConfig, loadConfigSchema, readEnvConfig };
export { loadConfig, loadConfigSchema, mergeConfigSchemas, readEnvConfig };
//# sourceMappingURL=index.esm.js.map
{
"name": "@backstage/config-loader",
"description": "Config loading functionality used by Backstage backend, and CLI",
"version": "0.5.1",
"version": "0.6.0",
"private": false,

@@ -35,2 +35,3 @@ "publishConfig": {

"@backstage/config": "^0.1.1",
"@types/json-schema": "^7.0.6",
"ajv": "^7.0.3",

@@ -40,3 +41,3 @@ "fs-extra": "^9.0.0",

"json-schema-merge-allof": "^0.7.0",
"typescript-json-schema": "^0.47.0",
"typescript-json-schema": "^0.49.0",
"yaml": "^1.9.2",

@@ -47,6 +48,5 @@ "yup": "^0.29.3"

"@types/jest": "^26.0.7",
"@types/json-schema": "^7.0.6",
"@types/json-schema-merge-allof": "^0.6.0",
"@types/mock-fs": "^4.10.0",
"@types/node": "^12.0.0",
"@types/node": "^14.14.32",
"@types/yup": "^0.29.8",

@@ -58,3 +58,4 @@ "mock-fs": "^4.13.0"

],
"gitHead": "ae7b2a28d60159b94eda7020b41e7c3a86c8fb3a",
"module": "dist/index.esm.js"
}

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