@backstage/config-loader
Advanced tools
Comparing version 0.0.0-nightly-20217521743 to 0.0.0-nightly-202182821947
# @backstage/config-loader | ||
## 0.0.0-nightly-20217521743 | ||
## 0.0.0-nightly-202182821947 | ||
### Patch Changes | ||
- ee7a1a4b64: Add option to collect configuration schemas from explicit package paths in addition to by package name. | ||
## 0.6.8 | ||
### Patch Changes | ||
- d1da88a19: Properly export all used types. | ||
- Updated dependencies | ||
- @backstage/cli-common@0.1.3 | ||
- @backstage/config@0.1.9 | ||
## 0.6.7 | ||
### Patch Changes | ||
- 0ade9d02b: Include `devDependencies` and `optionalDependencies` in the detection of Backstage packages when collecting configuration schema. | ||
- 9b8cec063: Add support for config file watching through a new group of `watch` options to `loadConfig`. | ||
- Updated dependencies | ||
- @backstage/config@0.1.7 | ||
## 0.6.6 | ||
### Patch Changes | ||
- e9d3983ee: Add option to populate the `filteredKeys` property when processing configuration with a schema. | ||
- Updated dependencies | ||
- @backstage/config@0.0.0-nightly-20217521743 | ||
- @backstage/config@0.1.6 | ||
@@ -11,0 +35,0 @@ ## 0.6.5 |
@@ -12,2 +12,3 @@ 'use strict'; | ||
var typescriptJsonSchema = require('typescript-json-schema'); | ||
var chokidar = require('chokidar'); | ||
@@ -20,2 +21,3 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); | ||
var chokidar__default = /*#__PURE__*/_interopDefaultLegacy(chokidar); | ||
@@ -312,3 +314,3 @@ const ENV_PREFIX = "APP_CONFIG_"; | ||
const req = typeof __non_webpack_require__ === "undefined" ? require : __non_webpack_require__; | ||
async function collectConfigSchemas(packageNames) { | ||
async function collectConfigSchemas(packageNames, packagePaths) { | ||
const visitedPackages = new Set(); | ||
@@ -318,14 +320,24 @@ const schemas = Array(); | ||
const currentDir = await fs__default['default'].realpath(process.cwd()); | ||
async function processItem({name, parentPath}) { | ||
var _a, _b; | ||
if (visitedPackages.has(name)) { | ||
return; | ||
async function processItem(item) { | ||
var _a, _b, _c, _d; | ||
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; | ||
if (visitedPackages.has(name)) { | ||
return; | ||
} | ||
visitedPackages.add(name); | ||
try { | ||
pkgPath = req.resolve(`${name}/package.json`, parentPath && { | ||
paths: [parentPath] | ||
}); | ||
} catch { | ||
} | ||
} | ||
visitedPackages.add(name); | ||
let pkgPath; | ||
try { | ||
pkgPath = req.resolve(`${name}/package.json`, parentPath && { | ||
paths: [parentPath] | ||
}); | ||
} catch { | ||
if (!pkgPath) { | ||
return; | ||
@@ -336,3 +348,5 @@ } | ||
...Object.keys((_a = pkg.dependencies) != null ? _a : {}), | ||
...Object.keys((_b = pkg.peerDependencies) != null ? _b : {}) | ||
...Object.keys((_b = pkg.devDependencies) != null ? _b : {}), | ||
...Object.keys((_c = pkg.optionalDependencies) != null ? _c : {}), | ||
...Object.keys((_d = pkg.peerDependencies) != null ? _d : {}) | ||
]; | ||
@@ -370,3 +384,6 @@ const hasSchema = "configSchema" in pkg; | ||
} | ||
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); | ||
@@ -468,5 +485,6 @@ return schemas.concat(tsSchemas); | ||
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 { | ||
@@ -512,4 +530,3 @@ const {serialized} = options; | ||
async function loadConfig(options) { | ||
const configs = []; | ||
const {configRoot, experimentalEnvFunc: envFunc} = options; | ||
const {configRoot, experimentalEnvFunc: envFunc, watch} = options; | ||
const configPaths = options.configPaths.slice(); | ||
@@ -524,3 +541,4 @@ if (configPaths.length === 0) { | ||
const env = envFunc != null ? envFunc : async (name) => process.env[name]; | ||
try { | ||
const loadConfigFiles = async () => { | ||
const configs = []; | ||
for (const configPath of configPaths) { | ||
@@ -540,7 +558,36 @@ if (!path.isAbsolute(configPath)) { | ||
} | ||
return configs; | ||
}; | ||
let fileConfigs; | ||
try { | ||
fileConfigs = await loadConfigFiles(); | ||
} catch (error) { | ||
throw new Error(`Failed to read static configuration file, ${error.message}`); | ||
} | ||
configs.push(...readEnvConfig(process.env)); | ||
return configs; | ||
const envConfigs = await readEnvConfig(process.env); | ||
if (watch) { | ||
let currentSerializedConfig = JSON.stringify(fileConfigs); | ||
const watcher = chokidar__default['default'].watch(configPaths, { | ||
usePolling: process.env.NODE_ENV === "test" | ||
}); | ||
watcher.on("change", async () => { | ||
try { | ||
const newConfigs = await loadConfigFiles(); | ||
const newSerializedConfig = JSON.stringify(newConfigs); | ||
if (currentSerializedConfig === newSerializedConfig) { | ||
return; | ||
} | ||
currentSerializedConfig = newSerializedConfig; | ||
watch.onChange([...newConfigs, ...envConfigs]); | ||
} catch (error) { | ||
console.error(`Failed to reload configuration files, ${error}`); | ||
} | ||
}); | ||
if (watch.stopSignal) { | ||
watch.stopSignal.then(() => { | ||
watcher.close(); | ||
}); | ||
} | ||
} | ||
return [...fileConfigs, ...envConfigs]; | ||
} | ||
@@ -547,0 +594,0 @@ |
@@ -21,2 +21,4 @@ import { AppConfig, JsonObject } from '@backstage/config'; | ||
* APP_CONFIG_app_title='"My Title"' | ||
* | ||
* @public | ||
*/ | ||
@@ -27,14 +29,15 @@ declare function readEnvConfig(env: { | ||
/** @public */ | ||
declare type EnvFunc = (name: string) => Promise<string | undefined>; | ||
/** | ||
* A list of all possible configuration value visibilities. | ||
*/ | ||
declare const CONFIG_VISIBILITIES: readonly ["frontend", "backend", "secret"]; | ||
/** | ||
* A type representing the possible configuration value visibilities | ||
* | ||
* @public | ||
*/ | ||
declare type ConfigVisibility = typeof CONFIG_VISIBILITIES[number]; | ||
declare type ConfigVisibility = 'frontend' | 'backend' | 'secret'; | ||
/** | ||
* A function used to transform primitive configuration values. | ||
* | ||
* @public | ||
*/ | ||
@@ -46,4 +49,6 @@ declare type TransformFunc<T extends number | string | boolean> = (value: T, context: { | ||
* Options used to process configuration data with a schema. | ||
* | ||
* @public | ||
*/ | ||
declare type ConfigProcessingOptions = { | ||
declare type ConfigSchemaProcessingOptions = { | ||
/** | ||
@@ -70,5 +75,7 @@ * The visibilities that should be included in the output data. | ||
* A loaded configuration schema that is ready to process configuration data. | ||
* | ||
* @public | ||
*/ | ||
declare type ConfigSchema = { | ||
process(appConfigs: AppConfig[], options?: ConfigProcessingOptions): AppConfig[]; | ||
process(appConfigs: AppConfig[], options?: ConfigSchemaProcessingOptions): AppConfig[]; | ||
serialize(): JsonObject; | ||
@@ -80,7 +87,11 @@ }; | ||
* into a single json schema. | ||
* | ||
* @public | ||
*/ | ||
declare function mergeConfigSchemas(schemas: JSONSchema7[]): JSONSchema7; | ||
declare type Options = { | ||
/** @public */ | ||
declare type LoadConfigSchemaOptions = { | ||
dependencies: string[]; | ||
packagePaths?: string[]; | ||
} | { | ||
@@ -91,5 +102,8 @@ serialized: JsonObject; | ||
* Loads config schema for a Backstage instance. | ||
* | ||
* @public | ||
*/ | ||
declare function loadConfigSchema(options: Options): Promise<ConfigSchema>; | ||
declare function loadConfigSchema(options: LoadConfigSchemaOptions): Promise<ConfigSchema>; | ||
/** @public */ | ||
declare type LoadConfigOptions = { | ||
@@ -106,5 +120,23 @@ configRoot: string; | ||
experimentalEnvFunc?: EnvFunc; | ||
/** | ||
* An optional configuration that enables watching of config files. | ||
*/ | ||
watch?: { | ||
/** | ||
* A listener that is called when a config file is changed. | ||
*/ | ||
onChange: (configs: AppConfig[]) => void; | ||
/** | ||
* An optional signal that stops the watcher once the promise resolves. | ||
*/ | ||
stopSignal?: Promise<void>; | ||
}; | ||
}; | ||
/** | ||
* Load configuration data. | ||
* | ||
* @public | ||
*/ | ||
declare function loadConfig(options: LoadConfigOptions): Promise<AppConfig[]>; | ||
export { ConfigSchema, ConfigVisibility, LoadConfigOptions, loadConfig, loadConfigSchema, mergeConfigSchemas, readEnvConfig }; | ||
export { ConfigSchema, ConfigSchemaProcessingOptions, ConfigVisibility, EnvFunc, LoadConfigOptions, LoadConfigSchemaOptions, TransformFunc, loadConfig, loadConfigSchema, mergeConfigSchemas, readEnvConfig }; |
@@ -8,2 +8,3 @@ import yaml from 'yaml'; | ||
import { getProgramFromFiles, generateSchema } from 'typescript-json-schema'; | ||
import chokidar from 'chokidar'; | ||
@@ -300,3 +301,3 @@ const ENV_PREFIX = "APP_CONFIG_"; | ||
const req = typeof __non_webpack_require__ === "undefined" ? require : __non_webpack_require__; | ||
async function collectConfigSchemas(packageNames) { | ||
async function collectConfigSchemas(packageNames, packagePaths) { | ||
const visitedPackages = new Set(); | ||
@@ -306,14 +307,24 @@ const schemas = Array(); | ||
const currentDir = await fs.realpath(process.cwd()); | ||
async function processItem({name, parentPath}) { | ||
var _a, _b; | ||
if (visitedPackages.has(name)) { | ||
return; | ||
async function processItem(item) { | ||
var _a, _b, _c, _d; | ||
let pkgPath = item.packagePath; | ||
if (pkgPath) { | ||
const pkgExists = await fs.pathExists(pkgPath); | ||
if (!pkgExists) { | ||
return; | ||
} | ||
} else if (item.name) { | ||
const {name, parentPath} = item; | ||
if (visitedPackages.has(name)) { | ||
return; | ||
} | ||
visitedPackages.add(name); | ||
try { | ||
pkgPath = req.resolve(`${name}/package.json`, parentPath && { | ||
paths: [parentPath] | ||
}); | ||
} catch { | ||
} | ||
} | ||
visitedPackages.add(name); | ||
let pkgPath; | ||
try { | ||
pkgPath = req.resolve(`${name}/package.json`, parentPath && { | ||
paths: [parentPath] | ||
}); | ||
} catch { | ||
if (!pkgPath) { | ||
return; | ||
@@ -324,3 +335,5 @@ } | ||
...Object.keys((_a = pkg.dependencies) != null ? _a : {}), | ||
...Object.keys((_b = pkg.peerDependencies) != null ? _b : {}) | ||
...Object.keys((_b = pkg.devDependencies) != null ? _b : {}), | ||
...Object.keys((_c = pkg.optionalDependencies) != null ? _c : {}), | ||
...Object.keys((_d = pkg.peerDependencies) != null ? _d : {}) | ||
]; | ||
@@ -358,3 +371,6 @@ const hasSchema = "configSchema" in pkg; | ||
} | ||
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); | ||
@@ -456,5 +472,6 @@ return schemas.concat(tsSchemas); | ||
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 { | ||
@@ -500,4 +517,3 @@ const {serialized} = options; | ||
async function loadConfig(options) { | ||
const configs = []; | ||
const {configRoot, experimentalEnvFunc: envFunc} = options; | ||
const {configRoot, experimentalEnvFunc: envFunc, watch} = options; | ||
const configPaths = options.configPaths.slice(); | ||
@@ -512,3 +528,4 @@ if (configPaths.length === 0) { | ||
const env = envFunc != null ? envFunc : async (name) => process.env[name]; | ||
try { | ||
const loadConfigFiles = async () => { | ||
const configs = []; | ||
for (const configPath of configPaths) { | ||
@@ -528,7 +545,36 @@ if (!isAbsolute(configPath)) { | ||
} | ||
return configs; | ||
}; | ||
let fileConfigs; | ||
try { | ||
fileConfigs = await loadConfigFiles(); | ||
} catch (error) { | ||
throw new Error(`Failed to read static configuration file, ${error.message}`); | ||
} | ||
configs.push(...readEnvConfig(process.env)); | ||
return configs; | ||
const envConfigs = await readEnvConfig(process.env); | ||
if (watch) { | ||
let currentSerializedConfig = JSON.stringify(fileConfigs); | ||
const watcher = chokidar.watch(configPaths, { | ||
usePolling: process.env.NODE_ENV === "test" | ||
}); | ||
watcher.on("change", async () => { | ||
try { | ||
const newConfigs = await loadConfigFiles(); | ||
const newSerializedConfig = JSON.stringify(newConfigs); | ||
if (currentSerializedConfig === newSerializedConfig) { | ||
return; | ||
} | ||
currentSerializedConfig = newSerializedConfig; | ||
watch.onChange([...newConfigs, ...envConfigs]); | ||
} catch (error) { | ||
console.error(`Failed to reload configuration files, ${error}`); | ||
} | ||
}); | ||
if (watch.stopSignal) { | ||
watch.stopSignal.then(() => { | ||
watcher.close(); | ||
}); | ||
} | ||
} | ||
return [...fileConfigs, ...envConfigs]; | ||
} | ||
@@ -535,0 +581,0 @@ |
{ | ||
"name": "@backstage/config-loader", | ||
"description": "Config loading functionality used by Backstage backend, and CLI", | ||
"version": "0.0.0-nightly-20217521743", | ||
"version": "0.0.0-nightly-202182821947", | ||
"private": false, | ||
@@ -33,6 +33,7 @@ "publishConfig": { | ||
"dependencies": { | ||
"@backstage/cli-common": "^0.1.1", | ||
"@backstage/config": "^0.0.0-nightly-20217521743", | ||
"@backstage/cli-common": "^0.1.3", | ||
"@backstage/config": "^0.1.9", | ||
"@types/json-schema": "^7.0.6", | ||
"ajv": "^7.0.3", | ||
"chokidar": "^3.5.2", | ||
"fs-extra": "9.1.0", | ||
@@ -51,3 +52,3 @@ "json-schema": "^0.3.0", | ||
"@types/yup": "^0.29.8", | ||
"mock-fs": "^4.13.0" | ||
"mock-fs": "^5.1.0" | ||
}, | ||
@@ -54,0 +55,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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
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
151087
1263
11
10
+ Addedchokidar@^3.5.2
+ Added@backstage/config@0.1.15(transitive)
+ Added@backstage/types@0.1.3(transitive)
+ Addedanymatch@3.1.3(transitive)
+ Addedbinary-extensions@2.3.0(transitive)
+ Addedbraces@3.0.3(transitive)
+ Addedchokidar@3.6.0(transitive)
+ Addedfill-range@7.1.1(transitive)
+ Addedfsevents@2.3.3(transitive)
+ Addedglob-parent@5.1.2(transitive)
+ Addedis-binary-path@2.1.0(transitive)
+ Addedis-extglob@2.1.1(transitive)
+ Addedis-glob@4.0.3(transitive)
+ Addedis-number@7.0.0(transitive)
+ Addednormalize-path@3.0.0(transitive)
+ Addedpicomatch@2.3.1(transitive)
+ Addedreaddirp@3.6.0(transitive)
+ Addedto-regex-range@5.0.1(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)
- Removedms@2.1.3(transitive)
- Removedserialize-error@8.1.0(transitive)
- Removedtype-fest@0.20.2(transitive)
Updated@backstage/cli-common@^0.1.3
Updated@backstage/config@^0.1.9