diff --git a/.changeset/short-geese-double.md b/.changeset/short-geese-double.md index 45d3764e6d..1c63321be7 100644 --- a/.changeset/short-geese-double.md +++ b/.changeset/short-geese-double.md @@ -1,5 +1,5 @@ --- -'@backstage/cli': minor +'@backstage/cli': patch --- New config command to export the configuration schema. When running backstage-cli with yarn, consider using `yarn --silent backstage-cli config:schema` to get a clean output on `stdout`. diff --git a/packages/cli/src/commands/config/schema.ts b/packages/cli/src/commands/config/schema.ts index 17525ec48b..fbf46bff1a 100644 --- a/packages/cli/src/commands/config/schema.ts +++ b/packages/cli/src/commands/config/schema.ts @@ -29,13 +29,17 @@ export default async (cmd: Command) => { mockEnv: true, }); - const { schema: data } = mergeConfigSchemas( - schema.serialize().schemas as ConfigSchemaPackageEntry[], + const merged = mergeConfigSchemas( + schema.serialize().schemas.map(_ => _.value), ); + merged.title = 'Application Configuration Schema'; + merged.description = + 'This is the schema describing the structure of the app-config.yaml configuration file.'; + if (cmd.format === 'json') { - process.stdout.write(`${JSON.stringify(data, null, 2)}\n`); + process.stdout.write(`${JSON.stringify(merged, null, 2)}\n`); } else { - process.stdout.write(`${stringifyYaml(data)}\n`); + process.stdout.write(`${stringifyYaml(merged)}\n`); } }; diff --git a/packages/config-loader/src/index.ts b/packages/config-loader/src/index.ts index 4878e9d098..d9e5ae1350 100644 --- a/packages/config-loader/src/index.ts +++ b/packages/config-loader/src/index.ts @@ -15,10 +15,6 @@ */ export { readEnvConfig, loadConfigSchema, mergeConfigSchemas } from './lib'; -export type { - ConfigSchema, - ConfigSchemaPackageEntry, - ConfigVisibility, -} from './lib'; +export type { ConfigSchema, ConfigVisibility } from './lib'; export { loadConfig } from './loader'; export type { LoadConfigOptions } from './loader'; diff --git a/packages/config-loader/src/lib/schema/compile.ts b/packages/config-loader/src/lib/schema/compile.ts index 0ac1ea8045..e85b6023cc 100644 --- a/packages/config-loader/src/lib/schema/compile.ts +++ b/packages/config-loader/src/lib/schema/compile.ts @@ -35,48 +35,6 @@ import { export function compileConfigSchemas( schemas: ConfigSchemaPackageEntry[], ): ValidationFunc { - const { schema: merged, parser: ajv, visibilityByPath } = mergeConfigSchemas( - schemas, - ); - - const validate = ajv.compile(merged); - - return configs => { - const config = ConfigReader.fromConfigs(configs).get(); - - visibilityByPath.clear(); - - const valid = validate(config); - if (!valid) { - const errors = validate.errors ?? []; - 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(), - }; - } - - return { - visibilityByPath: new Map(visibilityByPath), - }; - }; -} - -/** - * Given a list of configuration schemas from packages, merge them - * into a single json schema. - */ -export function mergeConfigSchemas( - schemas: ConfigSchemaPackageEntry[], -): { - schema: JSONSchema; - parser: Ajv; - visibilityByPath: Map; -} { // The ajv instance below is stateful and doesn't really allow for additional // output during validation. We work around this by having this extra piece // of state that we reset before each validation. @@ -119,8 +77,41 @@ export function mergeConfigSchemas( } } + const merged = mergeConfigSchemas(schemas.map(_ => _.value)); + const validate = ajv.compile(merged); + + return configs => { + const config = ConfigReader.fromConfigs(configs).get(); + + visibilityByPath.clear(); + + const valid = validate(config); + if (!valid) { + const errors = validate.errors ?? []; + 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(), + }; + } + + return { + visibilityByPath: new Map(visibilityByPath), + }; + }; +} + +/** + * Given a list of configuration schemas from packages, merge them + * into a single json schema. + */ +export function mergeConfigSchemas(schemas: JSONSchema[]): JSONSchema { const merged = mergeAllOf( - { allOf: schemas.map(_ => _.value) }, + { allOf: schemas }, { // JSONSchema is typically subtractive, as in it always reduces the set of allowed // inputs through constraints. This changes the object property merging to be additive @@ -149,10 +140,5 @@ export function mergeConfigSchemas( } as Partial>, }, ); - - return { - schema: merged, - parser: ajv, - visibilityByPath, - }; + return merged; } diff --git a/packages/config-loader/src/lib/schema/index.ts b/packages/config-loader/src/lib/schema/index.ts index dc2e471a28..00bb5c7d10 100644 --- a/packages/config-loader/src/lib/schema/index.ts +++ b/packages/config-loader/src/lib/schema/index.ts @@ -16,8 +16,4 @@ export { mergeConfigSchemas } from './compile'; export { loadConfigSchema } from './load'; -export type { - ConfigSchema, - ConfigSchemaPackageEntry, - ConfigVisibility, -} from './types'; +export type { ConfigSchema, ConfigVisibility } from './types';