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:
@@ -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
@@ -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',
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user