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
862
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.1.1-alpha.7 to 0.1.1-alpha.9

169

dist/index.cjs.js

@@ -8,8 +8,9 @@ 'use strict';

var fs = _interopDefault(require('fs-extra'));
var path = require('path');
var yaml2 = _interopDefault(require('yaml'));
var path = require('path');
var yup = require('yup');
function findRootPath(topPath) {
let path2 = topPath;
for (let i = 0; i < 1000; i++) {
for (let i = 0; i < 1e3; i++) {
const packagePath = path.resolve(path2, "package.json");

@@ -36,2 +37,63 @@ const exists = fs.pathExistsSync(packagePath);

async function resolveStaticConfig(options) {
let {configPath} = options;
if (!configPath) {
configPath = path.resolve(findRootPath(fs.realpathSync(process.cwd())), "app-config.yaml");
}
return [configPath];
}
function isObject(obj) {
if (typeof obj !== "object") {
return false;
} else if (Array.isArray(obj)) {
return false;
}
return obj !== null;
}
async function readConfigFile(filePath, ctx) {
const configYaml = await ctx.readFile(filePath);
const config2 = yaml2.parse(configYaml);
async function transform(obj, path) {
if (typeof obj !== "object") {
return obj;
} else if (obj === null) {
return void 0;
} else if (Array.isArray(obj)) {
const arr = new Array();
for (const [index, value] of obj.entries()) {
const out2 = await transform(value, `${path}[${index}]`);
if (out2 !== void 0) {
arr.push(out2);
}
}
return arr;
}
if ("$secret" in obj) {
if (!isObject(obj.$secret)) {
throw TypeError(`Expected object at secret ${path}.$secret`);
}
try {
return await ctx.readSecret(obj.$secret);
} catch (error) {
throw new Error(`Invalid secret at ${path}: ${error.message}`);
}
}
const out = {};
for (const [key, value] of Object.entries(obj)) {
const result = await transform(value, `${path}.${key}`);
if (result !== void 0) {
out[key] = result;
}
}
return out;
}
const finalConfig = await transform(config2, "");
if (!isObject(finalConfig)) {
throw new TypeError("Expected object at config root");
}
return finalConfig;
}
const ENV_PREFIX = "APP_CONFIG_";

@@ -79,19 +141,100 @@ const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;

}
async function readStaticConfig(options) {
let {configPath} = options;
if (!configPath) {
configPath = path.resolve(findRootPath(fs.realpathSync(process.cwd())), "app-config.yaml");
const secretLoaderSchemas = {
file: yup.object({
file: yup.string().required()
}),
env: yup.object({
env: yup.string().required()
}),
data: yup.object({
data: yup.string().required(),
path: yup.lazy((value) => {
if (typeof value === "string") {
return yup.string().required();
}
return yup.array().of(yup.string().required()).required();
})
})
};
const secretSchema = yup.lazy((value) => {
if (typeof value !== "object" || value === null) {
return yup.object().required().label("secret");
}
try {
const configYaml = await fs.readFile(configPath, "utf8");
const config2 = yaml2.parse(configYaml);
return [config2];
} catch (error) {
throw new Error(`Failed to read static configuration file, ${error}`);
const loaderTypes = Object.keys(secretLoaderSchemas);
for (const key of loaderTypes) {
if (key in value) {
return secretLoaderSchemas[key];
}
}
throw new yup.ValidationError(`Secret must contain one of '${loaderTypes.join("', '")}'`, value, "$secret");
});
const dataSecretParser = {
".json": async (content) => JSON.parse(content),
".yaml": async (content) => yaml2.parse(content),
".yml": async (content) => yaml2.parse(content)
};
async function readSecret(data, ctx) {
const secret = secretSchema.validateSync(data, {strict: true});
if ("file" in secret) {
return ctx.readFile(secret.file);
}
if ("env" in secret) {
return ctx.env[secret.env];
}
if ("data" in secret) {
const ext = path.extname(secret.data);
const parser = dataSecretParser[ext];
if (!parser) {
throw new Error(`No data secret parser available for extension ${ext}`);
}
const content = await ctx.readFile(secret.data);
const {path: path2} = secret;
const parts = typeof path2 === "string" ? path2.split(".") : path2;
let value = await parser(content);
for (const [index, part] of parts.entries()) {
if (!isObject(value)) {
const errPath = parts.slice(0, index).join(".");
throw new Error(`Value is not an object at ${errPath} in ${secret.data}`);
}
value = value[part];
}
return String(value);
}
throw new Error("Secret was left unhandled");
}
class Context {
constructor(options) {
this.options = options;
}
get env() {
return this.options.env;
}
async readFile(path2) {
return fs.readFile(path.resolve(this.options.rootPath, path2), "utf8");
}
async readSecret(desc) {
if (!this.options.shouldReadSecrets) {
return void 0;
}
return readSecret(desc, this);
}
}
async function loadConfig(options = {}) {
const configs = [];
configs.push(...readEnv(process.env));
configs.push(...await readStaticConfig(options));
const configPaths = await resolveStaticConfig(options);
try {
for (const configPath of configPaths) {
const config2 = await readConfigFile(configPath, new Context({
env: process.env,
rootPath: path.dirname(configPath),
shouldReadSecrets: Boolean(options.shouldReadSecrets)
}));
configs.push(config2);
}
} catch (error) {
throw new Error(`Failed to read static configuration file: ${error.message}`);
}
return configs;

@@ -98,0 +241,0 @@ }

2

dist/index.d.ts

@@ -5,6 +5,6 @@ import { AppConfig } from '@backstage/config';

configPath?: string;
shouldReadSecrets?: boolean;
};
declare function loadConfig(options?: LoadConfigOptions): Promise<AppConfig[]>;
export { LoadConfigOptions, loadConfig };
import fs from 'fs-extra';
import { resolve, dirname, extname } from 'path';
import yaml2 from 'yaml';
import { resolve, dirname } from 'path';
import { object, string, lazy, array, ValidationError } from 'yup';
function findRootPath(topPath) {
let path2 = topPath;
for (let i = 0; i < 1000; i++) {
for (let i = 0; i < 1e3; i++) {
const packagePath = resolve(path2, "package.json");

@@ -29,2 +30,63 @@ const exists = fs.pathExistsSync(packagePath);

async function resolveStaticConfig(options) {
let {configPath} = options;
if (!configPath) {
configPath = resolve(findRootPath(fs.realpathSync(process.cwd())), "app-config.yaml");
}
return [configPath];
}
function isObject(obj) {
if (typeof obj !== "object") {
return false;
} else if (Array.isArray(obj)) {
return false;
}
return obj !== null;
}
async function readConfigFile(filePath, ctx) {
const configYaml = await ctx.readFile(filePath);
const config2 = yaml2.parse(configYaml);
async function transform(obj, path) {
if (typeof obj !== "object") {
return obj;
} else if (obj === null) {
return void 0;
} else if (Array.isArray(obj)) {
const arr = new Array();
for (const [index, value] of obj.entries()) {
const out2 = await transform(value, `${path}[${index}]`);
if (out2 !== void 0) {
arr.push(out2);
}
}
return arr;
}
if ("$secret" in obj) {
if (!isObject(obj.$secret)) {
throw TypeError(`Expected object at secret ${path}.$secret`);
}
try {
return await ctx.readSecret(obj.$secret);
} catch (error) {
throw new Error(`Invalid secret at ${path}: ${error.message}`);
}
}
const out = {};
for (const [key, value] of Object.entries(obj)) {
const result = await transform(value, `${path}.${key}`);
if (result !== void 0) {
out[key] = result;
}
}
return out;
}
const finalConfig = await transform(config2, "");
if (!isObject(finalConfig)) {
throw new TypeError("Expected object at config root");
}
return finalConfig;
}
const ENV_PREFIX = "APP_CONFIG_";

@@ -72,19 +134,100 @@ const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i;

}
async function readStaticConfig(options) {
let {configPath} = options;
if (!configPath) {
configPath = resolve(findRootPath(fs.realpathSync(process.cwd())), "app-config.yaml");
const secretLoaderSchemas = {
file: object({
file: string().required()
}),
env: object({
env: string().required()
}),
data: object({
data: string().required(),
path: lazy((value) => {
if (typeof value === "string") {
return string().required();
}
return array().of(string().required()).required();
})
})
};
const secretSchema = lazy((value) => {
if (typeof value !== "object" || value === null) {
return object().required().label("secret");
}
try {
const configYaml = await fs.readFile(configPath, "utf8");
const config2 = yaml2.parse(configYaml);
return [config2];
} catch (error) {
throw new Error(`Failed to read static configuration file, ${error}`);
const loaderTypes = Object.keys(secretLoaderSchemas);
for (const key of loaderTypes) {
if (key in value) {
return secretLoaderSchemas[key];
}
}
throw new ValidationError(`Secret must contain one of '${loaderTypes.join("', '")}'`, value, "$secret");
});
const dataSecretParser = {
".json": async (content) => JSON.parse(content),
".yaml": async (content) => yaml2.parse(content),
".yml": async (content) => yaml2.parse(content)
};
async function readSecret(data, ctx) {
const secret = secretSchema.validateSync(data, {strict: true});
if ("file" in secret) {
return ctx.readFile(secret.file);
}
if ("env" in secret) {
return ctx.env[secret.env];
}
if ("data" in secret) {
const ext = extname(secret.data);
const parser = dataSecretParser[ext];
if (!parser) {
throw new Error(`No data secret parser available for extension ${ext}`);
}
const content = await ctx.readFile(secret.data);
const {path: path2} = secret;
const parts = typeof path2 === "string" ? path2.split(".") : path2;
let value = await parser(content);
for (const [index, part] of parts.entries()) {
if (!isObject(value)) {
const errPath = parts.slice(0, index).join(".");
throw new Error(`Value is not an object at ${errPath} in ${secret.data}`);
}
value = value[part];
}
return String(value);
}
throw new Error("Secret was left unhandled");
}
class Context {
constructor(options) {
this.options = options;
}
get env() {
return this.options.env;
}
async readFile(path2) {
return fs.readFile(resolve(this.options.rootPath, path2), "utf8");
}
async readSecret(desc) {
if (!this.options.shouldReadSecrets) {
return void 0;
}
return readSecret(desc, this);
}
}
async function loadConfig(options = {}) {
const configs = [];
configs.push(...readEnv(process.env));
configs.push(...await readStaticConfig(options));
const configPaths = await resolveStaticConfig(options);
try {
for (const configPath of configPaths) {
const config2 = await readConfigFile(configPath, new Context({
env: process.env,
rootPath: dirname(configPath),
shouldReadSecrets: Boolean(options.shouldReadSecrets)
}));
configs.push(config2);
}
} catch (error) {
throw new Error(`Failed to read static configuration file: ${error.message}`);
}
return configs;

@@ -91,0 +234,0 @@ }

{
"name": "@backstage/config-loader",
"description": "Config loading functionality used by Backstage backend, and CLI",
"version": "0.1.1-alpha.7",
"version": "0.1.1-alpha.9",
"private": false,

@@ -35,7 +35,9 @@ "publishConfig": {

"fs-extra": "^9.0.0",
"yaml": "^1.9.2"
"yaml": "^1.9.2",
"yup": "^0.28.5"
},
"devDependencies": {
"@types/jest": "^25.2.2",
"@types/node": "^12.0.0"
"@types/node": "^12.0.0",
"@types/yup": "^0.28.2"
},

@@ -45,4 +47,4 @@ "files": [

],
"gitHead": "e61d09d545e4f4fa68cc204c9db0e0fd4eac84c3",
"gitHead": "89168d4d367e89017eb8992bef0db5b2c6317371",
"module": "dist/index.esm.js"
}
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