From 3694511383e6c79aa4747afa73cec4b3afd6a28a Mon Sep 17 00:00:00 2001 From: aXenDeveloper Date: Wed, 14 May 2025 20:46:13 +0200 Subject: [PATCH 1/2] refactor: Split config to api and global, change buildPlugin to buildApiPlugin --- apps/docs/content/docs/plugins/blog.mdx | 5 +++ apps/docs/content/docs/plugins/create.mdx | 22 ++++++++++++ apps/docs/content/docs/plugins/index.mdx | 20 +---------- apps/docs/content/docs/plugins/meta.json | 2 +- apps/web/src/app/api/[...route]/route.ts | 36 ++----------------- ...esitant_sumo.sql => 0000_funny_korath.sql} | 3 +- .../migrations/meta/0000_snapshot.json | 13 ++----- .../database/migrations/meta/_journal.json | 4 +-- apps/web/src/database/schema/languages.ts | 3 +- apps/web/src/vitnode.api.config.ts | 32 +++++++++++++++++ apps/web/src/vitnode.config.ts | 3 +- packages/vitnode/src/api/config.ts | 25 ++++++++----- packages/vitnode/src/{ => api}/lib/plugin.ts | 8 ++--- .../src/api/middlewares/global/global.ts | 20 ++--------- packages/vitnode/src/api/plugin.ts | 4 +-- .../vitnode/src/database/schema/languages.ts | 3 +- packages/vitnode/src/vitnode.config.ts | 24 +++++++++++-- plugins/blog/src/plugin.config.ts | 6 ++-- 18 files changed, 123 insertions(+), 110 deletions(-) create mode 100644 apps/docs/content/docs/plugins/blog.mdx create mode 100644 apps/docs/content/docs/plugins/create.mdx rename apps/web/src/database/migrations/{0000_hesitant_sumo.sql => 0000_funny_korath.sql} (99%) create mode 100644 apps/web/src/vitnode.api.config.ts rename packages/vitnode/src/{ => api}/lib/plugin.ts (65%) diff --git a/apps/docs/content/docs/plugins/blog.mdx b/apps/docs/content/docs/plugins/blog.mdx new file mode 100644 index 000000000..f0eae6fa2 --- /dev/null +++ b/apps/docs/content/docs/plugins/blog.mdx @@ -0,0 +1,5 @@ +--- +title: Blog +description: xx +icon: House +--- diff --git a/apps/docs/content/docs/plugins/create.mdx b/apps/docs/content/docs/plugins/create.mdx new file mode 100644 index 000000000..5581e2e04 --- /dev/null +++ b/apps/docs/content/docs/plugins/create.mdx @@ -0,0 +1,22 @@ +--- +title: Get Started +description: xx +--- + +import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; + + + +```bash tab="pnpm" +pnpm create vitnode-app@canary --plugin +``` + +```bash tab="npm" +npx create-vitnode-app@canary --plugin +``` + +```bash tab="bun" +bun create vitnode-app@canary --plugin +``` + + diff --git a/apps/docs/content/docs/plugins/index.mdx b/apps/docs/content/docs/plugins/index.mdx index e5191490a..bef0d8b9a 100644 --- a/apps/docs/content/docs/plugins/index.mdx +++ b/apps/docs/content/docs/plugins/index.mdx @@ -4,22 +4,4 @@ description: Create awesome plugins for VitNode. icon: House --- -## Create a Plugin - -import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; - - - -```bash tab="pnpm" -pnpm create vitnode-app@canary --plugin -``` - -```bash tab="npm" -npx create-vitnode-app@canary --plugin -``` - -```bash tab="bun" -bun create vitnode-app@canary --plugin -``` - - +test diff --git a/apps/docs/content/docs/plugins/meta.json b/apps/docs/content/docs/plugins/meta.json index 930368824..824a519c3 100644 --- a/apps/docs/content/docs/plugins/meta.json +++ b/apps/docs/content/docs/plugins/meta.json @@ -3,5 +3,5 @@ "description": "Create and share own plugins", "icon": "Plug", "root": true, - "pages": ["index", "..."] + "pages": ["index", "---Plugins---", "blog", "---Custom Plugin---", "..."] } diff --git a/apps/web/src/app/api/[...route]/route.ts b/apps/web/src/app/api/[...route]/route.ts index 29085f900..267ff5d37 100644 --- a/apps/web/src/app/api/[...route]/route.ts +++ b/apps/web/src/app/api/[...route]/route.ts @@ -1,44 +1,14 @@ +import { vitNodeApiConfig } from '@/vitnode.api.config'; import { vitNodeConfig } from '@/vitnode.config'; import { OpenAPIHono } from '@hono/zod-openapi'; import { handle } from 'hono/vercel'; import { VitNodeAPI } from 'vitnode/api/config'; -import { NodemailerEmailPlugin } from 'vitnode/api/plugins/email/nodemailer'; -// import { ResendEmailPlugin } from 'vitnode/api/plugins/email/resend'; -import { DiscordSSOApiPlugin } from 'vitnode/api/plugins/sso/discord'; -import { FacebookSSOApiPlugin } from 'vitnode/api/plugins/sso/facebook'; -import { GoogleSSOApiPlugin } from 'vitnode/api/plugins/sso/google'; const app = new OpenAPIHono().basePath('/api'); VitNodeAPI({ app, - plugins: vitNodeConfig.plugins, - metadata: vitNodeConfig.metadata, - // emailProvider: ResendEmailPlugin({ - // apiKey: process.env.RESEND_API_KEY ?? '', - // from: process.env.RESEND_FROM ?? '', - // }), - emailProvider: NodemailerEmailPlugin({ - from: process.env.NODE_MAILER_FROM, - host: process.env.NODE_MAILER_HOST, - password: process.env.NODE_MAILER_PASSWORD, - user: process.env.NOD_EMAILER_USER, - }), - authorization: { - ssoPlugins: [ - DiscordSSOApiPlugin({ - clientId: process.env.DISCORD_CLIENT_ID, - clientSecret: process.env.DISCORD_CLIENT_SECRET, - }), - GoogleSSOApiPlugin({ - clientId: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - }), - FacebookSSOApiPlugin({ - clientId: process.env.FACEBOOK_CLIENT_ID, - clientSecret: process.env.FACEBOOK_CLIENT_SECRET, - }), - ], - }, + vitNodeApiConfig, + vitNodeConfig, }); export const GET = handle(app); diff --git a/apps/web/src/database/migrations/0000_hesitant_sumo.sql b/apps/web/src/database/migrations/0000_funny_korath.sql similarity index 99% rename from apps/web/src/database/migrations/0000_hesitant_sumo.sql rename to apps/web/src/database/migrations/0000_funny_korath.sql index e8c4e66fd..7c9155cf6 100644 --- a/apps/web/src/database/migrations/0000_hesitant_sumo.sql +++ b/apps/web/src/database/migrations/0000_funny_korath.sql @@ -30,8 +30,7 @@ CREATE TABLE "core_languages" ( "enabled" boolean DEFAULT true NOT NULL, "createdAt" timestamp DEFAULT now() NOT NULL, "updatedAt" timestamp NOT NULL, - "time_24" boolean DEFAULT false NOT NULL, - "allow_in_input" boolean DEFAULT true NOT NULL, + "time24" boolean DEFAULT false NOT NULL, CONSTRAINT "core_languages_code_unique" UNIQUE("code") ); --> statement-breakpoint diff --git a/apps/web/src/database/migrations/meta/0000_snapshot.json b/apps/web/src/database/migrations/meta/0000_snapshot.json index a64e83598..1ffbde22d 100644 --- a/apps/web/src/database/migrations/meta/0000_snapshot.json +++ b/apps/web/src/database/migrations/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "95791a90-7337-433f-9510-8d6f9e4125ff", + "id": "fd378528-22f1-4e30-9108-4e06020776ba", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -299,19 +299,12 @@ "primaryKey": false, "notNull": true }, - "time_24": { - "name": "time_24", + "time24": { + "name": "time24", "type": "boolean", "primaryKey": false, "notNull": true, "default": false - }, - "allow_in_input": { - "name": "allow_in_input", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true } }, "indexes": { diff --git a/apps/web/src/database/migrations/meta/_journal.json b/apps/web/src/database/migrations/meta/_journal.json index ce84836a6..3295cfd2a 100644 --- a/apps/web/src/database/migrations/meta/_journal.json +++ b/apps/web/src/database/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "7", - "when": 1746105704858, - "tag": "0000_hesitant_sumo", + "when": 1747245244787, + "tag": "0000_funny_korath", "breakpoints": true } ] diff --git a/apps/web/src/database/schema/languages.ts b/apps/web/src/database/schema/languages.ts index 7719c8ea7..f7dd32998 100644 --- a/apps/web/src/database/schema/languages.ts +++ b/apps/web/src/database/schema/languages.ts @@ -16,8 +16,7 @@ export const core_languages = pgTable( .timestamp() .notNull() .$onUpdate(() => new Date()), - time_24: t.boolean().notNull().default(false), - allow_in_input: t.boolean().default(true).notNull(), + time24: t.boolean().notNull().default(false), }), t => [ index('core_languages_code_idx').on(t.code), diff --git a/apps/web/src/vitnode.api.config.ts b/apps/web/src/vitnode.api.config.ts new file mode 100644 index 000000000..3a052c775 --- /dev/null +++ b/apps/web/src/vitnode.api.config.ts @@ -0,0 +1,32 @@ +import { blogApiPlugin } from 'vitnode-blog/plugin.config'; +import { NodemailerEmailPlugin } from 'vitnode/api/plugins/email/nodemailer'; +import { DiscordSSOApiPlugin } from 'vitnode/api/plugins/sso/discord'; +import { FacebookSSOApiPlugin } from 'vitnode/api/plugins/sso/facebook'; +import { GoogleSSOApiPlugin } from 'vitnode/api/plugins/sso/google'; +import { buildApiConfig } from 'vitnode/vitnode.config'; + +export const vitNodeApiConfig = buildApiConfig({ + plugins: [blogApiPlugin()], + emailProvider: NodemailerEmailPlugin({ + from: process.env.NODE_MAILER_FROM, + host: process.env.NODE_MAILER_HOST, + password: process.env.NODE_MAILER_PASSWORD, + user: process.env.NOD_EMAILER_USER, + }), + authorization: { + ssoPlugins: [ + DiscordSSOApiPlugin({ + clientId: process.env.DISCORD_CLIENT_ID, + clientSecret: process.env.DISCORD_CLIENT_SECRET, + }), + GoogleSSOApiPlugin({ + clientId: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + }), + FacebookSSOApiPlugin({ + clientId: process.env.FACEBOOK_CLIENT_ID, + clientSecret: process.env.FACEBOOK_CLIENT_SECRET, + }), + ], + }, +}); diff --git a/apps/web/src/vitnode.config.ts b/apps/web/src/vitnode.config.ts index 365873a75..98803065b 100644 --- a/apps/web/src/vitnode.config.ts +++ b/apps/web/src/vitnode.config.ts @@ -1,5 +1,4 @@ import { getRequestConfig } from 'next-intl/server'; -import { blogPlugin } from 'vitnode-blog/plugin.config'; import { buildConfig, handleRequestConfig } from 'vitnode/vitnode.config'; export const vitNodeConfig = buildConfig({ @@ -7,7 +6,7 @@ export const vitNodeConfig = buildConfig({ title: 'VitNode', shortTitle: 'VitNode', }, - plugins: [blogPlugin()], + plugins: [], i18n: { locales: ['en', 'pl'] as const, defaultLocale: 'en', diff --git a/packages/vitnode/src/api/config.ts b/packages/vitnode/src/api/config.ts index 557ce58bb..7e3e3fa03 100644 --- a/packages/vitnode/src/api/config.ts +++ b/packages/vitnode/src/api/config.ts @@ -1,14 +1,13 @@ +import type { VitNodeApiConfig, VitNodeConfig } from '@/vitnode.config'; import type { OpenAPIHono } from '@hono/zod-openapi'; import type { Context, Env, Schema } from 'hono'; -import { newBuildPluginCore } from '@/api/plugin'; +import { newBuildPluginApiCore } from '@/api/plugin'; import { swaggerUI } from '@hono/swagger-ui'; import { cors } from 'hono/cors'; import { csrf } from 'hono/csrf'; import { HTTPException } from 'hono/http-exception'; -import type { BuildPluginReturn } from '../lib/plugin'; - import { internalVitNodeConfig } from './internal-config'; import { globalAdminMiddleware, @@ -36,13 +35,14 @@ export function VitNodeAPI({ app, cors: corsOptions, csrf: csrfOptions, - plugins, - ...options -}: Parameters[0] & { + vitNodeApiConfig, + vitNodeConfig, +}: { app: OpenAPIHono; cors?: CORSOptions; csrf?: CSRFOptions; - plugins: BuildPluginReturn[]; + vitNodeApiConfig: VitNodeApiConfig; + vitNodeConfig: VitNodeConfig; }) { app.doc('/swagger/doc', { openapi: '3.0.0', @@ -54,7 +54,14 @@ export function VitNodeAPI({ app.use(cors(corsOptions)); app.use(csrf(csrfOptions)); app.get('/swagger', swaggerUI({ url: `/api/swagger/doc` })); - app.use('*', globalMiddleware(options)); + app.use( + '*', + globalMiddleware({ + emailProvider: vitNodeApiConfig.emailProvider, + metadata: vitNodeConfig.metadata, + authorization: vitNodeApiConfig.authorization, + }), + ); app.use('/*/admin/*', globalAdminMiddleware()); app.onError(error => { @@ -75,7 +82,7 @@ export function VitNodeAPI({ ); }); - [newBuildPluginCore, ...plugins].map(root => { + [newBuildPluginApiCore, ...vitNodeApiConfig.plugins].map(root => { app.route(`/${root.name}`, root.hono); }); diff --git a/packages/vitnode/src/lib/plugin.ts b/packages/vitnode/src/api/lib/plugin.ts similarity index 65% rename from packages/vitnode/src/lib/plugin.ts rename to packages/vitnode/src/api/lib/plugin.ts index 6992a2e9f..2bd77db6c 100644 --- a/packages/vitnode/src/lib/plugin.ts +++ b/packages/vitnode/src/api/lib/plugin.ts @@ -1,19 +1,19 @@ import { OpenAPIHono } from '@hono/zod-openapi'; -import type { BuildModuleReturn } from '../api/lib/module'; +import type { BuildModuleReturn } from './module'; -export interface BuildPluginReturn { +export interface BuildPluginApiReturn { hono: OpenAPIHono; name: string; } -export function buildPlugin

({ +export function buildApiPlugin

({ name, modules = [], }: { modules?: BuildModuleReturn[]; name: P; -}): BuildPluginReturn { +}): BuildPluginApiReturn { const hono = new OpenAPIHono(); modules.forEach(handler => { hono.route(`/${handler.name}`, handler.hono); diff --git a/packages/vitnode/src/api/middlewares/global/global.ts b/packages/vitnode/src/api/middlewares/global/global.ts index 345c968cb..56b299209 100644 --- a/packages/vitnode/src/api/middlewares/global/global.ts +++ b/packages/vitnode/src/api/middlewares/global/global.ts @@ -1,4 +1,5 @@ import type { EmailApiPlugin } from '@/api/models/email'; +import type { VitNodeApiConfig, VitNodeConfig } from '@/vitnode.config'; import type { Context, Env, Next } from 'hono'; import { DeviceModel } from '@/api/models/device'; @@ -61,23 +62,8 @@ export const globalMiddleware = ({ authorization, metadata, emailProvider, -}: { - authorization?: { - adminCookieExpires?: number; - adminCookieName?: string; - cookieExpires?: number; - cookieName?: string; - cookieSecure?: boolean; - deviceCookieExpires?: number; - deviceCookieName?: string; - ssoPlugins?: SSOApiPlugin[]; - }; - emailProvider?: EmailApiPlugin; - metadata: { - shortTitle?: string; - title: string; - }; -}) => { +}: Pick & + Pick) => { return async (c: Context, next: Next) => { c.set('core', { metadata, diff --git a/packages/vitnode/src/api/plugin.ts b/packages/vitnode/src/api/plugin.ts index 7e4fc30b8..b582d2bb9 100644 --- a/packages/vitnode/src/api/plugin.ts +++ b/packages/vitnode/src/api/plugin.ts @@ -1,9 +1,9 @@ -import { buildPlugin } from '../lib/plugin'; +import { buildApiPlugin } from './lib/plugin'; import { adminModule } from './modules/admin/admin.module'; import { middlewareModule } from './modules/middleware/middleware.module'; import { usersModule } from './modules/users/users.module'; -export const newBuildPluginCore = buildPlugin({ +export const newBuildPluginApiCore = buildApiPlugin({ name: 'core', modules: [middlewareModule, usersModule, adminModule], }); diff --git a/packages/vitnode/src/database/schema/languages.ts b/packages/vitnode/src/database/schema/languages.ts index 7719c8ea7..f7dd32998 100644 --- a/packages/vitnode/src/database/schema/languages.ts +++ b/packages/vitnode/src/database/schema/languages.ts @@ -16,8 +16,7 @@ export const core_languages = pgTable( .timestamp() .notNull() .$onUpdate(() => new Date()), - time_24: t.boolean().notNull().default(false), - allow_in_input: t.boolean().default(true).notNull(), + time24: t.boolean().notNull().default(false), }), t => [ index('core_languages_code_idx').on(t.code), diff --git a/packages/vitnode/src/vitnode.config.ts b/packages/vitnode/src/vitnode.config.ts index 0b7eba16e..6abbc047a 100644 --- a/packages/vitnode/src/vitnode.config.ts +++ b/packages/vitnode/src/vitnode.config.ts @@ -1,7 +1,8 @@ import type { ThemeProvider } from 'next-themes'; -import type { BuildPluginReturn } from './lib/plugin'; +import type { BuildPluginApiReturn } from './api/lib/plugin'; import type { EmailApiPlugin } from './api/models/email'; +import type { SSOApiPlugin } from './api/models/sso'; export interface VitNodeConfig { debug?: boolean; @@ -15,13 +16,28 @@ export interface VitNodeConfig { shortTitle?: string; title: string; }; - plugins: BuildPluginReturn[]; + plugins: []; theme: Omit< React.ComponentProps, 'attribute' | 'disableTransitionOnChange' | 'enableSystem' >; } +export interface VitNodeApiConfig { + authorization?: { + adminCookieExpires?: number; + adminCookieName?: string; + cookieExpires?: number; + cookieName?: string; + cookieSecure?: boolean; + deviceCookieExpires?: number; + deviceCookieName?: string; + ssoPlugins?: SSOApiPlugin[]; + }; + emailProvider?: EmailApiPlugin; + plugins: BuildPluginApiReturn[]; +} + export function buildConfig( args: VitNodeConfig, ): VitNodeConfig { @@ -34,6 +50,10 @@ export function buildConfig( }; } +export function buildApiConfig(args: VitNodeApiConfig): VitNodeApiConfig { + return args; +} + export const handleRequestConfig = async ({ requestLocale, vitNodeConfig, diff --git a/plugins/blog/src/plugin.config.ts b/plugins/blog/src/plugin.config.ts index 9dab5187c..e1ef86f9c 100644 --- a/plugins/blog/src/plugin.config.ts +++ b/plugins/blog/src/plugin.config.ts @@ -1,9 +1,9 @@ -import { buildPlugin } from 'vitnode/lib/plugin'; +import { buildApiPlugin } from 'vitnode/api/lib/plugin'; import { categoriesModule } from './modules/categories/categories.module'; -export const blogPlugin = () => { - return buildPlugin({ +export const blogApiPlugin = () => { + return buildApiPlugin({ name: 'blog', modules: [categoriesModule], }); From d0db947d1fd8b53858af2fe32cb401973f9517b7 Mon Sep 17 00:00:00 2001 From: aXenDeveloper Date: Thu, 15 May 2025 18:51:46 +0200 Subject: [PATCH 2/2] feat: Implement blog plugin with categories module and test page --- apps/web/src/vitnode.api.config.ts | 2 +- apps/web/src/vitnode.config.ts | 9 +++++---- packages/vitnode/src/lib/plugin.ts | 11 +++++++++++ packages/vitnode/src/views/dynamic-view.tsx | 10 ++++++++++ packages/vitnode/src/vitnode.config.ts | 3 ++- plugins/blog/package.json | 2 ++ plugins/blog/src/{plugin.config.ts => api.ts} | 5 +++-- .../modules/categories/categories.module.ts | 0 .../src/{ => api}/modules/categories/route.ts | 0 plugins/blog/src/config.ts | 3 +++ plugins/blog/src/plugin.tsx | 13 +++++++++++++ plugins/blog/src/views/test.tsx | 8 ++++++++ pnpm-lock.yaml | 15 +++++++++++++++ 13 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 packages/vitnode/src/lib/plugin.ts rename plugins/blog/src/{plugin.config.ts => api.ts} (53%) rename plugins/blog/src/{ => api}/modules/categories/categories.module.ts (100%) rename plugins/blog/src/{ => api}/modules/categories/route.ts (100%) create mode 100644 plugins/blog/src/config.ts create mode 100644 plugins/blog/src/plugin.tsx create mode 100644 plugins/blog/src/views/test.tsx diff --git a/apps/web/src/vitnode.api.config.ts b/apps/web/src/vitnode.api.config.ts index 3a052c775..28bb607e8 100644 --- a/apps/web/src/vitnode.api.config.ts +++ b/apps/web/src/vitnode.api.config.ts @@ -1,4 +1,4 @@ -import { blogApiPlugin } from 'vitnode-blog/plugin.config'; +import { blogApiPlugin } from 'vitnode-blog/api'; import { NodemailerEmailPlugin } from 'vitnode/api/plugins/email/nodemailer'; import { DiscordSSOApiPlugin } from 'vitnode/api/plugins/sso/discord'; import { FacebookSSOApiPlugin } from 'vitnode/api/plugins/sso/facebook'; diff --git a/apps/web/src/vitnode.config.ts b/apps/web/src/vitnode.config.ts index 98803065b..6dab4cc08 100644 --- a/apps/web/src/vitnode.config.ts +++ b/apps/web/src/vitnode.config.ts @@ -1,4 +1,5 @@ import { getRequestConfig } from 'next-intl/server'; +import { blogPlugin } from 'vitnode-blog/plugin'; import { buildConfig, handleRequestConfig } from 'vitnode/vitnode.config'; export const vitNodeConfig = buildConfig({ @@ -6,7 +7,7 @@ export const vitNodeConfig = buildConfig({ title: 'VitNode', shortTitle: 'VitNode', }, - plugins: [], + plugins: [blogPlugin()], i18n: { locales: ['en', 'pl'] as const, defaultLocale: 'en', @@ -18,6 +19,6 @@ export const vitNodeConfig = buildConfig({ }); // This is the request config for the app. It will be used in the app router. -export default getRequestConfig(async ({ requestLocale }) => - handleRequestConfig({ requestLocale, vitNodeConfig }), -); +export default getRequestConfig(async ({ requestLocale }) => { + return await handleRequestConfig({ requestLocale, vitNodeConfig }); +}); diff --git a/packages/vitnode/src/lib/plugin.ts b/packages/vitnode/src/lib/plugin.ts new file mode 100644 index 000000000..33731427d --- /dev/null +++ b/packages/vitnode/src/lib/plugin.ts @@ -0,0 +1,11 @@ +export interface BuildPluginReturn

{ + name: P; + pages?: () => React.ReactNode; +} + +export function buildPlugin

({ + name, + pages, +}: BuildPluginReturn

): BuildPluginReturn

{ + return { name, pages }; +} diff --git a/packages/vitnode/src/views/dynamic-view.tsx b/packages/vitnode/src/views/dynamic-view.tsx index 9b331ecb6..886c9c573 100644 --- a/packages/vitnode/src/views/dynamic-view.tsx +++ b/packages/vitnode/src/views/dynamic-view.tsx @@ -41,6 +41,7 @@ export const generateMetadataDynamicView = async ({ export const DynamicView = async ({ params, searchParams, + config, }: DynamicViewProps & { config: VitNodeConfig; }) => { @@ -75,6 +76,15 @@ export const DynamicView = async ({ return view; } + // Try to render a plugin view if available + const plugin = config.plugins.find( + p => p.name === rest[0] && typeof p.pages === 'function', + ); + + if (plugin?.pages) { + return plugin.pages(); + } + notFound(); }; diff --git a/packages/vitnode/src/vitnode.config.ts b/packages/vitnode/src/vitnode.config.ts index 6abbc047a..3269aa68a 100644 --- a/packages/vitnode/src/vitnode.config.ts +++ b/packages/vitnode/src/vitnode.config.ts @@ -3,6 +3,7 @@ import type { ThemeProvider } from 'next-themes'; import type { BuildPluginApiReturn } from './api/lib/plugin'; import type { EmailApiPlugin } from './api/models/email'; import type { SSOApiPlugin } from './api/models/sso'; +import type { BuildPluginReturn } from './lib/plugin'; export interface VitNodeConfig { debug?: boolean; @@ -16,7 +17,7 @@ export interface VitNodeConfig { shortTitle?: string; title: string; }; - plugins: []; + plugins: BuildPluginReturn[]; theme: Omit< React.ComponentProps, 'attribute' | 'disableTransitionOnChange' | 'enableSystem' diff --git a/plugins/blog/package.json b/plugins/blog/package.json index 5aede2486..e418504e9 100644 --- a/plugins/blog/package.json +++ b/plugins/blog/package.json @@ -34,6 +34,8 @@ "devDependencies": { "@swc/cli": "0.6.0", "@swc/core": "^1.11.24", + "@types/react": "^19.1.3", + "@types/react-dom": "^19.1.5", "concurrently": "^9.1.2", "eslint": "^9.25.1", "eslint-config-typescript-vitnode": "workspace:*", diff --git a/plugins/blog/src/plugin.config.ts b/plugins/blog/src/api.ts similarity index 53% rename from plugins/blog/src/plugin.config.ts rename to plugins/blog/src/api.ts index e1ef86f9c..a0e33347c 100644 --- a/plugins/blog/src/plugin.config.ts +++ b/plugins/blog/src/api.ts @@ -1,10 +1,11 @@ import { buildApiPlugin } from 'vitnode/api/lib/plugin'; -import { categoriesModule } from './modules/categories/categories.module'; +import { categoriesModule } from './api/modules/categories/categories.module'; +import { configPlugin } from './config'; export const blogApiPlugin = () => { return buildApiPlugin({ - name: 'blog', + ...configPlugin, modules: [categoriesModule], }); }; diff --git a/plugins/blog/src/modules/categories/categories.module.ts b/plugins/blog/src/api/modules/categories/categories.module.ts similarity index 100% rename from plugins/blog/src/modules/categories/categories.module.ts rename to plugins/blog/src/api/modules/categories/categories.module.ts diff --git a/plugins/blog/src/modules/categories/route.ts b/plugins/blog/src/api/modules/categories/route.ts similarity index 100% rename from plugins/blog/src/modules/categories/route.ts rename to plugins/blog/src/api/modules/categories/route.ts diff --git a/plugins/blog/src/config.ts b/plugins/blog/src/config.ts new file mode 100644 index 000000000..ca730fc84 --- /dev/null +++ b/plugins/blog/src/config.ts @@ -0,0 +1,3 @@ +export const configPlugin = { + name: 'blog', +}; diff --git a/plugins/blog/src/plugin.tsx b/plugins/blog/src/plugin.tsx new file mode 100644 index 000000000..714c43d50 --- /dev/null +++ b/plugins/blog/src/plugin.tsx @@ -0,0 +1,13 @@ +import { buildPlugin } from 'vitnode/lib/plugin'; + +import { configPlugin } from './config'; +import { Test } from './views/test'; + +export const blogPlugin = () => { + return buildPlugin({ + ...configPlugin, + pages: () => { + return ; + }, + }); +}; diff --git a/plugins/blog/src/views/test.tsx b/plugins/blog/src/views/test.tsx new file mode 100644 index 000000000..18c67c7f2 --- /dev/null +++ b/plugins/blog/src/views/test.tsx @@ -0,0 +1,8 @@ +export const Test = () => { + return ( +

+

Test

+

This is a test page.

+
+ ); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34e77f48f..246fd8411 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -460,6 +460,12 @@ importers: '@swc/core': specifier: ^1.11.24 version: 1.11.24 + '@types/react': + specifier: ^19.1.3 + version: 19.1.3 + '@types/react-dom': + specifier: ^19.1.5 + version: 19.1.5(@types/react@19.1.3) concurrently: specifier: ^9.1.2 version: 9.1.2 @@ -3409,6 +3415,11 @@ packages: peerDependencies: '@types/react': ^19.0.0 + '@types/react-dom@19.1.5': + resolution: {integrity: sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==} + peerDependencies: + '@types/react': ^19.0.0 + '@types/react-reconciler@0.28.9': resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==} peerDependencies: @@ -9922,6 +9933,10 @@ snapshots: dependencies: '@types/react': 19.1.3 + '@types/react-dom@19.1.5(@types/react@19.1.3)': + dependencies: + '@types/react': 19.1.3 + '@types/react-reconciler@0.28.9(@types/react@19.1.3)': dependencies: '@types/react': 19.1.3