diff --git a/.changeset/remove-portable-schema-deprecated-prop.md b/.changeset/remove-portable-schema-deprecated-prop.md new file mode 100644 index 0000000000..aea8426f65 --- /dev/null +++ b/.changeset/remove-portable-schema-deprecated-prop.md @@ -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. diff --git a/packages/frontend-plugin-api/report-alpha.api.md b/packages/frontend-plugin-api/report-alpha.api.md index 9531ec82c9..26cb4d1957 100644 --- a/packages/frontend-plugin-api/report-alpha.api.md +++ b/packages/frontend-plugin-api/report-alpha.api.md @@ -821,11 +821,8 @@ export type PluginWrapperDefinition = { // @public (undocumented) export type PortableSchema = { parse: (input: TInput) => TOutput; - schema: { - (): { - schema: JsonObject; - }; - [key: string]: any; + schema: () => { + schema: JsonObject; }; }; diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index a5e4110782..e908bc7184 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -2409,11 +2409,8 @@ export type PluginWrapperDefinition = { // @public (undocumented) export type PortableSchema = { parse: (input: TInput) => TOutput; - schema: { - (): { - schema: JsonObject; - }; - [key: string]: any; + schema: () => { + schema: JsonObject; }; }; diff --git a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts index 283ce66e8c..01527bd614 100644 --- a/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts +++ b/packages/frontend-plugin-api/src/blueprints/ApiBlueprint.test.ts @@ -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], diff --git a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx index 4efe16df12..0255d66604 100644 --- a/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/NavItemBlueprint.test.tsx @@ -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], diff --git a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx index cc043e6283..f891590dc0 100644 --- a/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/blueprints/PageBlueprint.test.tsx @@ -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], diff --git a/packages/frontend-plugin-api/src/schema/createPortableSchema.test.ts b/packages/frontend-plugin-api/src/schema/createPortableSchema.test.ts index c42298585b..312a17c68b 100644 --- a/packages/frontend-plugin-api/src/schema/createPortableSchema.test.ts +++ b/packages/frontend-plugin-api/src/schema/createPortableSchema.test.ts @@ -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(); }); }); diff --git a/packages/frontend-plugin-api/src/schema/createPortableSchema.ts b/packages/frontend-plugin-api/src/schema/createPortableSchema.ts index a6bb3340d7..ce7e05243a 100644 --- a/packages/frontend-plugin-api/src/schema/createPortableSchema.ts +++ b/packages/frontend-plugin-api/src/schema/createPortableSchema.ts @@ -143,27 +143,20 @@ function buildPortableSchema( return result as TOutput; } + let cached: { schema: JsonObject } | undefined; + const result: MergeablePortableSchema = { 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; + + Object.defineProperty(result, '_fields', { + value: fields, enumerable: false, }); diff --git a/packages/frontend-plugin-api/src/schema/types.ts b/packages/frontend-plugin-api/src/schema/types.ts index 54ad73ee96..a1c78513af 100644 --- a/packages/frontend-plugin-api/src/schema/types.ts +++ b/packages/frontend-plugin-api/src/schema/types.ts @@ -19,14 +19,5 @@ import { JsonObject } from '@backstage/types'; /** @public */ export type PortableSchema = { 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 }; }; diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx index 776c297fe0..8484ff1593 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx @@ -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], diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx index ff330473bf..3ed4362c8d 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx @@ -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], diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx index 467068c668..aefc199c51 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContextMenuItemBlueprint.test.tsx @@ -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], diff --git a/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx b/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx index fe3fd29fc3..78cfaa12bd 100644 --- a/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx +++ b/plugins/search-react/src/alpha/blueprints/SearchResultListItemBlueprint.test.tsx @@ -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],