Merge pull request #28253 from backstage/blam/configfi

fix: Omit collecting `backend-common` `config.d.ts` if `backend-defaults` is detected
This commit is contained in:
Ben Lambert
2024-12-20 14:32:31 +01:00
committed by GitHub
6 changed files with 150 additions and 21 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/config-loader': patch
'@backstage/backend-defaults': patch
---
Exclude `@backstage/backend-common` from schema collection if `@backstage/backend-defaults` is present
+13
View File
@@ -86,6 +86,19 @@ export interface Config {
* Options used by the default auth, httpAuth and userInfo services.
*/
auth?: {
/**
* Keys shared by all backends for signing and validating backend tokens.
* @deprecated this will be removed when the backwards compatibility is no longer needed with backend-common
*/
keys?: {
/**
* Secret for generating tokens. Should be a base64 string, recommended
* length is 24 bytes.
*
* @visibility secret
*/
secret: string;
}[];
/**
* This disables the otherwise default auth policy, which requires all
* requests to be authenticated with either user or service credentials.
@@ -68,6 +68,7 @@ describe('collectConfigSchemas', () => {
{
path: path.join('node_modules', 'a', 'package.json'),
value: mockSchema,
packageName: 'a',
},
]);
});
@@ -89,6 +90,62 @@ describe('collectConfigSchemas', () => {
{
path: path.join('root', 'package.json'),
value: mockSchema,
packageName: 'root',
},
]);
});
it('should not include schemas for backend-common if theres a backend-defaults package', async () => {
mockDir.setContent({
root: {
'package.json': JSON.stringify({
name: 'root',
dependencies: {
'@backstage/backend-common': '1',
'@backstage/backend-defaults': '1',
},
configSchema: { ...mockSchema, title: 'root' },
}),
},
node_modules: {
'@backstage': {
'backend-common': {
'package.json': JSON.stringify({
name: '@backstage/backend-common',
version: '1',
configSchema: { ...mockSchema, title: 'backend-common' },
}),
},
'backend-defaults': {
'package.json': JSON.stringify({
name: '@backstage/backend-defaults',
version: '1',
configSchema: { ...mockSchema, title: 'backend-defaults' },
}),
},
},
},
});
process.chdir(mockDir.path);
await expect(
collectConfigSchemas(['root'], [path.join('root', 'package.json')]),
).resolves.toEqual([
{
path: path.join('root', 'package.json'),
value: { ...mockSchema, title: 'root' },
packageName: 'root',
},
{
path: path.join(
'node_modules',
'@backstage',
'backend-defaults',
'package.json',
),
value: { ...mockSchema, title: 'backend-defaults' },
packageName: '@backstage/backend-defaults',
},
]);
});
@@ -159,18 +216,22 @@ describe('collectConfigSchemas', () => {
{
path: path.join('node_modules', 'b', 'package.json'),
value: { ...mockSchema, title: 'b' },
packageName: 'b',
},
{
path: path.join('node_modules', 'c1', 'package.json'),
value: { ...mockSchema, title: 'c1' },
packageName: 'c1',
},
{
path: path.join('node_modules', 'd1', 'package.json'),
value: { ...mockSchema, title: 'd1' },
packageName: 'd1',
},
{
path: path.join('root', 'package.json'),
value: { ...mockSchema, title: 'root' },
packageName: 'root',
},
]),
);
@@ -213,10 +274,12 @@ describe('collectConfigSchemas', () => {
{
path: path.join('node_modules', 'a', 'package.json'),
value: { ...mockSchema, title: 'inline' },
packageName: 'a',
},
{
path: path.join('node_modules', 'b', 'schema.json'),
value: { ...mockSchema, title: 'external' },
packageName: 'b',
},
{
path: path.join('node_modules', 'c', 'schema.d.ts'),
@@ -231,6 +294,7 @@ describe('collectConfigSchemas', () => {
},
required: ['tsKey'],
},
packageName: 'c',
},
]),
);
@@ -284,14 +348,17 @@ describe('collectConfigSchemas', () => {
{
path: path.join('node_modules', 'a', 'package.json'),
value: mockSchema,
packageName: 'a',
},
{
path: path.join('node_modules', 'b', 'package.json'),
value: { ...mockSchema, title: 'b' },
packageName: 'b',
},
{
path: path.join('node_modules', 'c', 'package.json'),
value: { ...mockSchema, title: 'c1' },
packageName: 'c',
},
{
path: path.join(
@@ -302,6 +369,7 @@ describe('collectConfigSchemas', () => {
'package.json',
),
value: { ...mockSchema, title: 'c2' },
packageName: 'c',
},
]),
);
+43 -21
View File
@@ -45,7 +45,7 @@ export async function collectConfigSchemas(
packagePaths: string[],
): Promise<ConfigSchemaPackageEntry[]> {
const schemas = new Array<ConfigSchemaPackageEntry>();
const tsSchemaPaths = new Array<string>();
const tsSchemaPaths = new Array<{ packageName: string; path: string }>();
const visitedPackageVersions = new Map<string, Set<string>>(); // pkgName: [versions...]
const currentDir = await fs.realpath(process.cwd());
@@ -115,22 +115,25 @@ export async function collectConfigSchemas(
);
}
if (isDts) {
tsSchemaPaths.push(
relativePath(
tsSchemaPaths.push({
path: relativePath(
currentDir,
resolvePath(dirname(pkgPath), pkg.configSchema),
),
);
packageName: pkg.name,
});
} else {
const path = resolvePath(dirname(pkgPath), pkg.configSchema);
const value = await fs.readJson(path);
schemas.push({
packageName: pkg.name,
value,
path: relativePath(currentDir, path),
});
}
} else {
schemas.push({
packageName: pkg.name,
value: pkg.configSchema,
path: relativePath(currentDir, pkgPath),
});
@@ -150,15 +153,31 @@ export async function collectConfigSchemas(
]);
const tsSchemas = await compileTsSchemas(tsSchemaPaths);
const allSchemas = schemas.concat(tsSchemas);
return schemas.concat(tsSchemas);
const hasBackendDefaults = allSchemas.some(
({ packageName }) => packageName === '@backstage/backend-defaults',
);
if (hasBackendDefaults) {
// We filter out backend-common schemas here to avoid issues with
// schema merging over different versions of the same schema.
// led to issues such as https://github.com/backstage/backstage/issues/28170
return allSchemas.filter(
({ packageName }) => packageName !== '@backstage/backend-common',
);
}
return allSchemas;
}
// 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.
async function compileTsSchemas(paths: string[]) {
if (paths.length === 0) {
async function compileTsSchemas(
entries: { path: string; packageName: string }[],
) {
if (entries.length === 0) {
return [];
}
@@ -168,20 +187,23 @@ async function compileTsSchemas(paths: string[]) {
'typescript-json-schema'
);
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 program = getProgramFromFiles(
entries.map(({ path }) => path),
{
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(path => {
const tsSchemas = entries.map(({ path, packageName }) => {
let value;
try {
const generator = buildGenerator(
@@ -228,7 +250,7 @@ async function compileTsSchemas(paths: string[]) {
if (!value) {
throw new Error(`Invalid schema in ${path}, missing Config export`);
}
return { path, value };
return { path, value, packageName };
});
return tsSchemas;
@@ -21,10 +21,12 @@ describe('compileConfigSchemas', () => {
const validate = compileConfigSchemas([
{
path: 'a',
packageName: 'a',
value: { type: 'object', properties: { a: { type: 'string' } } },
},
{
path: 'b',
packageName: 'b',
value: { type: 'object', properties: { b: { type: 'number' } } },
},
]);
@@ -64,6 +66,7 @@ describe('compileConfigSchemas', () => {
const validate = compileConfigSchemas([
{
path: 'a1',
packageName: 'a1',
value: {
type: 'object',
properties: {
@@ -80,6 +83,7 @@ describe('compileConfigSchemas', () => {
},
{
path: 'a2',
packageName: 'a2',
value: {
type: 'object',
properties: {
@@ -126,6 +130,7 @@ describe('compileConfigSchemas', () => {
compileConfigSchemas([
{
path: 'a1',
packageName: 'a1',
value: {
type: 'object',
properties: { a: { type: 'string', visibility: 'frontend' } },
@@ -133,6 +138,7 @@ describe('compileConfigSchemas', () => {
},
{
path: 'a2',
packageName: 'a2',
value: {
type: 'object',
properties: { a: { type: 'string', visibility: 'secret' } },
@@ -148,6 +154,7 @@ describe('compileConfigSchemas', () => {
const validate = compileConfigSchemas([
{
path: 'a1',
packageName: 'a1',
value: {
type: 'object',
properties: {
@@ -179,6 +186,7 @@ describe('compileConfigSchemas', () => {
const validate = compileConfigSchemas([
{
path: 'a1',
packageName: 'a1',
value: {
type: 'object',
properties: {
@@ -242,6 +250,7 @@ describe('deepVisibility', () => {
const validate = compileConfigSchemas([
{
path: 'a1',
packageName: 'a1',
value: {
type: 'object',
properties: {
@@ -257,6 +266,7 @@ describe('deepVisibility', () => {
},
{
path: 'a2',
packageName: 'a2',
value: {
type: 'object',
deepVisibility: 'secret',
@@ -305,6 +315,7 @@ describe('deepVisibility', () => {
compileConfigSchemas([
{
path: 'a1',
packageName: 'a1',
value: {
type: 'object',
properties: {
@@ -320,6 +331,7 @@ describe('deepVisibility', () => {
},
{
path: 'a2',
packageName: 'a2',
value: {
type: 'object',
deepVisibility: 'secret',
@@ -346,6 +358,7 @@ describe('deepVisibility', () => {
compileConfigSchemas([
{
path: 'a2',
packageName: 'a2',
value: {
type: 'object',
deepVisibility: 'secret',
@@ -376,6 +389,7 @@ describe('deepVisibility', () => {
compileConfigSchemas([
{
path: 'a2',
packageName: 'a2',
value: {
type: 'object',
properties: {
@@ -398,6 +412,7 @@ describe('deepVisibility', () => {
compileConfigSchemas([
{
path: 'a1',
packageName: 'a1',
value: {
type: 'object',
properties: {
@@ -418,6 +433,7 @@ describe('deepVisibility', () => {
},
{
path: 'a2',
packageName: 'a2',
value: {
type: 'object',
deepVisibility: 'secret',
@@ -29,6 +29,10 @@ export type ConfigSchemaPackageEntry = {
* The relative path that the configuration schema was discovered at.
*/
path: string;
/**
* The package name for the package this belongs to
*/
packageName: string;
};
/**