config-loader: add support for defining config schema using .d.ts files

This commit is contained in:
Patrik Oldsberg
2020-11-15 20:13:42 +01:00
parent 28a49c2ffa
commit 06205426d8
3 changed files with 79 additions and 15 deletions
+1
View File
@@ -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;
}