Merge pull request #33930 from backstage/remove-portable-schema-deprecated-prop

frontend-plugin-api: remove deprecated PortableSchema .schema property
This commit is contained in:
Patrik Oldsberg
2026-04-16 21:51:39 +02:00
committed by GitHub
13 changed files with 29 additions and 117 deletions
@@ -0,0 +1,5 @@
---
'@backstage/frontend-plugin-api': minor
---
**BREAKING**: Removed the deprecated property form of `PortableSchema.schema`. The `schema` member is now a plain method that must be called as `schema()` — direct property access like `schema.type` or `schema.properties` is no longer supported.
@@ -821,11 +821,8 @@ export type PluginWrapperDefinition<TValue = unknown | never> = {
// @public (undocumented)
export type PortableSchema<TOutput = unknown, TInput = TOutput> = {
parse: (input: TInput) => TOutput;
schema: {
(): {
schema: JsonObject;
};
[key: string]: any;
schema: () => {
schema: JsonObject;
};
};
+2 -5
View File
@@ -2409,11 +2409,8 @@ export type PluginWrapperDefinition<TValue = unknown | never> = {
// @public (undocumented)
export type PortableSchema<TOutput = unknown, TInput = TOutput> = {
parse: (input: TInput) => TOutput;
schema: {
(): {
schema: JsonObject;
};
[key: string]: any;
schema: () => {
schema: JsonObject;
};
};
@@ -182,14 +182,8 @@ describe('ApiBlueprint', () => {
"input": "apis",
},
"configSchema": {
"_fields": {
"test": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
},
"parse": [Function],
"schema": [Function],
},
"disabled": false,
"factory": [Function],
@@ -39,14 +39,8 @@ describe('NavItemBlueprint', () => {
"input": "items",
},
"configSchema": {
"_fields": {
"title": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
},
"parse": [Function],
"schema": [Function],
},
"disabled": false,
"factory": [Function],
@@ -48,19 +48,8 @@ describe('PageBlueprint', () => {
"input": "routes",
},
"configSchema": {
"_fields": {
"path": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
"title": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
},
"parse": [Function],
"schema": [Function],
},
"disabled": false,
"factory": [Function],
@@ -143,13 +143,14 @@ describe('createConfigSchema', () => {
});
});
it('should support backward-compatible property access on schema', () => {
it('should return the schema as a method', () => {
const schema = createConfigSchema({
title: zodV4.string(),
});
expect(schema.schema.type).toBe('object');
expect(schema.schema.properties).toBeDefined();
const result = schema.schema();
expect(result.schema.type).toBe('object');
expect(result.schema.properties).toBeDefined();
});
});
@@ -143,27 +143,20 @@ function buildPortableSchema<TOutput = unknown>(
return result as TOutput;
}
let cached: { schema: JsonObject } | undefined;
const result: MergeablePortableSchema<TOutput> = {
parse,
schema: undefined as any,
_fields: fields,
};
// Lazy getter — computes JSON Schema on first access, then caches it.
let cached: PortableSchema['schema'] | undefined;
Object.defineProperty(result, 'schema', {
get() {
schema() {
if (!cached) {
const jsonSchema = buildObjectJsonSchema(fields);
const callable = Object.assign(
() => ({ schema: jsonSchema }),
jsonSchema,
);
cached = callable as PortableSchema['schema'];
cached = { schema: buildObjectJsonSchema(fields) };
}
return cached;
},
configurable: true,
} as MergeablePortableSchema<TOutput>;
Object.defineProperty(result, '_fields', {
value: fields,
enumerable: false,
});
@@ -19,14 +19,5 @@ import { JsonObject } from '@backstage/types';
/** @public */
export type PortableSchema<TOutput = unknown, TInput = TOutput> = {
parse: (input: TInput) => TOutput;
/**
* The JSON Schema for this portable schema.
*
* @remarks
* Can be accessed as a property for backward compatibility (returns the
* JSON Schema object directly), or called as a method which returns
* `{ schema: JsonObject }`. Both forms compute the schema lazily on
* first access. The property form is deprecated prefer `schema()`.
*/
schema: { (): { schema: JsonObject }; [key: string]: any };
schema: () => { schema: JsonObject };
};
@@ -45,19 +45,8 @@ describe('EntityCardBlueprint', () => {
"input": "cards",
},
"configSchema": {
"_fields": {
"filter": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
"type": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
},
"parse": [Function],
"schema": [Function],
},
"disabled": false,
"factory": [Function],
@@ -47,34 +47,8 @@ describe('EntityContentBlueprint', () => {
"input": "contents",
},
"configSchema": {
"_fields": {
"filter": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
"group": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
"icon": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
"path": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
"title": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
},
"parse": [Function],
"schema": [Function],
},
"disabled": false,
"factory": [Function],
@@ -63,14 +63,8 @@ describe('EntityContextMenuItemBlueprint', () => {
"input": "contextMenuItems",
},
"configSchema": {
"_fields": {
"filter": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
},
"parse": [Function],
"schema": [Function],
},
"disabled": false,
"factory": [Function],
@@ -45,14 +45,8 @@ describe('SearchResultListItemBlueprint', () => {
"input": "items",
},
"configSchema": {
"_fields": {
"noTrack": {
"required": false,
"toJsonSchema": [Function],
"validate": [Function],
},
},
"parse": [Function],
"schema": [Function],
},
"disabled": false,
"factory": [Function],