From 571ff04b0cacb74bb8da9402b327793266567e67 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 26 Sep 2022 16:22:38 +0200 Subject: [PATCH] app-backend: initial port to new backend system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Johan Haals Signed-off-by: Patrik Oldsberg --- plugins/app-backend/api-report.md | 12 ++ plugins/app-backend/package.json | 10 +- plugins/app-backend/src/index.ts | 2 + .../app-backend/src/service/appPlugin.test.ts | 81 +++++++++++++ plugins/app-backend/src/service/appPlugin.ts | 107 ++++++++++++++++++ yarn.lock | 11 ++ 6 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 plugins/app-backend/src/service/appPlugin.test.ts create mode 100644 plugins/app-backend/src/service/appPlugin.ts diff --git a/plugins/app-backend/api-report.md b/plugins/app-backend/api-report.md index 0383129823..cfeebc6cd9 100644 --- a/plugins/app-backend/api-report.md +++ b/plugins/app-backend/api-report.md @@ -3,11 +3,23 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import express from 'express'; import { Logger } from 'winston'; import { PluginDatabaseManager } from '@backstage/backend-common'; +// @alpha +export const appPlugin: (options: AppPluginOptions) => BackendFeature; + +// @alpha (undocumented) +export type AppPluginOptions = { + appPackageName: string; + staticFallbackHandler?: express.Handler; + disableConfigInjection?: boolean; + disableStaticFallbackCache?: boolean; +}; + // @public (undocumented) export function createRouter(options: RouterOptions): Promise; diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index ffd40b89c0..d79ab46b12 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -8,7 +8,8 @@ "publishConfig": { "access": "public", "main": "dist/index.cjs.js", - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" }, "backstage": { "role": "backend-plugin" @@ -24,7 +25,7 @@ ], "scripts": { "start": "backstage-cli package start", - "build": "backstage-cli package build", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", @@ -33,6 +34,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/config-loader": "workspace:^", "@backstage/types": "workspace:^", @@ -49,16 +51,20 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-app-api": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/types": "workspace:^", "@types/supertest": "^2.0.8", + "get-port": "^6.1.2", "mock-fs": "^5.1.0", "msw": "^0.47.0", + "node-fetch": "^2.6.7", "supertest": "^6.1.3" }, "files": [ "dist", + "alpha", "migrations/**/*.{js,d.ts}", "static" ] diff --git a/plugins/app-backend/src/index.ts b/plugins/app-backend/src/index.ts index c91416eac1..167dc041fc 100644 --- a/plugins/app-backend/src/index.ts +++ b/plugins/app-backend/src/index.ts @@ -21,3 +21,5 @@ */ export * from './service/router'; +export { appPlugin } from './service/appPlugin'; +export type { AppPluginOptions } from './service/appPlugin'; diff --git a/plugins/app-backend/src/service/appPlugin.test.ts b/plugins/app-backend/src/service/appPlugin.test.ts new file mode 100644 index 0000000000..03ed4e8845 --- /dev/null +++ b/plugins/app-backend/src/service/appPlugin.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2022 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 mockFs from 'mock-fs'; +import { resolve as resolvePath } from 'path'; +import fetch from 'node-fetch'; +import { configServiceRef } from '@backstage/backend-plugin-api'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { appPlugin } from './appPlugin'; +import { + databaseFactory, + httpRouterFactory, + loggerFactory, + rootLoggerFactory, +} from '@backstage/backend-app-api'; +import { ConfigReader } from '@backstage/config'; +import getPort from 'get-port'; + +describe('appPlugin', () => { + beforeAll(() => { + mockFs({ + [resolvePath(process.cwd(), 'node_modules/app')]: { + 'package.json': '{}', + dist: { + static: {}, + 'index.html': 'winning', + }, + }, + }); + }); + + afterAll(() => { + mockFs.restore(); + }); + + it('boots', async () => { + const port = await getPort(); + await startTestBackend({ + services: [ + [ + configServiceRef, + new ConfigReader({ + backend: { + listen: { port }, + database: { client: 'better-sqlite3', connection: ':memory:' }, + }, + }), + ], + loggerFactory(), + rootLoggerFactory(), + databaseFactory(), + httpRouterFactory(), + ], + features: [ + appPlugin({ + appPackageName: 'app', + disableStaticFallbackCache: true, + }), + ], + }); + + await expect( + fetch(`http://localhost:${port}/api/app/derp.html`).then(res => + res.text(), + ), + ).resolves.toBe('winning'); + }); +}); diff --git a/plugins/app-backend/src/service/appPlugin.ts b/plugins/app-backend/src/service/appPlugin.ts new file mode 100644 index 0000000000..185279f0f0 --- /dev/null +++ b/plugins/app-backend/src/service/appPlugin.ts @@ -0,0 +1,107 @@ +/* + * Copyright 2022 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 express from 'express'; +import { + configServiceRef, + createBackendPlugin, + databaseServiceRef, + loggerServiceRef, + loggerToWinstonLogger, + httpRouterServiceRef, +} from '@backstage/backend-plugin-api'; +import { createRouter } from './router'; + +/** @alpha */ +export type AppPluginOptions = { + /** + * The name of the app package (in most Backstage repositories, this is the + * "name" field in `packages/app/package.json`) that content should be served + * from. The same app package should be added as a dependency to the backend + * package in order for it to be accessible at runtime. + * + * In a typical setup with a single app package, this would be set to 'app'. + */ + appPackageName: string; + + /** + * A request handler to handle requests for static content that are not present in the app bundle. + * + * This can be used to avoid issues with clients on older deployment versions trying to access lazy + * loaded content that is no longer present. Typically the requests would fall back to a long-term + * object store where all recently deployed versions of the app are present. + * + * Another option is to provide a `database` that will take care of storing the static assets instead. + * + * If both `database` and `staticFallbackHandler` are provided, the `database` will attempt to serve + * static assets first, and if they are not found, the `staticFallbackHandler` will be called. + */ + staticFallbackHandler?: express.Handler; + + /** + * Disables the configuration injection. This can be useful if you're running in an environment + * with a read-only filesystem, or for some other reason don't want configuration to be injected. + * + * Note that this will cause the configuration used when building the app bundle to be used, unless + * a separate configuration loading strategy is set up. + * + * This also disables configuration injection though `APP_CONFIG_` environment variables. + */ + disableConfigInjection?: boolean; + + /** + * By default the app backend plugin will cache previously deployed static assets in the database. + * If you disable this, it is recommended to set a `staticFallbackHandler` instead. + */ + disableStaticFallbackCache?: boolean; +}; + +/** + * The App plugin is responsible for serving the frontend app bundle and static assets. + * @alpha + */ +export const appPlugin = createBackendPlugin({ + id: 'app', + register(env, options: AppPluginOptions) { + env.registerInit({ + deps: { + logger: loggerServiceRef, + config: configServiceRef, + database: databaseServiceRef, + httpRouter: httpRouterServiceRef, + }, + async init({ logger, config, database, httpRouter }) { + const { + appPackageName, + staticFallbackHandler, + disableConfigInjection, + disableStaticFallbackCache, + } = options; + const winstonLogger = loggerToWinstonLogger(logger); + + const router = await createRouter({ + logger: winstonLogger, + config, + database: disableStaticFallbackCache ? undefined : database, + appPackageName, + staticFallbackHandler, + disableConfigInjection, + }); + httpRouter.use(router); + }, + }); + }, +}); diff --git a/yarn.lock b/yarn.lock index 0b643f5711..6ef0daf878 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3932,7 +3932,9 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-app-backend@workspace:plugins/app-backend" dependencies: + "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -3943,6 +3945,7 @@ __metadata: express: ^4.17.1 express-promise-router: ^4.1.0 fs-extra: 10.1.0 + get-port: ^6.1.2 globby: ^11.0.0 helmet: ^6.0.0 knex: ^2.0.0 @@ -3950,6 +3953,7 @@ __metadata: luxon: ^3.0.0 mock-fs: ^5.1.0 msw: ^0.47.0 + node-fetch: ^2.6.7 supertest: ^6.1.3 winston: ^3.2.1 yn: ^4.0.0 @@ -23132,6 +23136,13 @@ __metadata: languageName: node linkType: hard +"get-port@npm:^6.1.2": + version: 6.1.2 + resolution: "get-port@npm:6.1.2" + checksum: e3c3d591492a11393455ef220f24c812a28f7da56ec3e4a2512d931a1f196d42850b50ac6138349a44622eda6dc3c0ccd8495cd91376d968e2d9e6f6f849e0a9 + languageName: node + linkType: hard + "get-stdin@npm:^8.0.0": version: 8.0.0 resolution: "get-stdin@npm:8.0.0"