diff --git a/.changeset/smart-beers-give.md b/.changeset/smart-beers-give.md new file mode 100644 index 0000000000..5b9d9888ba --- /dev/null +++ b/.changeset/smart-beers-give.md @@ -0,0 +1,6 @@ +--- +'@backstage/frontend-plugin-api': patch +'@backstage/frontend-test-utils': patch +--- + +Internal refactor diff --git a/packages/frontend-internal/.eslintrc.js b/packages/frontend-internal/.eslintrc.js new file mode 100644 index 0000000000..e487f765b2 --- /dev/null +++ b/packages/frontend-internal/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, +}); diff --git a/packages/frontend-internal/README.md b/packages/frontend-internal/README.md new file mode 100644 index 0000000000..72e54f21cf --- /dev/null +++ b/packages/frontend-internal/README.md @@ -0,0 +1,3 @@ +# @internal/frontend + +This is an internal package used by the other frontend packages. It does not get published to NPM, but instead inlined into consuming packages due to the `backstage.inline` flag in `package.json`. diff --git a/packages/frontend-internal/catalog-info.yaml b/packages/frontend-internal/catalog-info.yaml new file mode 100644 index 0000000000..630f53275a --- /dev/null +++ b/packages/frontend-internal/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: internal-frontend + title: '@internal/frontend' +spec: + lifecycle: experimental + type: backstage-web-library + owner: maintainers diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json new file mode 100644 index 0000000000..1e637b9d78 --- /dev/null +++ b/packages/frontend-internal/package.json @@ -0,0 +1,39 @@ +{ + "name": "@internal/frontend", + "version": "0.0.0", + "backstage": { + "role": "web-library", + "inline": true + }, + "private": true, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/frontend-internal" + }, + "license": "Apache-2.0", + "sideEffects": false, + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist" + ], + "scripts": { + "lint": "backstage-cli package lint", + "test": "backstage-cli package test" + }, + "dependencies": { + "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/types": "workspace:^", + "@backstage/version-bridge": "workspace:^", + "zod": "^3.22.4" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/frontend-app-api": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^15.0.0" + } +} diff --git a/packages/frontend-internal/src/index.ts b/packages/frontend-internal/src/index.ts new file mode 100644 index 0000000000..a5728f2ff6 --- /dev/null +++ b/packages/frontend-internal/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './wiring'; diff --git a/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts b/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts new file mode 100644 index 0000000000..840e45ff4a --- /dev/null +++ b/packages/frontend-internal/src/wiring/InternalExtensionDefinition.ts @@ -0,0 +1,104 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + AnyExtensionDataRef, + ApiHolder, + AppNode, + ExtensionDataValue, + ExtensionDefinition, + ExtensionDefinitionParameters, + ExtensionInput, + PortableSchema, + ResolvedExtensionInputs, +} from '@backstage/frontend-plugin-api'; + +export type InternalExtensionDefinition< + T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, +> = ExtensionDefinition & { + readonly kind?: string; + readonly namespace?: string; + readonly name?: string; + readonly attachTo: { id: string; input: string }; + readonly disabled: boolean; + readonly configSchema?: PortableSchema; +} & ( + | { + readonly version: 'v1'; + readonly inputs: { + [inputName in string]: { + $$type: '@backstage/ExtensionInput'; + extensionData: { + [name in string]: AnyExtensionDataRef; + }; + config: { optional: boolean; singleton: boolean }; + }; + }; + readonly output: { + [name in string]: AnyExtensionDataRef; + }; + factory(context: { + node: AppNode; + apis: ApiHolder; + config: object; + inputs: { + [inputName in string]: unknown; + }; + }): { + [inputName in string]: unknown; + }; + } + | { + readonly version: 'v2'; + readonly inputs: { + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }; + readonly output: Array; + factory(context: { + node: AppNode; + apis: ApiHolder; + config: object; + inputs: ResolvedExtensionInputs<{ + [inputName in string]: ExtensionInput< + AnyExtensionDataRef, + { optional: boolean; singleton: boolean } + >; + }>; + }): Iterable>; + } + ); + +/** @internal */ +export function toInternalExtensionDefinition< + T extends ExtensionDefinitionParameters, +>(overrides: ExtensionDefinition): InternalExtensionDefinition { + const internal = overrides as InternalExtensionDefinition; + if (internal.$$type !== '@backstage/ExtensionDefinition') { + throw new Error( + `Invalid extension definition instance, bad type '${internal.$$type}'`, + ); + } + const version = internal.version; + if (version !== 'v1' && version !== 'v2') { + throw new Error( + `Invalid extension definition instance, bad version '${version}'`, + ); + } + return internal; +} diff --git a/packages/frontend-internal/src/wiring/index.ts b/packages/frontend-internal/src/wiring/index.ts new file mode 100644 index 0000000000..d0aefbed64 --- /dev/null +++ b/packages/frontend-internal/src/wiring/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { + toInternalExtensionDefinition, + type InternalExtensionDefinition, +} from './InternalExtensionDefinition'; diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 445bbd8a68..4d7ebee521 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -15,7 +15,6 @@ */ import { ApiHolder, AppNode } from '../apis'; -import { PortableSchema } from '../schema'; import { Expand } from '../types'; import { ResolveInputValueOverrides, @@ -32,6 +31,7 @@ import { import { ExtensionInput } from './createExtensionInput'; import { z } from 'zod'; import { createSchemaFromZod } from '../schema/createSchemaFromZod'; +import { InternalExtensionDefinition } from '@internal/frontend'; /** * Convert a single extension input into a matching resolved input. @@ -235,84 +235,6 @@ export type ExtensionDefinition< }>; }; -/** @internal */ -export type InternalExtensionDefinition< - T extends ExtensionDefinitionParameters = ExtensionDefinitionParameters, -> = ExtensionDefinition & { - readonly kind?: string; - readonly namespace?: string; - readonly name?: string; - readonly attachTo: { id: string; input: string }; - readonly disabled: boolean; - readonly configSchema?: PortableSchema; -} & ( - | { - readonly version: 'v1'; - readonly inputs: { - [inputName in string]: { - $$type: '@backstage/ExtensionInput'; - extensionData: { - [name in string]: AnyExtensionDataRef; - }; - config: { optional: boolean; singleton: boolean }; - }; - }; - readonly output: { - [name in string]: AnyExtensionDataRef; - }; - factory(context: { - node: AppNode; - apis: ApiHolder; - config: object; - inputs: { - [inputName in string]: unknown; - }; - }): { - [inputName in string]: unknown; - }; - } - | { - readonly version: 'v2'; - readonly inputs: { - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }; - readonly output: Array; - factory(context: { - node: AppNode; - apis: ApiHolder; - config: object; - inputs: ResolvedExtensionInputs<{ - [inputName in string]: ExtensionInput< - AnyExtensionDataRef, - { optional: boolean; singleton: boolean } - >; - }>; - }): Iterable>; - } - ); - -/** @internal */ -export function toInternalExtensionDefinition< - T extends ExtensionDefinitionParameters, ->(overrides: ExtensionDefinition): InternalExtensionDefinition { - const internal = overrides as InternalExtensionDefinition; - if (internal.$$type !== '@backstage/ExtensionDefinition') { - throw new Error( - `Invalid extension definition instance, bad type '${internal.$$type}'`, - ); - } - const version = internal.version; - if (version !== 'v1' && version !== 'v2') { - throw new Error( - `Invalid extension definition instance, bad version '${version}'`, - ); - } - return internal; -} - /** @public */ export function createExtension< UOutput extends AnyExtensionDataRef, diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index fc77c63e97..47ef7a75b4 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -27,11 +27,9 @@ import { } from './createExtensionDataRef'; import { createExtensionInput } from './createExtensionInput'; import { RouteRef } from '../routing'; -import { - ExtensionDefinition, - toInternalExtensionDefinition, -} from './createExtension'; +import { ExtensionDefinition } from './createExtension'; import { createExtensionDataContainer } from './createExtensionDataContainer'; +import { toInternalExtensionDefinition } from '@internal/frontend'; function unused(..._any: any[]) {} diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts index aacc34ccf0..e3fa8b6682 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendModule.ts @@ -15,10 +15,10 @@ */ import { - ExtensionDefinition, InternalExtensionDefinition, toInternalExtensionDefinition, -} from './createExtension'; +} from '@internal/frontend'; +import { ExtensionDefinition } from './createExtension'; import { Extension, resolveExtensionDefinition, diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts index 03c843543d..adcd2b0867 100644 --- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts @@ -15,10 +15,10 @@ */ import { - ExtensionDefinition, InternalExtensionDefinition, toInternalExtensionDefinition, -} from './createExtension'; +} from '@internal/frontend'; +import { ExtensionDefinition } from './createExtension'; import { Extension, ResolveExtensionId, diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts index 870fb258f5..ce275406e7 100644 --- a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -19,7 +19,6 @@ import { ExtensionDefinition, ExtensionDefinitionParameters, ResolvedExtensionInputs, - toInternalExtensionDefinition, } from './createExtension'; import { PortableSchema } from '../schema'; import { ExtensionInput } from './createExtensionInput'; @@ -27,6 +26,7 @@ import { AnyExtensionDataRef, ExtensionDataValue, } from './createExtensionDataRef'; +import { toInternalExtensionDefinition } from '@internal/frontend'; /** @public */ export interface Extension { diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index a84d296c55..8ada3151bf 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -36,7 +36,9 @@ "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-app": "workspace:^", "@backstage/test-utils": "workspace:^", - "@backstage/types": "workspace:^" + "@backstage/types": "workspace:^", + "@backstage/version-bridge": "workspace:^", + "zod": "^3.22.4" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx index f9c1b3cb89..31ca9c86ce 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -29,8 +29,6 @@ import { JsonArray, JsonObject, JsonValue } from '@backstage/types'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/createExtension'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { resolveAppTree } from '../../../frontend-app-api/src/tree/resolveAppTree'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { resolveAppNodeSpecs } from '../../../frontend-app-api/src/tree/resolveAppNodeSpecs'; @@ -39,6 +37,7 @@ import { instantiateAppNodeTree } from '../../../frontend-app-api/src/tree/insta // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { readAppExtensionsConfig } from '../../../frontend-app-api/src/tree/readAppExtensionsConfig'; import { TestApiRegistry } from '@backstage/test-utils'; +import { toInternalExtensionDefinition } from '@internal/frontend'; /** @public */ export class ExtensionQuery { diff --git a/yarn.lock b/yarn.lock index dfc8cc25cd..bba0d759b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4522,8 +4522,10 @@ __metadata: "@backstage/plugin-app": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/version-bridge": "workspace:^" "@testing-library/jest-dom": ^6.0.0 "@types/react": "*" + zod: ^3.22.4 peerDependencies: "@testing-library/react": ^15.0.0 react: ^16.13.1 || ^17.0.0 || ^18.0.0 @@ -10002,6 +10004,23 @@ __metadata: languageName: node linkType: hard +"@internal/frontend@workspace:packages/frontend-internal": + version: 0.0.0-use.local + resolution: "@internal/frontend@workspace:packages/frontend-internal" + dependencies: + "@backstage/cli": "workspace:^" + "@backstage/frontend-app-api": "workspace:^" + "@backstage/frontend-plugin-api": "workspace:^" + "@backstage/frontend-test-utils": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@backstage/types": "workspace:^" + "@backstage/version-bridge": "workspace:^" + "@testing-library/jest-dom": ^6.0.0 + "@testing-library/react": ^15.0.0 + zod: ^3.22.4 + languageName: unknown + linkType: soft + "@internal/plugin-todo-list-backend@workspace:plugins/example-todo-list-backend": version: 0.0.0-use.local resolution: "@internal/plugin-todo-list-backend@workspace:plugins/example-todo-list-backend"