diff --git a/.env.example b/.env.example
index 1d34646f3..8cd02f224 100644
--- a/.env.example
+++ b/.env.example
@@ -1,8 +1,7 @@
POSTGRES_URL=postgresql://root:root@localhost:5432/vitnode
-NEXT_PUBLIC_BACKEND_URL=http://localhost:3000
-NEXT_PUBLIC_BACKEND_CLIENT_URL=http://localhost:3000
-NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000
+NEXT_PUBLIC_API_URL=http://localhost:3000
+NEXT_PUBLIC_WEB_URL=http://localhost:3000
# === Docker Database Postgres ===
POSTGRES_USER=root
diff --git a/apps/api/drizzle.config.ts b/apps/api/drizzle.config.ts
new file mode 100644
index 000000000..0d630172b
--- /dev/null
+++ b/apps/api/drizzle.config.ts
@@ -0,0 +1,12 @@
+import { defineVitNodeDrizzleConfig } from '@vitnode/core/drizzle.config';
+
+import { POSTGRES_URL, vitNodeApiConfig } from './src/vitnode.api.config';
+
+export default defineVitNodeDrizzleConfig({
+ vitNodeApiConfig,
+ out: './migrations/',
+ dialect: 'postgresql',
+ dbCredentials: {
+ url: POSTGRES_URL,
+ },
+});
diff --git a/apps/api/eslint.config.mjs b/apps/api/eslint.config.mjs
new file mode 100644
index 000000000..dd3efc12a
--- /dev/null
+++ b/apps/api/eslint.config.mjs
@@ -0,0 +1,8 @@
+import eslintVitNode from '@vitnode/eslint-config/eslint';
+
+export default [
+ ...eslintVitNode,
+ {
+ ignores: ['drizzle.config.ts'],
+ },
+];
diff --git a/apps/api/package.json b/apps/api/package.json
new file mode 100644
index 000000000..efcd03033
--- /dev/null
+++ b/apps/api/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "api",
+ "version": "1.2.0-canary.31",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "tsx watch src/index.ts",
+ "build": "tsc && tsc-alias -p tsconfig.json",
+ "start": "node dist/index.js",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "drizzle-kit": "drizzle-kit"
+ },
+ "dependencies": {
+ "@hono/zod-openapi": "^0.19.8",
+ "@hono/zod-validator": "^0.7.0",
+ "@react-email/components": "^0.1.1",
+ "@vitnode/core": "workspace:*",
+ "drizzle-kit": "^0.31.3",
+ "drizzle-orm": "^0.44.2",
+ "hono": "^4.8.3",
+ "next-intl": "^4.3.1",
+ "react": "^19.1",
+ "react-dom": "^19.1",
+ "zod": "^3.25.67"
+ },
+ "devDependencies": {
+ "@hono/node-server": "^1.15.0",
+ "@types/node": "^24",
+ "@types/react": "^19.1",
+ "@types/react-dom": "^19.1",
+ "@vitnode/eslint-config": "workspace:*",
+ "dotenv": "^17.1.0",
+ "eslint": "^9.29.0",
+ "prettier": "^3.6.1",
+ "prettier-plugin-tailwindcss": "^0.6.12",
+ "react-email": "^4.0.17",
+ "tsc-alias": "^1.8.16",
+ "tsx": "^4.20.3",
+ "typescript": "^5.8.3"
+ }
+}
diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts
new file mode 100644
index 000000000..98369de4f
--- /dev/null
+++ b/apps/api/src/index.ts
@@ -0,0 +1,29 @@
+import { serve } from '@hono/node-server';
+import { OpenAPIHono } from '@hono/zod-openapi';
+import { VitNodeAPI } from '@vitnode/core/api/config';
+
+import { vitNodeApiConfig } from './vitnode.api.config.js';
+import { vitNodeConfig } from './vitnode.config.js';
+
+const app = new OpenAPIHono().basePath('/api');
+
+VitNodeAPI({
+ app,
+ vitNodeApiConfig,
+ vitNodeConfig,
+});
+
+serve(
+ {
+ fetch: app.fetch,
+ port: 8080,
+ },
+ info => {
+ const initMessage = '\x1b[34m[VitNode]\x1b[0m';
+
+ // eslint-disable-next-line no-console
+ console.log(
+ `${initMessage} API server is running on http://localhost:${info.port}`,
+ );
+ },
+);
diff --git a/apps/api/src/vitnode.api.config.ts b/apps/api/src/vitnode.api.config.ts
new file mode 100644
index 000000000..5111961a1
--- /dev/null
+++ b/apps/api/src/vitnode.api.config.ts
@@ -0,0 +1,17 @@
+import { buildApiConfig } from '@vitnode/core/vitnode.config';
+import * as dotenv from 'dotenv';
+import { drizzle } from 'drizzle-orm/postgres-js';
+
+dotenv.config();
+
+export const POSTGRES_URL =
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ process.env.POSTGRES_URL || 'postgresql://root:root@localhost:5432/vitnode';
+
+export const vitNodeApiConfig = buildApiConfig({
+ plugins: [],
+ dbProvider: drizzle({
+ connection: POSTGRES_URL,
+ casing: 'camelCase',
+ }),
+});
diff --git a/apps/api/src/vitnode.config.ts b/apps/api/src/vitnode.config.ts
new file mode 100644
index 000000000..4f20e8a11
--- /dev/null
+++ b/apps/api/src/vitnode.config.ts
@@ -0,0 +1,32 @@
+import { buildConfig, handleRequestConfig } from '@vitnode/core/vitnode.config';
+import { getRequestConfig } from 'next-intl/server';
+
+export const vitNodeConfig = buildConfig({
+ metadata: {
+ title: 'VitNode',
+ shortTitle: 'VitNode',
+ },
+ plugins: [],
+ i18n: {
+ locales: [
+ {
+ code: 'en',
+ name: 'English',
+ },
+ ],
+ defaultLocale: 'en',
+ },
+ theme: {
+ defaultTheme: 'light',
+ },
+});
+
+// This is the request config for the app. It will be used in the app router.
+export default getRequestConfig(
+ async ({ requestLocale }) =>
+ await handleRequestConfig({
+ requestLocale,
+ vitNodeConfig,
+ pathToMessages: async path => await import(`./locales/${path}`),
+ }),
+);
diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json
new file mode 100644
index 000000000..9498a3d91
--- /dev/null
+++ b/apps/api/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "extends": "@vitnode/eslint-config/tsconfig",
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "NodeNext",
+ "baseUrl": ".",
+ "outDir": "./dist",
+ "types": ["node"],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["src"],
+ "exclude": ["node_modules"]
+}
diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore
index 55a12ae71..c3a96f073 100644
--- a/apps/docs/.gitignore
+++ b/apps/docs/.gitignore
@@ -1,5 +1,6 @@
# deps
/node_modules
+.turbo
# generated content
.contentlayer
diff --git a/apps/docs/.prettierrc.mjs b/apps/docs/.prettierrc.mjs
deleted file mode 100644
index 92fe83d4d..000000000
--- a/apps/docs/.prettierrc.mjs
+++ /dev/null
@@ -1,11 +0,0 @@
-import vitnodePrettier from '@vitnode/eslint-config/prettierrc';
-
-/**
- * @see https://prettier.io/docs/en/configuration.html
- * @type {import("prettier").Config}
- */
-const config = {
- ...vitnodePrettier,
-};
-
-export default config;
diff --git a/apps/docs/content/docs/dev/config/auth.mdx b/apps/docs/content/docs/dev/advanced/auth.mdx
similarity index 100%
rename from apps/docs/content/docs/dev/config/auth.mdx
rename to apps/docs/content/docs/dev/advanced/auth.mdx
diff --git a/apps/docs/content/docs/dev/config/meta.json b/apps/docs/content/docs/dev/advanced/meta.json
similarity index 50%
rename from apps/docs/content/docs/dev/config/meta.json
rename to apps/docs/content/docs/dev/advanced/meta.json
index bcf23fb73..baff7a786 100644
--- a/apps/docs/content/docs/dev/config/meta.json
+++ b/apps/docs/content/docs/dev/advanced/meta.json
@@ -1,4 +1,4 @@
{
- "title": "Config",
+ "title": "Advanced",
"pages": ["..."]
}
diff --git a/apps/docs/content/docs/dev/config/rate-limiter.mdx b/apps/docs/content/docs/dev/advanced/rate-limiter.mdx
similarity index 100%
rename from apps/docs/content/docs/dev/config/rate-limiter.mdx
rename to apps/docs/content/docs/dev/advanced/rate-limiter.mdx
diff --git a/apps/docs/content/docs/guides/captcha/cloudflare.mdx b/apps/docs/content/docs/dev/captcha/cloudflare.mdx
similarity index 100%
rename from apps/docs/content/docs/guides/captcha/cloudflare.mdx
rename to apps/docs/content/docs/dev/captcha/cloudflare.mdx
diff --git a/apps/docs/content/docs/guides/captcha/cloudflare.png b/apps/docs/content/docs/dev/captcha/cloudflare.png
similarity index 100%
rename from apps/docs/content/docs/guides/captcha/cloudflare.png
rename to apps/docs/content/docs/dev/captcha/cloudflare.png
diff --git a/apps/docs/content/docs/dev/captcha/meta.json b/apps/docs/content/docs/dev/captcha/meta.json
new file mode 100644
index 000000000..0c7e112e0
--- /dev/null
+++ b/apps/docs/content/docs/dev/captcha/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "Captcha",
+ "pages": ["overview", "---Adapters---", "..."]
+}
diff --git a/apps/docs/content/docs/dev/captcha.mdx b/apps/docs/content/docs/dev/captcha/overview.mdx
similarity index 99%
rename from apps/docs/content/docs/dev/captcha.mdx
rename to apps/docs/content/docs/dev/captcha/overview.mdx
index c6fd6ed0b..5d17778e0 100644
--- a/apps/docs/content/docs/dev/captcha.mdx
+++ b/apps/docs/content/docs/dev/captcha/overview.mdx
@@ -1,5 +1,5 @@
---
-title: Captcha
+title: Overview
description: Protect your forms and API call with captcha validation.
---
diff --git a/apps/docs/content/docs/guides/captcha/recaptcha.mdx b/apps/docs/content/docs/dev/captcha/recaptcha.mdx
similarity index 100%
rename from apps/docs/content/docs/guides/captcha/recaptcha.mdx
rename to apps/docs/content/docs/dev/captcha/recaptcha.mdx
diff --git a/apps/docs/content/docs/guides/captcha/recaptcha.png b/apps/docs/content/docs/dev/captcha/recaptcha.png
similarity index 100%
rename from apps/docs/content/docs/guides/captcha/recaptcha.png
rename to apps/docs/content/docs/dev/captcha/recaptcha.png
diff --git a/apps/docs/content/docs/dev/debugging.mdx b/apps/docs/content/docs/dev/debugging/index.mdx
similarity index 100%
rename from apps/docs/content/docs/dev/debugging.mdx
rename to apps/docs/content/docs/dev/debugging/index.mdx
diff --git a/apps/docs/content/docs/dev/logging.mdx b/apps/docs/content/docs/dev/debugging/logging.mdx
similarity index 74%
rename from apps/docs/content/docs/dev/logging.mdx
rename to apps/docs/content/docs/dev/debugging/logging.mdx
index 93885237a..367972152 100644
--- a/apps/docs/content/docs/dev/logging.mdx
+++ b/apps/docs/content/docs/dev/debugging/logging.mdx
@@ -17,8 +17,8 @@ import { buildRoute } from '@vitnode/core/api/lib/route';
export const testRoute = buildRoute(
{},
{
- handler: c => {
- c.get('log').warn('This is a test warn log'); // [!code ++]
+ handler: async c => {
+ await c.get('log').warn('This is a test warn log'); // [!code ++]
return c.text('test');
},
@@ -33,15 +33,15 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
{page.data.description}
-+
Simplifies development with a powerful Plugin System, Admin Control Panel and extensible architecture.
diff --git a/apps/docs/src/app/[locale]/(main)/(home)/sections/admin/admin.tsx b/apps/docs/src/app/[locale]/(main)/(home)/sections/admin/admin.tsx index 34eb36d6b..9dfc71db9 100644 --- a/apps/docs/src/app/[locale]/(main)/(home)/sections/admin/admin.tsx +++ b/apps/docs/src/app/[locale]/(main)/(home)/sections/admin/admin.tsx @@ -13,8 +13,8 @@ export const AdminSection = () => {+
Everything you need for modern web apps, zero config.
diff --git a/apps/docs/src/app/[locale]/(main)/(home)/sections/powering-by/logos/tailwindcss.tsx b/apps/docs/src/app/[locale]/(main)/(home)/sections/powering-by/logos/tailwindcss.tsx index 82cd065f9..0dee1a73b 100644 --- a/apps/docs/src/app/[locale]/(main)/(home)/sections/powering-by/logos/tailwindcss.tsx +++ b/apps/docs/src/app/[locale]/(main)/(home)/sections/powering-by/logos/tailwindcss.tsx @@ -2,7 +2,7 @@ export const TailwindCSSLogo = () => { return (An open-source UI component library.
diff --git a/apps/docs/src/vitnode.api.config.ts b/apps/docs/src/vitnode.api.config.ts index 1a4dceb94..ed34849c9 100644 --- a/apps/docs/src/vitnode.api.config.ts +++ b/apps/docs/src/vitnode.api.config.ts @@ -4,13 +4,7 @@ import { DiscordSSOApiPlugin } from '@vitnode/core/api/adapters/sso/discord'; import { FacebookSSOApiPlugin } from '@vitnode/core/api/adapters/sso/facebook'; import { GoogleSSOApiPlugin } from '@vitnode/core/api/adapters/sso/google'; import { buildApiConfig } from '@vitnode/core/vitnode.config'; -import * as dotenv from 'dotenv'; import { drizzle } from 'drizzle-orm/postgres-js'; -import { join } from 'path'; - -dotenv.config({ - path: join(process.cwd(), '..', '..', '.env'), -}); export const POSTGRES_URL = // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -31,12 +25,14 @@ export const vitNodeApiConfig = buildApiConfig({ points: 20, // 20 requests duration: 60, // per 60 seconds }, - emailAdapter: NodemailerEmailAdapter({ - from: process.env.NODE_MAILER_FROM, - host: process.env.NODE_MAILER_HOST, - password: process.env.NODE_MAILER_PASSWORD, - user: process.env.NOD_EMAILER_USER, - }), + email: { + adapter: NodemailerEmailAdapter({ + 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: { ssoAdapters: [ DiscordSSOApiPlugin({ diff --git a/package.json b/package.json index 94c6e9e86..b836265b6 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,11 @@ "devDependencies": { "@types/node": "^24", "@vitnode/eslint-config": "workspace:*", - "prettier": "^3.6.1", + "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.13", - "tsx": "^4.20.3", "turbo": "^2.5.4", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "zod": "^3.25.74" }, "engines": { "node": ">=22" diff --git a/packages/create-vitnode-app/README.md b/packages/create-vitnode-app/README.md index f80887a89..91a9fcb0c 100644 --- a/packages/create-vitnode-app/README.md +++ b/packages/create-vitnode-app/README.md @@ -37,8 +37,9 @@ bun create vitnode-app@latest ## Options -| Option | Description | -| ------------------- | ---------------------------------------------------------- | -| `--package-manager` | Specify the package manager to use. Support `npm`, `pnpm`. | -| `--eslint` | Initialize with eslint config. | -| `--skip-install` | Skip installing packages after initializing the project. | +| Option | Description | +| ------------------- | --------------------------------------------------------------------------------- | +| `--package-manager` | Specify the package manager to use. Support `npm`, `pnpm`. | +| `--eslint` | Initialize with eslint config. | +| `--skip-install` | Skip installing packages after initializing the project. | +| `--mode` | Specify the type of app to create. Support `singleApp`, `apiMonorepo`, `onlyApi`. | diff --git a/packages/create-vitnode-app/copy-of-vitnode-app/root/.env.example b/packages/create-vitnode-app/copy-of-vitnode-app/root/.env.example index 1d34646f3..8cd02f224 100644 --- a/packages/create-vitnode-app/copy-of-vitnode-app/root/.env.example +++ b/packages/create-vitnode-app/copy-of-vitnode-app/root/.env.example @@ -1,8 +1,7 @@ POSTGRES_URL=postgresql://root:root@localhost:5432/vitnode -NEXT_PUBLIC_BACKEND_URL=http://localhost:3000 -NEXT_PUBLIC_BACKEND_CLIENT_URL=http://localhost:3000 -NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000 +NEXT_PUBLIC_API_URL=http://localhost:3000 +NEXT_PUBLIC_WEB_URL=http://localhost:3000 # === Docker Database Postgres === POSTGRES_USER=root diff --git a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global.css b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global.css index 346d12d40..985e57ba6 100644 --- a/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global.css +++ b/packages/create-vitnode-app/copy-of-vitnode-app/root/src/app/global.css @@ -6,72 +6,72 @@ @source "../../node_modules/@vitnode/core/dist/src/views"; :root:not(.dark) { - --background: oklch(0.98 0 0); - --foreground: oklch(0.145 0 0); + --background: oklch(0.96 0.01 250); + --foreground: oklch(0.18 0.01 250); --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); + --card-foreground: oklch(0.22 0.01 250); --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); + --popover-foreground: oklch(0.22 0.01 250); --primary: oklch(0.51 0.16 262.61); --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.96 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.45 0 0); - --accent: oklch(0.95 0 0); - --accent-foreground: oklch(0.205 0 0); + --secondary: oklch(0.93 0.01 250); + --secondary-foreground: oklch(0.25 0.01 250); + --muted: oklch(0.95 0.01 250); + --muted-foreground: oklch(0.35 0.01 250); + --accent: oklch(0.92 0.01 250); + --accent-foreground: oklch(0.25 0.01 250); --destructive: oklch(0.6 0.2 24.45); --warn: oklch(0.54 0.12 82.58); - --border: oklch(0.9 0 0); - --input: oklch(0.9 0 0); - --ring: oklch(0.7 0.16 262.61); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); + --border: oklch(0.9 0.01 250); + --input: oklch(0.9 0.01 250); + --ring: oklch(0.7 0.13 250); + --chart-1: oklch(0.65 0.13 250); + --chart-2: oklch(0.6 0.11 260); + --chart-3: oklch(0.45 0.09 250); + --chart-4: oklch(0.8 0.13 250); + --chart-5: oklch(0.75 0.13 250); --sidebar: var(--card); - --sidebar-foreground: oklch(0.145 0 0); + --sidebar-foreground: oklch(0.22 0.01 250); --sidebar-primary: var(--primary); --sidebar-primary-foreground: var(--primary-foreground); - --sidebar-accent: oklch(0.975 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.902 0 0); - --sidebar-ring: oklch(0.708 0 0); + --sidebar-accent: oklch(0.97 0.01 250); + --sidebar-accent-foreground: oklch(0.25 0.01 250); + --sidebar-border: oklch(0.91 0.01 250); + --sidebar-ring: oklch(0.7 0.13 250); } .dark { - --background: oklch(0.1 0 0); - --foreground: oklch(0.98 0 0); - --card: oklch(0.18 0 0); - --card-foreground: oklch(0.98 0 0); - --popover: oklch(0.18 0 0); - --popover-foreground: oklch(0.98 0 0); + --background: oklch(0.16 0.01 250); + --foreground: oklch(0.96 0.01 250); + --card: oklch(0.18 0.01 250); + --card-foreground: oklch(0.96 0.01 250); + --popover: oklch(0.18 0.01 250); + --popover-foreground: oklch(0.96 0.01 250); --primary: oklch(0.51 0.16 262.61); --primary-foreground: oklch(0.98 0 0); - --secondary: oklch(0.24 0.01 240); - --secondary-foreground: oklch(0.98 0.005 240); - --muted: oklch(0.22 0 0); - --muted-foreground: oklch(0.7 0 0); - --accent: oklch(0.28 0 0); + --secondary: oklch(0.22 0.01 250); + --secondary-foreground: oklch(0.96 0.01 250); + --muted: oklch(0.22 0.01 250); + --muted-foreground: oklch(0.7 0.01 250); + --accent: oklch(0.28 0.01 250); --destructive: oklch(0.62 0.2 25.35); --warn: oklch(0.76 0.18 81.84); - --border: oklch(0.28 0 0); - --input: oklch(0.28 0 0); - --ring: oklch(0.51 0.16 262.61); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); + --border: oklch(0.26 0.01 250); + --input: oklch(0.26 0.01 250); + --ring: oklch(0.54 0.13 250); + --chart-1: oklch(0.45 0.09 250); + --chart-2: oklch(0.6 0.11 260); + --chart-3: oklch(0.75 0.13 250); + --chart-4: oklch(0.8 0.13 250); + --chart-5: oklch(0.65 0.13 250); --sidebar: var(--card); - --sidebar-foreground: oklch(0.98 0 0); + --sidebar-foreground: oklch(0.96 0.01 250); --sidebar-primary: var(--primary); --sidebar-primary-foreground: var(--primary-foreground); - --sidebar-accent: oklch(0.22 0 0); - --sidebar-accent-foreground: oklch(0.98 0 0); - --sidebar-border: oklch(0.269 0 0); - --sidebar-ring: oklch(0.51 0.16 262.61); + --sidebar-accent: oklch(0.22 0.01 250); + --sidebar-accent-foreground: oklch(0.96 0.01 250); + --sidebar-border: oklch(0.21 0.01 250); + --sidebar-ring: oklch(0.54 0.13 250); } :root { diff --git a/packages/create-vitnode-app/copy-of-vitnode-app/root/tsconfig.json b/packages/create-vitnode-app/copy-of-vitnode-app/root/tsconfig.json new file mode 100644 index 000000000..13dc93961 --- /dev/null +++ b/packages/create-vitnode-app/copy-of-vitnode-app/root/tsconfig.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vitnode/eslint-config/tsconfig", + "compilerOptions": { + "target": "ESNext", + "module": "esnext", + "moduleResolution": "bundler", + "noEmit": true, + "baseUrl": ".", + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/create-vitnode-app/package.json b/packages/create-vitnode-app/package.json index 427a1885b..286ab4637 100644 --- a/packages/create-vitnode-app/package.json +++ b/packages/create-vitnode-app/package.json @@ -28,18 +28,18 @@ "typescript" ], "dependencies": { - "@inquirer/prompts": "^7.5.3", + "@inquirer/prompts": "^7.6.0", "commander": "^14.0.0", "ora": "^8.2.0", "picocolors": "^1.1.1", "validate-npm-package-name": "^6.0.1" }, "devDependencies": { - "@types/node": "^24", + "@types/node": "^24.0.10", "@types/prompts": "^2.4.9", "@types/validate-npm-package-name": "^4.0.2", "@vitnode/eslint-config": "workspace:*", - "eslint": "^9.29.0", + "eslint": "^9.30.1", "typescript": "^5.8.3" } } diff --git a/packages/create-vitnode-app/src/create/create-package-json.ts b/packages/create-vitnode-app/src/create/create-package-json.ts index 5f617947e..51e5c7946 100644 --- a/packages/create-vitnode-app/src/create/create-package-json.ts +++ b/packages/create-vitnode-app/src/create/create-package-json.ts @@ -3,6 +3,7 @@ import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; import type { PackageJSON } from '../helpers/packages-json.js'; +import type { CreateCliReturn } from '../questions.js'; import { getAvailablePackageManagers } from '../helpers/get-available-package-managers.js'; @@ -15,10 +16,12 @@ export const createPackageJSON = async ({ root, eslint, docker, + mode, }: { appName: string; docker?: boolean; eslint: boolean; + mode: CreateCliReturn['mode']; packageManager: string; root: string; }) => { @@ -27,24 +30,22 @@ export const createPackageJSON = async ({ await readFile(join(__dirname, '..', '..', '..', 'package.json'), 'utf-8'), ); - const packageJson: PackageJSON = { - name: appName, + const apiPackageJson: PackageJSON = { + name: mode === 'apiMonorepo' ? 'api' : appName, version: '0.1.0', private: true, type: 'module', scripts: { - 'db:push': 'vitnode push', - 'db:migrate': 'vitnode migrate', - dev: 'vitnode init && next dev --turbopack', - build: 'next build --turbopack', - start: 'next start', + dev: 'tsx watch src/index.ts', + build: 'tsc && tsc-alias -p tsconfig.json', + start: 'node dist/index.js', ...(eslint ? { lint: 'eslint .', 'lint:fix': 'eslint . --fix', } : {}), - ...(docker + ...(docker && mode === 'onlyApi' ? { 'docker:dev': `docker compose -f ./docker-compose.yml -p ${appName}-vitnode-dev up -d`, } @@ -54,45 +55,205 @@ export const createPackageJSON = async ({ dependencies: { '@hono/zod-openapi': '^0.19.8', '@hono/zod-validator': '^0.7.0', - '@hookform/resolvers': '^5.1.1', '@react-email/components': '^0.1.1', '@vitnode/core': `^${pkg.version}`, - 'babel-plugin-react-compiler': '19.1.0-rc.2', 'drizzle-kit': '^0.31.3', 'drizzle-orm': '^0.44.2', hono: '^4.8.3', - 'lucide-react': '^0.523.0', - next: '^15.3.4', 'next-intl': '^4.3.1', - react: '^19.1.0', - 'react-dom': '^19.1.0', - 'react-hook-form': '^7.58.1', - sonner: '^2.0.5', + react: '^19.1', + 'react-dom': '^19.1', zod: '^3.25.67', }, devDependencies: { - '@tailwindcss/postcss': '^4.1.11', + '@hono/node-server': '^1.15.0', '@types/node': '^24', '@types/react': '^19.1', '@types/react-dom': '^19.1', + '@vitnode/eslint-config': `^${pkg.version}`, + dotenv: '^17.1.0', ...(eslint ? { - eslint: '^9.29.0', - '@vitnode/eslint-config': `^${pkg.version}`, - 'prettier-plugin-tailwindcss': '^0.6.12', - prettier: '^3.6.1', + eslint: '^9.30.1', + ...(mode === 'onlyApi' + ? { + 'prettier-plugin-tailwindcss': '^0.6.12', + prettier: '^3.6.2', + } + : {}), } : {}), 'react-email': '^4.0.17', - tailwindcss: '^4.1.11', - 'tw-animate-css': '^1.3.2', + 'tsc-alias': '^1.8.16', + tsx: '^4.20.3', typescript: '^5.8.3', }, - packageManager: `${packageManager}@${availablePackageManagers[packageManager]}`, }; - await writeFile( - join(root, 'package.json'), - JSON.stringify(packageJson, null, 2), - ); + if (mode === 'singleApp') { + const packageJson: PackageJSON = { + name: appName, + version: '0.1.0', + private: true, + type: 'module', + scripts: { + 'db:push': 'vitnode push', + 'db:migrate': 'vitnode migrate', + dev: 'vitnode init && next dev --turbopack', + build: 'next build --turbopack', + start: 'next start', + ...(eslint + ? { + lint: 'eslint .', + 'lint:fix': 'eslint . --fix', + } + : {}), + ...(docker + ? { + 'docker:dev': `docker compose -f ./docker-compose.yml -p ${appName}-vitnode-dev up -d`, + } + : {}), + 'drizzle-kit': 'drizzle-kit', + }, + dependencies: { + '@hono/zod-openapi': '^0.19.9', + '@hono/zod-validator': '^0.7.0', + '@hookform/resolvers': '^5.1.1', + '@react-email/components': '^0.1.1', + '@vitnode/core': `^${pkg.version}`, + 'babel-plugin-react-compiler': '19.1.0-rc.2', + 'drizzle-kit': '^0.31.4', + 'drizzle-orm': '^0.44.2', + hono: '^4.8.4', + 'lucide-react': '^0.525.0', + next: '^15.3.5', + 'next-intl': '^4.3.4', + react: '^19.1', + 'react-dom': '^19.1', + 'react-hook-form': '^7.60.0', + sonner: '^2.0.6', + zod: '^3.25.74', + }, + devDependencies: { + '@tailwindcss/postcss': '^4.1.11', + '@types/node': '^24', + '@types/react': '^19.1', + '@types/react-dom': '^19.1', + '@vitnode/eslint-config': `^${pkg.version}`, + ...(eslint + ? { + eslint: '^9.30.1', + 'prettier-plugin-tailwindcss': '^0.6.12', + prettier: '^3.6.2', + } + : {}), + 'react-email': '^4.0.17', + turbo: '^2.5.4', + tailwindcss: '^4.1.11', + 'tw-animate-css': '^1.3.5', + typescript: '^5.8.3', + }, + packageManager: `${packageManager}@${availablePackageManagers[packageManager]}`, + }; + + await writeFile( + join(root, 'package.json'), + JSON.stringify(packageJson, null, 2), + ); + } else if (mode === 'apiMonorepo') { + const rootPackageJson: PackageJSON = { + name: appName, + private: true, + scripts: { + 'db:migrate': 'turbo db:migrate', + 'db:push': 'turbo db:push', + build: 'turbo build', + start: 'turbo start', + dev: ' turbo dev', + lint: 'turbo lint', + 'lint:fix': 'turbo lint:fix', + }, + devDependencies: { + '@types/node': '^24', + '@vitnode/eslint-config': `^${pkg.version}`, + ...(eslint + ? { + 'prettier-plugin-tailwindcss': '^0.6.12', + prettier: '^3.6.2', + } + : {}), + turbo: '^2.5.4', + typescript: '^5.8.3', + zod: '^3.25.74', + }, + packageManager: `${packageManager}@${availablePackageManagers[packageManager]}`, + workspaces: ['apps/*', 'plugins/*'], + }; + + await writeFile( + join(root, 'package.json'), + JSON.stringify(rootPackageJson, null, 2), + ); + + await writeFile( + join(root, 'apps', 'api', 'package.json'), + JSON.stringify(apiPackageJson, null, 2), + ); + + const webPackageJson: PackageJSON = { + name: 'web', + version: '0.1.0', + private: true, + type: 'module', + scripts: { + dev: 'vitnode init && next dev --turbopack', + build: 'next build --turbopack', + start: 'next start', + lint: 'eslint .', + 'lint:fix': 'eslint . --fix', + }, + dependencies: { + '@vitnode/core': `^${pkg.version}`, + 'babel-plugin-react-compiler': '19.1.0-rc.2', + 'lucide-react': '^0.525.0', + next: '^15.3.5', + 'next-intl': '^4.3.4', + react: '^19.1', + 'react-dom': '^19.1', + 'react-hook-form': '^7.60.0', + sonner: '^2.0.6', + }, + devDependencies: { + '@playwright/test': '^1.53.2', + '@tailwindcss/postcss': '^4.1.11', + '@types/mdx': '^2.0.13', + '@types/node': '^24.0.10', + '@types/react': '^19.1', + '@types/react-dom': '^19.1', + '@vitnode/eslint-config': `^${pkg.version}`, + 'class-variance-authority': '^0.7.1', + ...(eslint + ? { + eslint: '^9.30.1', + } + : {}), + postcss: '^8.5.6', + 'react-email': '^4.0.17', + tailwindcss: '^4.1.11', + 'tw-animate-css': '^1.3.5', + typescript: '^5.8.3', + zod: '^3.25.74', + }, + }; + + await writeFile( + join(root, 'apps', 'web', 'package.json'), + JSON.stringify(webPackageJson, null, 2), + ); + } else if (mode === 'onlyApi') { + await writeFile( + join(root, 'package.json'), + JSON.stringify(apiPackageJson, null, 2), + ); + } }; diff --git a/packages/create-vitnode-app/src/create/create-vitnode.ts b/packages/create-vitnode-app/src/create/create-vitnode.ts index a25729d2f..a56972a7a 100644 --- a/packages/create-vitnode-app/src/create/create-vitnode.ts +++ b/packages/create-vitnode-app/src/create/create-vitnode.ts @@ -22,6 +22,7 @@ export const createVitNode = async ({ eslint, install, docker, + mode, }: CreateCliReturn & { appName: string; root: string; @@ -45,11 +46,44 @@ export const createVitNode = async ({ if (!isFolderEmpty(root, appName)) { process.exit(1); } + const monorepoStructure = { + api: join(root, 'apps', 'api'), + web: join(root, 'apps', 'web'), + }; + + if (mode === 'apiMonorepo') { + spinner.text = 'Preparing monorepo structure...'; + // Create api, web folders + await Promise.all([ + mkdir(monorepoStructure.api, { recursive: true }), + mkdir(monorepoStructure.web, { recursive: true }), + ]); + } spinner.text = 'Copying files...'; - await cp(join(templatePath, 'root'), root, { - recursive: true, - }); + if (mode === 'singleApp') { + await Promise.all([ + cp(join(templatePath, 'root'), root, { + recursive: true, + }), + cp(join(templatePath, 'api-single-app'), root, { + recursive: true, + }), + ]); + } else if (mode === 'apiMonorepo') { + await Promise.all([ + cp(join(templatePath, 'root'), monorepoStructure.web, { + recursive: true, + }), + cp(join(templatePath, 'api'), monorepoStructure.api, { + recursive: true, + }), + ]); + } else if (mode === 'onlyApi') { + await cp(join(templatePath, 'api'), root, { + recursive: true, + }); + } if (eslint) { spinner.text = 'Copying eslint files...'; @@ -58,7 +92,6 @@ export const createVitNode = async ({ }); } - // Rename special files spinner.text = 'Renaming special files...'; await rename(join(root, '.gitignore_template'), join(root, '.gitignore')); @@ -69,6 +102,7 @@ export const createVitNode = async ({ packageManager, eslint, docker, + mode, }); if (docker) { diff --git a/packages/create-vitnode-app/src/helpers/packages-json.ts b/packages/create-vitnode-app/src/helpers/packages-json.ts index 240b6954e..90febe98f 100644 --- a/packages/create-vitnode-app/src/helpers/packages-json.ts +++ b/packages/create-vitnode-app/src/helpers/packages-json.ts @@ -9,6 +9,6 @@ export interface PackageJSON { private: boolean; scripts?: RecordTest email
', - // to: 'ithereplay@gmail.com', - // subject: 'Test Email', - // }); + handler: async c => { + await c.get('email').send({ + to: 'ithereplay@gmail.com', + subject: 'Test Email', + content: 'This is a test email', + }); // throw new Error('Test error'); - c.get('log').warn('This is a test warn log'); + await c.get('log').warn('This is a test warn log'); return c.text('test'); }, diff --git a/packages/vitnode/src/emails/default-template.tsx b/packages/vitnode/src/emails/default-template.tsx new file mode 100644 index 000000000..a4b69d546 --- /dev/null +++ b/packages/vitnode/src/emails/default-template.tsx @@ -0,0 +1,124 @@ +import { + Body, + Button, + Container, + Head, + Heading, + Html, + Img, + Link, + Preview, + Section, + Tailwind, + Text, +} from '@react-email/components'; +import { createTranslator } from 'next-intl'; + +import { CONFIG } from '../lib/config'; + +interface DefaultTemplateEmailProps { + children: React.ReactNode; + head?: React.ReactNode; + logo?: { + className?: string; + src: Blob | string; + }; + metadata: { + shortTitle?: string; + title: string; + url: string; + }; + previewText?: string; +} + +export default function DefaultTemplateEmail({ + previewText, + head, + children, + logo, + metadata, +}: DefaultTemplateEmailProps) { + const intl = createTranslator({ + messages: { + email: { + previewText: 'This is a preview text for the email template.', + }, + }, + locale: 'en', + }); + + return ( + + {head} + {previewText &&
+ ) : (
+