diff --git a/.changeset/bright-kids-raise.md b/.changeset/bright-kids-raise.md new file mode 100644 index 0000000000..e0d69e0c71 --- /dev/null +++ b/.changeset/bright-kids-raise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-todo-backend': patch +--- + +todo-backend is now exposed as a plugin which uses the new plugin system diff --git a/.changeset/new-waves-sing.md b/.changeset/new-waves-sing.md deleted file mode 100644 index 124a508bde..0000000000 --- a/.changeset/new-waves-sing.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-todo-backend': minor -'example-backend-next': patch ---- - -Expose the todos-backend plugin using the new system, add the new entrypoint for todos-backend into the experimental backend diff --git a/packages/backend-next/src/index.ts b/packages/backend-next/src/index.ts index a2f542afb1..f4979760cf 100644 --- a/packages/backend-next/src/index.ts +++ b/packages/backend-next/src/index.ts @@ -18,12 +18,12 @@ import { catalogPlugin } from '@backstage/plugin-catalog-backend'; import { catalogModuleTemplateKind } from '@backstage/plugin-scaffolder-backend'; import { createBackend } from '@backstage/backend-defaults'; import { appPlugin } from '@backstage/plugin-app-backend'; -import { todosPlugin } from '@backstage/plugin-todo-backend'; +import { todoPlugin } from '@backstage/plugin-todo-backend'; const backend = createBackend(); backend.add(catalogPlugin()); backend.add(catalogModuleTemplateKind()); backend.add(appPlugin({ appPackageName: 'example-app' })); -backend.add(todosPlugin()); +backend.add(todoPlugin()); backend.start(); diff --git a/plugins/todo-backend/api-report.md b/plugins/todo-backend/api-report.md index 7492865ee4..3908d2475b 100644 --- a/plugins/todo-backend/api-report.md +++ b/plugins/todo-backend/api-report.md @@ -8,9 +8,9 @@ import { CatalogApi } from '@backstage/catalog-client'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import express from 'express'; -import type { HttpRouterService } from '@backstage/backend-plugin-api'; import { Logger } from 'winston'; import { ScmIntegrations } from '@backstage/integration'; +import { ServiceRef } from '@backstage/backend-plugin-api'; import { UrlReader } from '@backstage/backend-common'; // @public (undocumented) @@ -90,6 +90,9 @@ export type TodoParserResult = { lineNumber: number; }; +// @alpha +export const todoPlugin: () => BackendFeature; + // @public (undocumented) export interface TodoReader { readTodos(options: ReadTodosOptions): Promise; @@ -136,6 +139,9 @@ export type TodoScmReaderOptions = { filePathFilter?: (filePath: string) => boolean; }; +// @alpha (undocumented) +export const todoScmReaderRef: ServiceRef; + // @public (undocumented) export interface TodoService { // (undocumented) @@ -146,13 +152,4 @@ export interface TodoService { }, ): Promise; } - -// @alpha (undocumented) -export const todosPlugin: () => BackendFeature; - -// @alpha (undocumented) -export type TodosPluginDependencies = { - todoReader: TodoService; - http: HttpRouterService; -}; ``` diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index 6a3b22aab4..51c3f8ede8 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/todo-backend/src/index.ts b/plugins/todo-backend/src/index.ts index 973c91df44..b1654327d6 100644 --- a/plugins/todo-backend/src/index.ts +++ b/plugins/todo-backend/src/index.ts @@ -22,3 +22,4 @@ export * from './lib'; export * from './service'; +export * from './plugin'; diff --git a/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.ts b/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.ts index 2696f0d54b..d364316ae1 100644 --- a/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.ts +++ b/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { loggerToWinstonLogger, UrlReader } from '@backstage/backend-common'; import { ScmIntegrations } from '@backstage/integration'; import { Logger } from 'winston'; @@ -28,6 +28,11 @@ import { import { Config } from '@backstage/config'; import { createTodoParser } from './createTodoParser'; import path from 'path'; +import { + coreServices, + createServiceFactory, + createServiceRef, +} from '@backstage/backend-plugin-api'; const excludedExtensions = [ '.png', @@ -167,3 +172,26 @@ export class TodoScmReader implements TodoReader { return { result: { items: todos }, etag: tree.etag }; } } + +/** + * @alpha + */ +export const todoScmReaderRef = createServiceRef({ + id: 'todoScmReaderFactory', + defaultFactory: async service => + createServiceFactory({ + service, + deps: { + reader: coreServices.urlReader, + config: coreServices.config, + logger: coreServices.logger, + }, + factory: async ({ reader, config, logger }) => { + const winstonLogger = loggerToWinstonLogger(logger); + return TodoScmReader.fromConfig(config, { + logger: winstonLogger, + reader, + }); + }, + }), +}); diff --git a/plugins/todo-backend/src/lib/TodoReader/index.ts b/plugins/todo-backend/src/lib/TodoReader/index.ts index d0537861da..a56d5805d5 100644 --- a/plugins/todo-backend/src/lib/TodoReader/index.ts +++ b/plugins/todo-backend/src/lib/TodoReader/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -export { TodoScmReader } from './TodoScmReader'; +export { TodoScmReader, todoScmReaderRef } from './TodoScmReader'; export type { TodoScmReaderOptions } from './TodoScmReader'; export { createTodoParser } from './createTodoParser'; export type { TodoParserOptions } from './createTodoParser'; diff --git a/plugins/todo-backend/src/plugin.ts b/plugins/todo-backend/src/plugin.ts index 49e8d354b1..cb25d3ddf0 100644 --- a/plugins/todo-backend/src/plugin.ts +++ b/plugins/todo-backend/src/plugin.ts @@ -18,23 +18,15 @@ import { createBackendPlugin, coreServices, } from '@backstage/backend-plugin-api'; -import { CompoundEntityRef, parseEntityRef } from '@backstage/catalog-model'; -import { InputError } from '@backstage/errors'; -import express, { Router } from 'express'; -import { - getBearerToken, - parseFilterParam, - parseIntegerParam, - parseOrderByParam, -} from './lib/utils'; + import { todoReaderServiceRef } from './service/TodoReaderService'; -import { TODO_FIELDS } from './service/types'; +import { createRouter } from './service/router'; /** * The Todos plugin is responsible for aggregating todo comments within source. * @alpha */ -export const todosPlugin = createBackendPlugin({ +export const todoPlugin = createBackendPlugin({ pluginId: 'todo-backend', register(env) { env.registerInit({ @@ -43,43 +35,11 @@ export const todosPlugin = createBackendPlugin({ http: coreServices.httpRouter, }, async init({ http, todoReader }) { - const router = Router(); - router.use(express.json()); - - router.get('/v1/todos', async (req, res) => { - const offset = parseIntegerParam(req.query.offset, 'offset query'); - const limit = parseIntegerParam(req.query.limit, 'limit query'); - const orderBy = parseOrderByParam(req.query.orderBy, TODO_FIELDS); - const filters = parseFilterParam(req.query.filter, TODO_FIELDS); - - const entityRef = req.query.entity; - if (entityRef && typeof entityRef !== 'string') { - throw new InputError(`entity query must be a string`); - } - let entity: CompoundEntityRef | undefined = undefined; - if (entityRef) { - try { - entity = parseEntityRef(entityRef); - } catch (error) { - throw new InputError(`Invalid entity ref, ${error}`); - } - } - - const todos = await todoReader.listTodos( - { - entity, - offset, - limit, - orderBy, - filters, - }, - { - token: getBearerToken(req.headers.authorization), - }, - ); - res.json(todos); - }); - http.use(router); + http.use( + await createRouter({ + todoService: todoReader, + }), + ); }, }); }, diff --git a/plugins/todo-backend/src/service/TodoReaderService.ts b/plugins/todo-backend/src/service/TodoReaderService.ts index 93908c0f9f..b3ed793d38 100644 --- a/plugins/todo-backend/src/service/TodoReaderService.ts +++ b/plugins/todo-backend/src/service/TodoReaderService.ts @@ -20,15 +20,15 @@ import { getEntitySourceLocation, stringifyEntityRef, } from '@backstage/catalog-model'; -import { TodoReader, TodoScmReader } from '../lib'; -import { ListTodosRequest, ListTodosResponse, TodoService } from './types'; import { - coreServices, createServiceFactory, createServiceRef, + ServiceRef, } from '@backstage/backend-plugin-api'; import { catalogServiceRef } from '@backstage/plugin-catalog-node'; -import { loggerToWinstonLogger } from '@backstage/backend-common'; + +import { TodoReader, todoScmReaderRef } from '../lib'; +import { ListTodosRequest, ListTodosResponse, TodoService } from './types'; const DEFAULT_DEFAULT_PAGE_SIZE = 10; const DEFAULT_MAX_PAGE_SIZE = 50; @@ -138,28 +138,22 @@ export class TodoReaderService implements TodoService { } } -export const todoReaderServiceRef = createServiceRef({ - id: 'todo-client', - defaultFactory: async service => - createServiceFactory({ - service, - deps: { - catalogApi: catalogServiceRef, - logger: coreServices.logger, - reader: coreServices.urlReader, - config: coreServices.config, - }, - async factory({ catalogApi, logger, reader, config }) { - const winstonLogger = loggerToWinstonLogger(logger); - const todoReader = TodoScmReader.fromConfig(config, { - reader: reader, - logger: winstonLogger, - }); - return new TodoReaderService({ - catalogClient: catalogApi, - todoReader, - // TODO: override defaults here - }); - }, - }), -}); +export const todoReaderServiceRef: ServiceRef = + createServiceRef({ + id: 'todo.client', + defaultFactory: async service => + createServiceFactory({ + service, + deps: { + catalogApi: catalogServiceRef, + todoReader: todoScmReaderRef, + }, + async factory({ catalogApi, todoReader }) { + const todoReaderService = new TodoReaderService({ + catalogClient: catalogApi, + todoReader, + }); + return todoReaderService; + }, + }), + }); diff --git a/plugins/todo-backend/src/service/index.ts b/plugins/todo-backend/src/service/index.ts index 89a6939cda..380d95dbe1 100644 --- a/plugins/todo-backend/src/service/index.ts +++ b/plugins/todo-backend/src/service/index.ts @@ -16,12 +16,6 @@ export { createRouter } from './router'; export type { RouterOptions } from './router'; -export type { - TodoService, - ListTodosRequest, - ListTodosResponse, - TodosPluginDependencies, -} from './types'; +export type { TodoService, ListTodosRequest, ListTodosResponse } from './types'; export { TodoReaderService } from './TodoReaderService'; export type { TodoReaderServiceOptions } from './TodoReaderService'; -export { todosPlugin } from './plugin'; diff --git a/plugins/todo-backend/src/service/plugin.ts b/plugins/todo-backend/src/service/plugin.ts deleted file mode 100644 index 6c5fd3bf6c..0000000000 --- a/plugins/todo-backend/src/service/plugin.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2023 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 { - createBackendPlugin, - coreServices, -} from '@backstage/backend-plugin-api'; -import { CompoundEntityRef, parseEntityRef } from '@backstage/catalog-model'; -import { InputError } from '@backstage/errors'; -import express, { Router } from 'express'; -import { - getBearerToken, - parseFilterParam, - parseIntegerParam, - parseOrderByParam, -} from '../lib/utils'; -import { todoReaderServiceRef } from './TodoReaderService'; -import { TODO_FIELDS } from './types'; -import type { TodosPluginDependencies } from './types'; - -const todosPluginInit = async (params: TodosPluginDependencies) => { - const { http, todoReader } = params; - const router = Router(); - router.use(express.json()); - - router.get('/v1/todos', async (req, res) => { - const offset = parseIntegerParam(req.query.offset, 'offset query'); - const limit = parseIntegerParam(req.query.limit, 'limit query'); - const orderBy = parseOrderByParam(req.query.orderBy, TODO_FIELDS); - const filters = parseFilterParam(req.query.filter, TODO_FIELDS); - - const entityRef = req.query.entity; - if (entityRef && typeof entityRef !== 'string') { - throw new InputError(`entity query must be a string`); - } - let entity: CompoundEntityRef | undefined = undefined; - if (entityRef) { - try { - entity = parseEntityRef(entityRef); - } catch (error) { - throw new InputError(`Invalid entity ref, ${error}`); - } - } - - const todos = await todoReader.listTodos( - { - entity, - offset, - limit, - orderBy, - filters, - }, - { - token: getBearerToken(req.headers.authorization), - }, - ); - res.json(todos); - }); - http.use(router); -}; - -/** - * @alpha - */ -export const todosPlugin = createBackendPlugin({ - pluginId: 'todo-backend', - register(env) { - env.registerInit({ - deps: { - todoReader: todoReaderServiceRef, - http: coreServices.httpRouter, - }, - init: todosPluginInit, - }); - }, -});