From 8517c9b90ba1f27cfbff590d93bb212d27266195 Mon Sep 17 00:00:00 2001 From: dswbx Date: Thu, 12 Jun 2025 19:58:18 +0200 Subject: [PATCH] added a few initial plugins --- app/__test__/App.spec.ts | 4 +++ app/build.ts | 1 + app/package.json | 22 ++++++++++++ app/src/App.ts | 2 ++ app/src/adapter/cloudflare/index.ts | 4 --- app/src/data/entities/EntityTypescript.ts | 2 +- app/src/modules/server/SystemController.ts | 1 + .../cloudflare}/image-optimization.plugin.ts | 6 ++-- app/src/plugins/dev/show-routes.plugin.ts | 18 ++++++++++ app/src/plugins/dev/sync-config.plugin.ts | 35 +++++++++++++++++++ app/src/plugins/dev/sync-types.plugin.ts | 31 ++++++++++++++++ app/src/plugins/index.ts | 7 ++++ 12 files changed, 125 insertions(+), 8 deletions(-) rename app/src/{adapter/cloudflare/plugins => plugins/cloudflare}/image-optimization.plugin.ts (95%) create mode 100644 app/src/plugins/dev/show-routes.plugin.ts create mode 100644 app/src/plugins/dev/sync-config.plugin.ts create mode 100644 app/src/plugins/dev/sync-types.plugin.ts create mode 100644 app/src/plugins/index.ts diff --git a/app/__test__/App.spec.ts b/app/__test__/App.spec.ts index 93c4f6f..2eb070f 100644 --- a/app/__test__/App.spec.ts +++ b/app/__test__/App.spec.ts @@ -51,6 +51,9 @@ describe("App tests", async () => { }, ); }, + onBoot: async () => { + called.push("onBoot"); + }, beforeBuild: async () => { called.push("beforeBuild"); }, @@ -90,6 +93,7 @@ describe("App tests", async () => { }, ]); expect(called).toEqual([ + "onBoot", "onServerInit", "beforeBuild", "onServerInit", diff --git a/app/build.ts b/app/build.ts index 698778b..44f704a 100644 --- a/app/build.ts +++ b/app/build.ts @@ -78,6 +78,7 @@ async function buildApi() { "src/core/utils/index.ts", "src/data/index.ts", "src/media/index.ts", + "src/plugins/index.ts", ], outDir: "dist", external: [...external], diff --git a/app/package.json b/app/package.json index 9ca0d89..a7e9050 100644 --- a/app/package.json +++ b/app/package.json @@ -183,6 +183,11 @@ "import": "./dist/media/index.js", "require": "./dist/media/index.js" }, + "./plugins": { + "types": "./dist/types/plugins/index.d.ts", + "import": "./dist/plugins/index.js", + "require": "./dist/plugins/index.js" + }, "./adapter/cloudflare": { "types": "./dist/types/adapter/cloudflare/index.d.ts", "import": "./dist/adapter/cloudflare/index.js", @@ -231,6 +236,23 @@ "./dist/styles.css": "./dist/ui/styles.css", "./dist/manifest.json": "./dist/static/.vite/manifest.json" }, + "typesVersions": { + "*": { + "data": ["./dist/types/data/index.d.ts"], + "core": ["./dist/types/core/index.d.ts"], + "utils": ["./dist/types/core/utils/index.d.ts"], + "cli": ["./dist/types/cli/index.d.ts"], + "media": ["./dist/types/media/index.d.ts"], + "plugins": ["./dist/types/plugins/index.d.ts"], + "adapter": ["./dist/types/adapter/index.d.ts"], + "adapter/cloudflare": ["./dist/types/adapter/cloudflare/index.d.ts"], + "adapter/vite": ["./dist/types/adapter/vite/index.d.ts"], + "adapter/nextjs": ["./dist/types/adapter/nextjs/index.d.ts"], + "adapter/react-router": ["./dist/types/adapter/react-router/index.d.ts"], + "adapter/bun": ["./dist/types/adapter/bun/index.d.ts"], + "adapter/node": ["./dist/types/adapter/node/index.d.ts"] + } + }, "publishConfig": { "access": "public" }, diff --git a/app/src/App.ts b/app/src/App.ts index ea72feb..628bfc3 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -27,6 +27,7 @@ export type AppPluginConfig = { onBuilt?: () => MaybePromise; onServerInit?: (server: Hono) => MaybePromise; onFirstBoot?: () => MaybePromise; + onBoot?: () => MaybePromise; }; export type AppPlugin = (app: App) => AppPluginConfig; @@ -93,6 +94,7 @@ export class App { private options?: AppOptions, ) { this.plugins = (options?.plugins ?? []).map((plugin) => plugin(this)); + this.runPlugins("onBoot"); this.modules = new ModuleManager(connection, { ...(options?.manager ?? {}), initial: _initialConfig, diff --git a/app/src/adapter/cloudflare/index.ts b/app/src/adapter/cloudflare/index.ts index 57daed1..60e6a77 100644 --- a/app/src/adapter/cloudflare/index.ts +++ b/app/src/adapter/cloudflare/index.ts @@ -1,5 +1,4 @@ import { D1Connection, type D1ConnectionConfig } from "./connection/D1Connection"; -import { ImageOptimizationPlugin } from "./plugins/image-optimization.plugin"; export * from "./cloudflare-workers.adapter"; export { makeApp, getFresh } from "./modes/fresh"; @@ -14,9 +13,6 @@ export { type BindingMap, } from "./bindings"; export { constants } from "./config"; -export const plugins = { - imageOptimization: ImageOptimizationPlugin, -}; export function d1(config: D1ConnectionConfig) { return new D1Connection(config); diff --git a/app/src/data/entities/EntityTypescript.ts b/app/src/data/entities/EntityTypescript.ts index 26c1df9..b0aa89e 100644 --- a/app/src/data/entities/EntityTypescript.ts +++ b/app/src/data/entities/EntityTypescript.ts @@ -56,7 +56,7 @@ export class EntityTypescript { return this.em.entities.map((e) => e.toTypes()); } - protected getTab(count = 1) { + getTab(count = 1) { return this.options.indentChar.repeat(this.options.indentWidth).repeat(count); } diff --git a/app/src/modules/server/SystemController.ts b/app/src/modules/server/SystemController.ts index f457e47..4793381 100644 --- a/app/src/modules/server/SystemController.ts +++ b/app/src/modules/server/SystemController.ts @@ -317,6 +317,7 @@ export class SystemController extends Controller { local: datetimeStringLocal(), utc: datetimeStringUTC(), }, + plugins: this.app.plugins.map((p) => p.name), }), ); diff --git a/app/src/adapter/cloudflare/plugins/image-optimization.plugin.ts b/app/src/plugins/cloudflare/image-optimization.plugin.ts similarity index 95% rename from app/src/adapter/cloudflare/plugins/image-optimization.plugin.ts rename to app/src/plugins/cloudflare/image-optimization.plugin.ts index db9130a..f023977 100644 --- a/app/src/adapter/cloudflare/plugins/image-optimization.plugin.ts +++ b/app/src/plugins/cloudflare/image-optimization.plugin.ts @@ -1,18 +1,18 @@ import type { App, AppPlugin } from "bknd"; -export type ImageOptimizationPluginOptions = { +export type CloudflareImageOptimizationOptions = { accessUrl?: string; resolvePath?: string; autoFormat?: boolean; devBypass?: string; }; -export function ImageOptimizationPlugin({ +export function cloudflareImageOptimization({ accessUrl = "/_plugin/image/optimize", resolvePath = "/api/media/file", autoFormat = true, devBypass, -}: ImageOptimizationPluginOptions = {}): AppPlugin { +}: CloudflareImageOptimizationOptions = {}): AppPlugin { const disallowedAccessUrls = ["/api", "/admin", "/_optimize"]; if (disallowedAccessUrls.includes(accessUrl) || accessUrl.length < 2) { throw new Error(`Disallowed accessUrl: ${accessUrl}`); diff --git a/app/src/plugins/dev/show-routes.plugin.ts b/app/src/plugins/dev/show-routes.plugin.ts new file mode 100644 index 0000000..dcb75bf --- /dev/null +++ b/app/src/plugins/dev/show-routes.plugin.ts @@ -0,0 +1,18 @@ +import type { App, AppPlugin } from "bknd"; +import { showRoutes as showRoutesHono } from "hono/dev"; + +export type ShowRoutesOptions = { + once?: boolean; +}; + +export function showRoutes({ once = false }: ShowRoutesOptions = {}): AppPlugin { + let shown = false; + return (app: App) => ({ + name: "bknd-show-routes", + onBuilt: () => { + if (once && shown) return; + shown = true; + showRoutesHono(app.server); + }, + }); +} diff --git a/app/src/plugins/dev/sync-config.plugin.ts b/app/src/plugins/dev/sync-config.plugin.ts new file mode 100644 index 0000000..24d84d3 --- /dev/null +++ b/app/src/plugins/dev/sync-config.plugin.ts @@ -0,0 +1,35 @@ +import { App, type AppConfig, type AppPlugin } from "bknd"; + +export type SyncConfigOptions = { + enabled?: boolean; + includeSecrets?: boolean; + write: (config: AppConfig) => Promise; +}; + +export function syncConfig({ + enabled = true, + includeSecrets = false, + write, +}: SyncConfigOptions): AppPlugin { + let firstBoot = true; + return (app: App) => ({ + name: "bknd-sync-config", + onBuilt: async () => { + if (!enabled) return; + app.emgr.onEvent( + App.Events.AppConfigUpdatedEvent, + async () => { + await write?.(app.toJSON(includeSecrets)); + }, + { + id: "sync-config", + }, + ); + + if (firstBoot) { + firstBoot = false; + await write?.(app.toJSON(true)); + } + }, + }); +} diff --git a/app/src/plugins/dev/sync-types.plugin.ts b/app/src/plugins/dev/sync-types.plugin.ts new file mode 100644 index 0000000..484f8b6 --- /dev/null +++ b/app/src/plugins/dev/sync-types.plugin.ts @@ -0,0 +1,31 @@ +import { App, type AppPlugin } from "bknd"; +import { EntityTypescript } from "data/entities/EntityTypescript"; + +export type SyncTypesOptions = { + enabled?: boolean; + write: (et: EntityTypescript) => Promise; +}; + +export function syncTypes({ enabled = true, write }: SyncTypesOptions): AppPlugin { + let firstBoot = true; + return (app: App) => ({ + name: "bknd-sync-types", + onBuilt: async () => { + if (!enabled) return; + app.emgr.onEvent( + App.Events.AppConfigUpdatedEvent, + async () => { + await write?.(new EntityTypescript(app.em)); + }, + { + id: "sync-types", + }, + ); + + if (firstBoot) { + firstBoot = false; + await write?.(new EntityTypescript(app.em)); + } + }, + }); +} diff --git a/app/src/plugins/index.ts b/app/src/plugins/index.ts new file mode 100644 index 0000000..ee7a31a --- /dev/null +++ b/app/src/plugins/index.ts @@ -0,0 +1,7 @@ +export { + cloudflareImageOptimization, + type CloudflareImageOptimizationOptions, +} from "./cloudflare/image-optimization.plugin"; +export { showRoutes, type ShowRoutesOptions } from "./dev/show-routes.plugin"; +export { syncConfig, type SyncConfigOptions } from "./dev/sync-config.plugin"; +export { syncTypes, type SyncTypesOptions } from "./dev/sync-types.plugin";