feat: add app-create and frp commands
This commit is contained in:
@@ -71,6 +71,7 @@ if (!(await Bun.file(g3nConf).exists())) {
|
|||||||
FRP_HOST=
|
FRP_HOST=
|
||||||
FRP_USER=
|
FRP_USER=
|
||||||
FRP_SECRET=
|
FRP_SECRET=
|
||||||
|
FRP_AUTH_TOKEN=
|
||||||
`
|
`
|
||||||
Bun.write(g3nConf, conf);
|
Bun.write(g3nConf, conf);
|
||||||
console.log(`✅ G3N config created at ${g3nConf}`);
|
console.log(`✅ G3N config created at ${g3nConf}`);
|
||||||
|
|||||||
@@ -60,32 +60,35 @@ export default function Home() {
|
|||||||
`
|
`
|
||||||
|
|
||||||
const serverTemplate = `
|
const serverTemplate = `
|
||||||
|
|
||||||
import Elysia from "elysia";
|
import Elysia from "elysia";
|
||||||
import Swagger from "@elysiajs/swagger";
|
import Swagger from "@elysiajs/swagger";
|
||||||
import html from "./index.html"
|
import html from "./index.html"
|
||||||
|
import Darmasaba from "./server/routes/darmasaba";
|
||||||
|
import apiAuth from "./server/middlewares/apiAuth";
|
||||||
|
|
||||||
const Docs = new Elysia({})
|
const Docs = new Elysia()
|
||||||
.use(Swagger({
|
.use(Swagger({
|
||||||
path: "/docs",
|
path: "/docs",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
const Api = new Elysia({
|
const Api = new Elysia({
|
||||||
prefix: "/api",
|
prefix: "/api",
|
||||||
})
|
})
|
||||||
.use(Docs)
|
.use(apiAuth)
|
||||||
.post("/hello", () => "Hello, world!")
|
.use(Darmasaba)
|
||||||
|
|
||||||
|
|
||||||
const app = new Elysia()
|
const app = new Elysia()
|
||||||
.use(Api)
|
.use(Api)
|
||||||
.get("/*", html)
|
.use(Docs)
|
||||||
|
.get("*", html)
|
||||||
.listen(3000, () => {
|
.listen(3000, () => {
|
||||||
console.log("Server running at http://localhost:3000");
|
console.log("Server running at http://localhost:3000");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
export type Server = typeof app;
|
export type Server = typeof app;
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
const notFoundTemplate = `
|
const notFoundTemplate = `
|
||||||
@@ -98,6 +101,104 @@ export default function NotFound() {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const prismaTemplate = `
|
||||||
|
import { PrismaClient } from 'generated/prisma'
|
||||||
|
|
||||||
|
const globalForPrisma = globalThis as unknown as {
|
||||||
|
prisma: PrismaClient | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
globalForPrisma.prisma = prisma
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const apiAuthTemplate = `
|
||||||
|
//* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import jwt, { type JWTPayloadSpec } from '@elysiajs/jwt'
|
||||||
|
import Elysia from 'elysia'
|
||||||
|
import { prisma } from '../lib/prisma'
|
||||||
|
|
||||||
|
const secret = process.env.JWT_SECRET
|
||||||
|
|
||||||
|
export default function apiAuth(app: Elysia) {
|
||||||
|
if (!secret) {
|
||||||
|
throw new Error('JWT_SECRET is not defined')
|
||||||
|
}
|
||||||
|
return app
|
||||||
|
.use(
|
||||||
|
jwt({
|
||||||
|
name: 'jwt',
|
||||||
|
secret,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.derive(async ({ cookie, headers, jwt }) => {
|
||||||
|
let token: string | undefined
|
||||||
|
|
||||||
|
if (cookie?.token?.value) {
|
||||||
|
token = cookie.token.value as any
|
||||||
|
}
|
||||||
|
if (headers['x-token']?.startsWith('Bearer ')) {
|
||||||
|
token = (headers['x-token'] as string).slice(7)
|
||||||
|
}
|
||||||
|
if (headers['authorization']?.startsWith('Bearer ')) {
|
||||||
|
token = (headers['authorization'] as string).slice(7)
|
||||||
|
}
|
||||||
|
|
||||||
|
let user: null | Awaited<ReturnType<typeof prisma.user.findUnique>> = null
|
||||||
|
if (token) {
|
||||||
|
try {
|
||||||
|
const decoded = (await jwt.verify(token)) as JWTPayloadSpec
|
||||||
|
if (decoded.sub) {
|
||||||
|
user = await prisma.user.findUnique({
|
||||||
|
where: { id: decoded.sub as string },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[SERVER][apiAuth] Invalid token', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { user }
|
||||||
|
})
|
||||||
|
.onBeforeHandle(({ user, set }) => {
|
||||||
|
if (!user) {
|
||||||
|
set.status = 401
|
||||||
|
return { error: 'Unauthorized' }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
const envFileTemplate = (appName: string) => `
|
||||||
|
DATABASE_URL="postgresql://bip:Production_123@localhost:5432/${appName}?schema=public"
|
||||||
|
JWT_SECRET=super_sangat_rahasia_sekali
|
||||||
|
`
|
||||||
|
|
||||||
|
const prismaSchemaTemplate = `
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
output = "../generated/prisma"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String?
|
||||||
|
email String? @unique
|
||||||
|
password String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
const cmd = (appName: string) => `
|
const cmd = (appName: string) => `
|
||||||
@@ -105,45 +206,78 @@ bun init --react ${appName}
|
|||||||
echo "init react"
|
echo "init react"
|
||||||
cd ${appName}
|
cd ${appName}
|
||||||
echo "cd ${appName}"
|
echo "cd ${appName}"
|
||||||
|
|
||||||
|
echo "install dependencies"
|
||||||
bun add react-router-dom
|
bun add react-router-dom
|
||||||
echo "add react-router-dom"
|
|
||||||
bun add @mantine/core @mantine/hooks
|
bun add @mantine/core @mantine/hooks
|
||||||
echo "add @mantine/core @mantine/hooks"
|
|
||||||
bun add --dev postcss postcss-preset-mantine postcss-simple-vars
|
bun add --dev postcss postcss-preset-mantine postcss-simple-vars
|
||||||
echo "add --dev postcss postcss-preset-mantine postcss-simple-vars"
|
bun add elysia @elysiajs/cors @elysiajs/swagger @elysiajs/eden @elysiajs/jwt prisma @prisma/client
|
||||||
bun add elysia @elysiajs/cors @elysiajs/swagger @elysiajs/eden
|
|
||||||
echo "add elysia @elysiajs/cors @elysiajs/swagger @elysiajs/eden"
|
echo "init prisma"
|
||||||
|
bun x prisma init
|
||||||
|
|
||||||
|
echo "generate file ..."
|
||||||
|
|
||||||
|
echo "generate postcss.config.js"
|
||||||
cat <<EOF > postcss.config.js
|
cat <<EOF > postcss.config.js
|
||||||
${postCssTemplate}
|
${postCssTemplate}
|
||||||
EOF
|
EOF
|
||||||
echo "postcss.config.js"
|
|
||||||
|
echo "generate src/App.tsx"
|
||||||
cat <<EOF > src/App.tsx
|
cat <<EOF > src/App.tsx
|
||||||
${appTemplate}
|
${appTemplate}
|
||||||
EOF
|
EOF
|
||||||
echo "src/App.tsx"
|
|
||||||
|
|
||||||
|
echo "generate src/AppRoutes.tsx"
|
||||||
cat <<EOF > src/AppRoutes.tsx
|
cat <<EOF > src/AppRoutes.tsx
|
||||||
${appRoutesTemplate}
|
${appRoutesTemplate}
|
||||||
EOF
|
EOF
|
||||||
echo "src/AppRoutes.tsx"
|
|
||||||
|
|
||||||
|
echo "create dir src/pages"
|
||||||
mkdir src/pages
|
mkdir src/pages
|
||||||
echo "mkdir src/pages"
|
|
||||||
|
echo "generate src/pages/Home.tsx"
|
||||||
cat <<EOF > src/pages/Home.tsx
|
cat <<EOF > src/pages/Home.tsx
|
||||||
${homeTemplate}
|
${homeTemplate}
|
||||||
EOF
|
EOF
|
||||||
echo "src/pages/Home.tsx"
|
|
||||||
|
|
||||||
|
echo "generate src/index.tsx"
|
||||||
cat <<EOF > src/index.tsx
|
cat <<EOF > src/index.tsx
|
||||||
${serverTemplate}
|
${serverTemplate}
|
||||||
EOF
|
EOF
|
||||||
echo "src/index.tsx"
|
|
||||||
|
|
||||||
|
echo "generate src/pages/NotFound.tsx"
|
||||||
cat <<EOF > src/pages/NotFound.tsx
|
cat <<EOF > src/pages/NotFound.tsx
|
||||||
${notFoundTemplate}
|
${notFoundTemplate}
|
||||||
EOF
|
EOF
|
||||||
echo "src/pages/NotFound.tsx"
|
|
||||||
|
|
||||||
|
echo "create dir src/server/lib"
|
||||||
|
mkdir -p src/server/lib
|
||||||
|
|
||||||
|
echo "generate src/server/lib/prisma.ts"
|
||||||
|
cat <<EOF > src/server/lib/prisma.ts
|
||||||
|
${prismaTemplate}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "create dir server/middlewares"
|
||||||
|
mkdir -p server/middlewares
|
||||||
|
|
||||||
|
echo "generate server/middlewares/apiAuth.ts"
|
||||||
|
cat <<EOF > server/middlewares/apiAuth.ts
|
||||||
|
${apiAuthTemplate}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "generate .env"
|
||||||
|
cat <<EOF > .env
|
||||||
|
${envFileTemplate(appName)}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "generate prisma/schema.prisma"
|
||||||
|
cat <<EOF > prisma/schema.prisma
|
||||||
|
${prismaSchemaTemplate}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "remove src/APITester.tsx"
|
||||||
rm src/APITester.tsx
|
rm src/APITester.tsx
|
||||||
|
|
||||||
ls
|
ls
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ interface FrpConfig {
|
|||||||
FRP_USER: string;
|
FRP_USER: string;
|
||||||
FRP_SECRET: string;
|
FRP_SECRET: string;
|
||||||
FRP_PROTO: string;
|
FRP_PROTO: string;
|
||||||
|
FRP_AUTH_TOKEN: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProxyConf {
|
interface ProxyConf {
|
||||||
@@ -30,17 +31,19 @@ interface ProxyResponse {
|
|||||||
proxies?: Proxy[];
|
proxies?: Proxy[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const templateConfig = `
|
||||||
|
FRP_HOST=""
|
||||||
|
FRP_USER=""
|
||||||
|
FRP_SECRET=""
|
||||||
|
FRP_AUTH_TOKEN=""`;
|
||||||
|
|
||||||
async function ensureConfigFile(): Promise<void> {
|
async function ensureConfigFile(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await fs.access(CONFIG_FILE);
|
await fs.access(CONFIG_FILE);
|
||||||
} catch {
|
} catch {
|
||||||
const template = `
|
|
||||||
FRP_HOST=""
|
|
||||||
FRP_USER=""
|
|
||||||
FRP_SECRET=""
|
|
||||||
`;
|
|
||||||
console.error(`❌ Config not found. Template created at: ${CONFIG_FILE}`);
|
console.error(`❌ Config not found. Template created at: ${CONFIG_FILE}`);
|
||||||
console.log(template);
|
console.log(templateConfig);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,12 +69,19 @@ async function loadConfig(): Promise<FrpConfig> {
|
|||||||
conf[key] = value;
|
conf[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!conf.FRP_HOST || !conf.FRP_USER || !conf.FRP_SECRET || !conf.FRP_AUTH_TOKEN) {
|
||||||
|
console.error(`❌ Config not found. Template created at: ${CONFIG_FILE}`);
|
||||||
|
console.log(raw);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
FRP_HOST: conf.FRP_HOST || "",
|
FRP_HOST: conf.FRP_HOST || "",
|
||||||
FRP_PORT: "443",
|
FRP_PORT: "443",
|
||||||
FRP_USER: conf.FRP_USER || "",
|
FRP_USER: conf.FRP_USER || "",
|
||||||
FRP_SECRET: conf.FRP_SECRET || "",
|
FRP_SECRET: conf.FRP_SECRET || "",
|
||||||
FRP_PROTO: "https",
|
FRP_PROTO: "https",
|
||||||
|
FRP_AUTH_TOKEN: conf.FRP_AUTH_TOKEN || "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user