config-loader: allow schemas to be collected by package path as well
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/config-loader': patch
|
||||
---
|
||||
|
||||
Add option to collect configuration schemas from explicit package paths in addition to by package name.
|
||||
@@ -53,6 +53,7 @@ export function loadConfigSchema(
|
||||
export type LoadConfigSchemaOptions =
|
||||
| {
|
||||
dependencies: string[];
|
||||
packagePaths?: string[];
|
||||
}
|
||||
| {
|
||||
serialized: JsonObject;
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('collectConfigSchemas', () => {
|
||||
}),
|
||||
});
|
||||
|
||||
await expect(collectConfigSchemas([])).resolves.toEqual([]);
|
||||
await expect(collectConfigSchemas([], [])).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it('should find schema in a local package', async () => {
|
||||
@@ -64,7 +64,7 @@ describe('collectConfigSchemas', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await expect(collectConfigSchemas(['a'])).resolves.toEqual([
|
||||
await expect(collectConfigSchemas(['a'], [])).resolves.toEqual([
|
||||
{
|
||||
path: path.join('node_modules', 'a', 'package.json'),
|
||||
value: mockSchema,
|
||||
@@ -72,8 +72,34 @@ describe('collectConfigSchemas', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should find schema in transitive dependencies', async () => {
|
||||
it('should find schema at explicit package path', async () => {
|
||||
mockFs({
|
||||
root: {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'root',
|
||||
configSchema: mockSchema,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
collectConfigSchemas([], [path.join('root', 'package.json')]),
|
||||
).resolves.toEqual([
|
||||
{
|
||||
path: path.join('root', 'package.json'),
|
||||
value: mockSchema,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should find schema in transitive dependencies and explicit path', async () => {
|
||||
mockFs({
|
||||
root: {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'root',
|
||||
configSchema: { ...mockSchema, title: 'root' },
|
||||
}),
|
||||
},
|
||||
node_modules: {
|
||||
a: {
|
||||
'package.json': JSON.stringify({
|
||||
@@ -124,20 +150,28 @@ describe('collectConfigSchemas', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await expect(collectConfigSchemas(['a'])).resolves.toEqual([
|
||||
{
|
||||
path: path.join('node_modules', 'b', 'package.json'),
|
||||
value: { ...mockSchema, title: 'b' },
|
||||
},
|
||||
{
|
||||
path: path.join('node_modules', 'c1', 'package.json'),
|
||||
value: { ...mockSchema, title: 'c1' },
|
||||
},
|
||||
{
|
||||
path: path.join('node_modules', 'd1', 'package.json'),
|
||||
value: { ...mockSchema, title: 'd1' },
|
||||
},
|
||||
]);
|
||||
await expect(
|
||||
collectConfigSchemas(['a'], [path.join('root', 'package.json')]),
|
||||
).resolves.toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
path: path.join('node_modules', 'b', 'package.json'),
|
||||
value: { ...mockSchema, title: 'b' },
|
||||
},
|
||||
{
|
||||
path: path.join('node_modules', 'c1', 'package.json'),
|
||||
value: { ...mockSchema, title: 'c1' },
|
||||
},
|
||||
{
|
||||
path: path.join('node_modules', 'd1', 'package.json'),
|
||||
value: { ...mockSchema, title: 'd1' },
|
||||
},
|
||||
{
|
||||
path: path.join('root', 'package.json'),
|
||||
value: { ...mockSchema, title: 'root' },
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should schema of different types', async () => {
|
||||
@@ -171,7 +205,7 @@ describe('collectConfigSchemas', () => {
|
||||
[typescriptModuleDir]: (mockFs as any).load(typescriptModuleDir),
|
||||
});
|
||||
|
||||
await expect(collectConfigSchemas(['a', 'b', 'c'])).resolves.toEqual([
|
||||
await expect(collectConfigSchemas(['a', 'b', 'c'], [])).resolves.toEqual([
|
||||
{
|
||||
path: path.join('node_modules', 'a', 'package.json'),
|
||||
value: { ...mockSchema, title: 'inline' },
|
||||
@@ -210,7 +244,7 @@ describe('collectConfigSchemas', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await expect(collectConfigSchemas(['a'])).rejects.toThrow(
|
||||
await expect(collectConfigSchemas(['a'], [])).rejects.toThrow(
|
||||
'Config schema files must be .json or .d.ts, got schema.yaml',
|
||||
);
|
||||
});
|
||||
@@ -230,7 +264,7 @@ describe('collectConfigSchemas', () => {
|
||||
[typescriptModuleDir]: (mockFs as any).load(typescriptModuleDir),
|
||||
});
|
||||
|
||||
await expect(collectConfigSchemas(['a'])).rejects.toThrow(
|
||||
await expect(collectConfigSchemas(['a'], [])).rejects.toThrow(
|
||||
`Invalid schema in ${path.join(
|
||||
'node_modules',
|
||||
'a',
|
||||
|
||||
@@ -26,8 +26,9 @@ import { getProgramFromFiles, generateSchema } from 'typescript-json-schema';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
|
||||
type Item = {
|
||||
name: string;
|
||||
name?: string;
|
||||
parentPath?: string;
|
||||
packagePath?: string;
|
||||
};
|
||||
|
||||
const req =
|
||||
@@ -40,34 +41,47 @@ const req =
|
||||
*/
|
||||
export async function collectConfigSchemas(
|
||||
packageNames: string[],
|
||||
packagePaths: string[],
|
||||
): Promise<ConfigSchemaPackageEntry[]> {
|
||||
const visitedPackages = new Set<string>();
|
||||
const schemas = Array<ConfigSchemaPackageEntry>();
|
||||
const tsSchemaPaths = Array<string>();
|
||||
const currentDir = await fs.realpath(process.cwd());
|
||||
|
||||
async function processItem({ name, parentPath }: Item) {
|
||||
// Ensures that we only process each package once. We don't bother with
|
||||
// loading in schemas from duplicates of different versions, as that's not
|
||||
// supported by Backstage right now anyway. We may want to change that in
|
||||
// the future though, if it for example becomes possible to load in two
|
||||
// different versions of e.g. @backstage/core at once.
|
||||
if (visitedPackages.has(name)) {
|
||||
return;
|
||||
}
|
||||
visitedPackages.add(name);
|
||||
async function processItem(item: Item) {
|
||||
let pkgPath = item.packagePath;
|
||||
|
||||
let pkgPath: string;
|
||||
try {
|
||||
pkgPath = req.resolve(
|
||||
`${name}/package.json`,
|
||||
parentPath && {
|
||||
paths: [parentPath],
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
// We can somewhat safely ignore packages that don't export package.json,
|
||||
// as they are likely not part of the Backstage ecosystem anyway.
|
||||
if (pkgPath) {
|
||||
const pkgExists = await fs.pathExists(pkgPath);
|
||||
if (!pkgExists) {
|
||||
return;
|
||||
}
|
||||
} else if (item.name) {
|
||||
const { name, parentPath } = item;
|
||||
|
||||
// Ensures that we only process each package once. We don't bother with
|
||||
// loading in schemas from duplicates of different versions, as that's not
|
||||
// supported by Backstage right now anyway. We may want to change that in
|
||||
// the future though, if it for example becomes possible to load in two
|
||||
// different versions of e.g. @backstage/core at once.
|
||||
if (visitedPackages.has(name)) {
|
||||
return;
|
||||
}
|
||||
visitedPackages.add(name);
|
||||
|
||||
try {
|
||||
pkgPath = req.resolve(
|
||||
`${name}/package.json`,
|
||||
parentPath && {
|
||||
paths: [parentPath],
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
// We can somewhat safely ignore packages that don't export package.json,
|
||||
// as they are likely not part of the Backstage ecosystem anyway.
|
||||
}
|
||||
}
|
||||
if (!pkgPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -126,9 +140,10 @@ export async function collectConfigSchemas(
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
export type LoadConfigSchemaOptions =
|
||||
| {
|
||||
dependencies: string[];
|
||||
packagePaths?: string[];
|
||||
}
|
||||
| {
|
||||
serialized: JsonObject;
|
||||
@@ -44,7 +45,10 @@ export async function loadConfigSchema(
|
||||
let schemas: ConfigSchemaPackageEntry[];
|
||||
|
||||
if ('dependencies' in options) {
|
||||
schemas = await collectConfigSchemas(options.dependencies);
|
||||
schemas = await collectConfigSchemas(
|
||||
options.dependencies,
|
||||
options.packagePaths ?? [],
|
||||
);
|
||||
} else {
|
||||
const { serialized } = options;
|
||||
if (serialized?.backstageConfigSchemaVersion !== 1) {
|
||||
|
||||
Reference in New Issue
Block a user