config-loader: add support for defining config schema using .d.ts files
This commit is contained in:
@@ -38,6 +38,7 @@
|
||||
"fs-extra": "^9.0.0",
|
||||
"json-schema": "^0.2.5",
|
||||
"json-schema-merge-allof": "^0.7.0",
|
||||
"typescript-json-schema": "^0.43.0",
|
||||
"yaml": "^1.9.2",
|
||||
"yup": "^0.29.3"
|
||||
},
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
dirname,
|
||||
} from 'path';
|
||||
import { ConfigSchemaPackageEntry } from './types';
|
||||
import { getProgramFromFiles, generateSchema } from 'typescript-json-schema';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
|
||||
type Item = {
|
||||
name: string;
|
||||
@@ -40,6 +42,7 @@ export async function collectConfigSchemas(
|
||||
): Promise<ConfigSchemaPackageEntry[]> {
|
||||
const visitedPackages = new Set<string>();
|
||||
const schemas = Array<ConfigSchemaPackageEntry>();
|
||||
const tsSchemaPaths = Array<string>();
|
||||
const currentDir = process.cwd();
|
||||
|
||||
async function processItem({ name, parentPath }: Item) {
|
||||
@@ -83,18 +86,23 @@ export async function collectConfigSchemas(
|
||||
}
|
||||
if (hasSchema) {
|
||||
if (typeof pkg.configSchema === 'string') {
|
||||
if (!pkg.configSchema.endsWith('.json')) {
|
||||
const isJson = pkg.configSchema.endsWith('.json');
|
||||
const isDts = pkg.configSchema.endsWith('.d.ts');
|
||||
if (!isJson && !isDts) {
|
||||
throw new Error(
|
||||
`Config schema files must be .json, got ${pkg.configSchema}`,
|
||||
`Config schema files must be .json or .d.ts, got ${pkg.configSchema}`,
|
||||
);
|
||||
}
|
||||
const value = await fs.readJson(
|
||||
resolvePath(dirname(pkgPath), pkg.configSchema),
|
||||
);
|
||||
schemas.push({
|
||||
value,
|
||||
path: relativePath(currentDir, pkgPath),
|
||||
});
|
||||
if (isDts) {
|
||||
tsSchemaPaths.push(resolvePath(dirname(pkgPath), pkg.configSchema));
|
||||
} else {
|
||||
const path = resolvePath(dirname(pkgPath), pkg.configSchema);
|
||||
const value = await fs.readJson(path);
|
||||
schemas.push({
|
||||
value,
|
||||
path: relativePath(currentDir, path),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
schemas.push({
|
||||
value: pkg.configSchema,
|
||||
@@ -110,5 +118,49 @@ export async function collectConfigSchemas(
|
||||
|
||||
await Promise.all(packageNames.map(name => processItem({ name })));
|
||||
|
||||
return schemas;
|
||||
const tsSchemas = compileTsSchemas(currentDir, tsSchemaPaths);
|
||||
|
||||
return schemas.concat(tsSchemas);
|
||||
}
|
||||
|
||||
// This handles the support of TypeScript .d.ts config schema declarations.
|
||||
// We collect all typescript schema definition and compile them all in one go.
|
||||
// This is much faster than compiling them separately.
|
||||
function compileTsSchemas(currentDir: string, paths: string[]) {
|
||||
if (paths.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const program = getProgramFromFiles(paths, {
|
||||
incremental: false,
|
||||
isolatedModules: true,
|
||||
lib: ['ES5'], // Skipping most libs speeds processing up a lot, we just need the primitive types anyway
|
||||
noEmit: true,
|
||||
noResolve: true,
|
||||
skipLibCheck: true, // Skipping lib checks speeds things up
|
||||
skipDefaultLibCheck: true,
|
||||
strict: true,
|
||||
typeRoots: [], // Do not include any additional types
|
||||
types: [],
|
||||
});
|
||||
|
||||
const tsSchemas = paths.map(schemaFile => {
|
||||
const value = generateSchema(
|
||||
program,
|
||||
// All schemas should export a `Config` symbol
|
||||
'Config',
|
||||
// This enables usage of @visibility is doc comments
|
||||
{ validationKeywords: ['visibility'] },
|
||||
[schemaFile],
|
||||
) as JsonObject | null;
|
||||
|
||||
const path = relativePath(currentDir, schemaFile);
|
||||
|
||||
if (!value) {
|
||||
throw new Error(`Invalid schema in ${path}, missing Config export`);
|
||||
}
|
||||
return { path, value };
|
||||
});
|
||||
|
||||
return tsSchemas;
|
||||
}
|
||||
|
||||
@@ -12478,7 +12478,7 @@ glob-to-regexp@^0.3.0:
|
||||
resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
|
||||
integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
|
||||
|
||||
glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.6:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||
@@ -23239,10 +23239,21 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5"
|
||||
integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==
|
||||
typescript-json-schema@^0.43.0:
|
||||
version "0.43.0"
|
||||
resolved "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.43.0.tgz#8bd9c832f1f15f006ff933907ce192222fdfd92f"
|
||||
integrity sha512-4c9IMlIlHYJiQtzL1gh2nIPJEjBgJjDUs50gsnnc+GFyDSK1oFM3uQIBSVosiuA/4t6LSAXDS9vTdqbQC6EcgA==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.5"
|
||||
glob "~7.1.6"
|
||||
json-stable-stringify "^1.0.1"
|
||||
typescript "~4.0.2"
|
||||
yargs "^15.4.1"
|
||||
|
||||
typescript@^4.0.3, typescript@~4.0.2:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz#ae9dddfd1069f1cb5beb3ef3b2170dd7c1332389"
|
||||
integrity sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==
|
||||
|
||||
ua-parser-js@^0.7.18:
|
||||
version "0.7.21"
|
||||
|
||||
Reference in New Issue
Block a user