diff --git a/bin/g3n.ts b/bin/g3n.ts index e6410f3..d0e9cbe 100755 --- a/bin/g3n.ts +++ b/bin/g3n.ts @@ -1,15 +1,18 @@ #!/usr/bin/env bun import minimist from "minimist"; +import os from "os"; import path from "path"; -import { generateEnvTypes } from "../generate/env.generate"; -import checkPort from "./src/port"; -import route from "./src/route"; import compose from "./src/compose"; import generateDockerfile from "./src/docker-file"; import frp from "./src/frp"; +import { generateEnvTypes } from "./src/generate/env.generate"; +import checkPort from "./src/port"; +import route from "./src/route"; import { version } from '../package.json' assert { type: 'json' }; +import appCreate from "./src/app-create"; + interface CheckPortResult { port: number; @@ -36,6 +39,7 @@ Commands: compose Generate compose.yml from name docker-file Generate Dockerfile frp Show frp proxy list + app-init Generate app-init.ts Options: --env Path ke file .env (default: .env) @@ -51,10 +55,28 @@ Examples: g3n compose g3n docker-file g3n frp + g3n app-init Version: ${version} `; +const g3nConf = path.join(os.homedir(), ".g3n.conf"); + +if (!(await Bun.file(g3nConf).exists())) { + const conf = ` + # CODE + CODE_TOKEN= + + # FRP + FRP_HOST= + FRP_USER= + FRP_SECRET= + ` + Bun.write(g3nConf, conf); + console.log(`✅ G3N config created at ${g3nConf}`); +} + + // Parse CLI arguments const args = minimist(process.argv.slice(2)); @@ -90,6 +112,13 @@ async function main(): Promise { process.exit(1); }); break; + case "app-create": + if (!name) { + console.error("❌ App name is required"); + return; + } + appCreate({ appName: name }); + break; default: console.error(HELP_TEXT); diff --git a/bin/src/app-create.ts b/bin/src/app-create.ts new file mode 100644 index 0000000..a64c256 --- /dev/null +++ b/bin/src/app-create.ts @@ -0,0 +1,160 @@ +import ora from "ora" + +const appRoutesTemplate = ` +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import Home from "./pages/Home"; +import NotFound from "./pages/NotFound"; + +export default function AppRoutes() { + return ( + + + } /> + } /> + + + ); +} + + +` + +const postCssTemplate = ` +module.exports = { + plugins: { + 'postcss-preset-mantine': {}, + 'postcss-simple-vars': { + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }, + }, +}; +` + +const appTemplate = ` +import '@mantine/core/styles.css'; + +import { MantineProvider } from '@mantine/core'; +import AppRoutes from './AppRoutes'; + +export function App() { + return + + ; +} +` + +const homeTemplate = ` +export default function Home() { + return ( +
+

Home

+
+ ); +} +` + +const serverTemplate = ` +import Elysia from "elysia"; +import Swagger from "@elysiajs/swagger"; +import html from "./index.html" + +const Docs = new Elysia({}) + .use(Swagger({ + path: "/docs", + })) + + +const Api = new Elysia({ + prefix: "/api", +}) + .use(Docs) + .post("/hello", () => "Hello, world!") + + +const app = new Elysia() + .use(Api) + .get("/*", html) + .listen(3000, () => { + console.log("Server running at http://localhost:3000"); + }); + + +export type Server = typeof app; +` + +const notFoundTemplate = ` +export default function NotFound() { + return ( +
+

404 Not Found

+
+ ); +} +` + + + +const cmd = (appName: string) => ` +bun init --react ${appName} +echo "init react" +cd ${appName} +echo "cd ${appName}" +bun add react-router-dom +echo "add react-router-dom" +bun add @mantine/core @mantine/hooks +echo "add @mantine/core @mantine/hooks" +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 +echo "add elysia @elysiajs/cors @elysiajs/swagger @elysiajs/eden" +cat < postcss.config.js +${postCssTemplate} +EOF +echo "postcss.config.js" +cat < src/App.tsx +${appTemplate} +EOF +echo "src/App.tsx" + +cat < src/AppRoutes.tsx +${appRoutesTemplate} +EOF +echo "src/AppRoutes.tsx" + +mkdir src/pages +echo "mkdir src/pages" +cat < src/pages/Home.tsx +${homeTemplate} +EOF +echo "src/pages/Home.tsx" + +cat < src/index.tsx +${serverTemplate} +EOF +echo "src/index.tsx" + +cat < src/pages/NotFound.tsx +${notFoundTemplate} +EOF +echo "src/pages/NotFound.tsx" + +rm src/APITester.tsx + +ls + +echo "✅ done" +` + + +export default async function appCreate({ appName }: { appName: string }) { + const spinner = ora(`Creating app ${appName}...`).start(); + const { stdout } = Bun.spawnSync(["bash", "-c", cmd(appName)]); + spinner.stop(); + console.log(stdout.toString()); +} diff --git a/bin/src/code.ts b/bin/src/code.ts new file mode 100644 index 0000000..ec765ff --- /dev/null +++ b/bin/src/code.ts @@ -0,0 +1,41 @@ +import path from "path"; +import ora from "ora"; + +async function query({ question }: { question: string }) { + const spinner = ora("Processing...").start(); + const response = await fetch( + "https://cloud-aiflow.wibudev.com/api/v1/prediction/4da85628-c638-43d3-9491-4cd0a7e6b1b8", + { + headers: { + Authorization: "Bearer v3WdPjn61bNDsEYCO5_LYPRs16ICKjpQE6lF60DjpNo", + "Content-Type": "application/json" + }, + method: "POST", + body: JSON.stringify({ question }) + } + ); + const result: { text: string } = await response.json() as { text: string }; + spinner.stop(); + return result.text; +} + + +async function code({ sourcePath }: { sourcePath: string }) { + const file = Bun.file(sourcePath); + const content = await file.text(); + const result = await query({ + question: content + }); + + const output = `${path.dirname(sourcePath)}/${path.basename(sourcePath, path.extname(sourcePath))}.md`; + + Bun.write(output, result); + console.log(`✅ Code generated at ${output}`); + +} + +if (import.meta.main) { + code({ sourcePath: "bin/g3n.ts" }); +} + +export default code; \ No newline at end of file diff --git a/bin/src/frp.ts b/bin/src/frp.ts index 9bb69dc..88058c5 100644 --- a/bin/src/frp.ts +++ b/bin/src/frp.ts @@ -3,7 +3,7 @@ import { promises as fs } from "fs"; import * as os from "os"; import * as path from "path"; -const CONFIG_FILE = path.join(os.homedir(), ".frpdev.conf"); +const CONFIG_FILE = path.join(os.homedir(), ".g3n.conf"); interface FrpConfig { FRP_HOST: string; diff --git a/generate/env.generate.ts b/bin/src/generate/env.generate.ts similarity index 100% rename from generate/env.generate.ts rename to bin/src/generate/env.generate.ts diff --git a/bun.lock b/bun.lock index fa87eee..648033c 100644 --- a/bun.lock +++ b/bun.lock @@ -12,6 +12,7 @@ "dedent": "^1.7.0", "dotenv": "^17.2.1", "minimist": "^1.2.8", + "ora": "^9.0.0", }, "peerDependencies": { "typescript": "^5", @@ -49,22 +50,56 @@ "@types/minimist": ["@types/minimist@1.2.5", "", {}, "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag=="], + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "cli-spinners": ["cli-spinners@3.3.0", "", {}, "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ=="], + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], "dedent": ["dedent@1.7.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ=="], "dotenv": ["dotenv@17.2.1", "", {}, "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ=="], + "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + + "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + "log-symbols": ["log-symbols@7.0.1", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "ora": ["ora@9.0.0", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.2.2", "string-width": "^8.1.0", "strip-ansi": "^7.1.2" } }, "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], + + "string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + + "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], } } diff --git a/compose_dir.txt b/compose_dir.txt new file mode 100644 index 0000000..05f811f --- /dev/null +++ b/compose_dir.txt @@ -0,0 +1 @@ +"https://cld-dkr-makuro-seafile.wibudev.com/seafhttp/files/03c405b3-1ff0-4dff-9283-e18832a7f8d4/compose_dir.txt" \ No newline at end of file diff --git a/index.ts b/index.ts index 7a76715..f4432bf 100755 --- a/index.ts +++ b/index.ts @@ -1,6 +1,6 @@ #!/usr/bin/env bun import minimist from "minimist"; -import { generateEnvTypes } from "./generate/env.generate.js"; +import { generateEnvTypes } from "./bin/src/generate/env.generate.ts"; import path from "path"; import net from "net"; diff --git a/package.json b/package.json index fc4c4dc..9481486 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "g3n", - "version": "1.0.9", + "version": "1.0.13", "type": "module", "bin": { "g3n": "./bin/g3n.ts" @@ -16,6 +16,7 @@ "@types/minimist": "^1.2.5", "dedent": "^1.7.0", "dotenv": "^17.2.1", - "minimist": "^1.2.8" + "minimist": "^1.2.8", + "ora": "^9.0.0" } } diff --git a/x.sh b/x.sh new file mode 100644 index 0000000..a9a98e8 --- /dev/null +++ b/x.sh @@ -0,0 +1,2 @@ +curl -o compose_dir.txt -H "Authorization: Bearer fa49bf1774cad2ec89d2882ae2c6ac1f5d7df445" \ + "https://cld-dkr-makuro-seafile.wibudev.com/api2/repos/e23626dc-cc18-4bb8-8fbc-d103b7d33bc8/file/?p=/compose_dir.txt&op=download" diff --git a/x.ts b/x.ts deleted file mode 100644 index aaf762a..0000000 --- a/x.ts +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env bun -import { promises as fs } from "fs"; -import * as os from "os"; -import * as path from "path"; - -const CONFIG_FILE = path.join(os.homedir(), ".frpdev.conf"); - -interface FrpConfig { - FRP_HOST: string; - FRP_PORT: string; - FRP_USER: string; - FRP_SECRET: string; - FRP_PROTO: string; -} - -interface ProxyConf { - type?: string; - remotePort?: number; - subdomain?: string; - customDomains?: string[]; -} - -interface Proxy { - name?: string; - status?: string; - conf?: ProxyConf; -} - -interface ProxyResponse { - proxies?: Proxy[]; -} - -async function ensureConfigFile(): Promise { - try { - await fs.access(CONFIG_FILE); - } catch { - const template = ` -FRP_HOST="" -FRP_USER="" -FRP_SECRET="" -`; - console.error(`❌ Config not found. Template created at: ${CONFIG_FILE}`); - console.log(template); - process.exit(1); - } -} - -async function loadConfig(): Promise { - await ensureConfigFile(); - - const raw = await fs.readFile(CONFIG_FILE, "utf8"); - const lines = raw - .split("\n") - .map((line) => line.trim()) - .filter(Boolean); - - const conf: Record = {}; - for (const line of lines) { - const [key, ...rest] = line.split("="); - if (!key) continue; - let value = rest.join("=").trim(); - - if (value.startsWith('"') && value.endsWith('"')) { - value = value.slice(1, -1); - } - conf[key] = value; - } - - return { - FRP_HOST: conf.FRP_HOST || "", - FRP_PORT: "443", - FRP_USER: conf.FRP_USER || "", - FRP_SECRET: conf.FRP_SECRET || "", - FRP_PROTO: "https", - }; -} - -async function fetchFrp(config: FrpConfig, url: string): Promise { - const fullUrl = `${config.FRP_PROTO}://${config.FRP_HOST}:${config.FRP_PORT}${url}`; - - try { - const resp = await fetch(fullUrl, { - headers: { - Authorization: - "Basic " + - Buffer.from(`${config.FRP_USER}:${config.FRP_SECRET}`).toString("base64"), - }, - }); - - if (!resp.ok) return { proxies: [] }; - - return (await resp.json()) as ProxyResponse; - } catch { - return { proxies: [] }; - } -} - -function sortProxies(proxies: Proxy[]): Proxy[] { - return [...proxies].sort((a, b) => { - const order = (status?: string) => - status?.toLowerCase() === "online" || status?.toLowerCase() === "running" - ? 0 - : 1; - return order(a.status) - order(b.status); - }); -} - -function formatTable(headers: string[], rows: string[][]): string { - const allRows = [headers, ...rows]; - const colWidths = headers.map((_, i) => - Math.max(...allRows.map((row) => (row[i] || "").length)), - ); - - return allRows - .map((row) => - row.map((cell, i) => (cell || "").padEnd(colWidths[i] ?? 0)).join(" ").trimEnd(), - ) - .join("\n"); -} - -async function printTable( - title: string, - headers: string[], - rows: string[][], -): Promise { - console.log(`========== ${title} ==========`); - - if (rows.length === 0) { - console.log("No proxies found.\n"); - return; - } - - console.log(formatTable(headers, rows)); - console.log(); -} - -async function main(): Promise { - const config = await loadConfig(); - - const [tcpResp, httpResp] = await Promise.all([ - fetchFrp(config, "/api/proxy/tcp"), - fetchFrp(config, "/api/proxy/http"), - ]); - - const tcpRows: string[][] = sortProxies(tcpResp.proxies || []).map((p) => [ - p.name ?? "-", - p.status ?? "-", - p.conf?.remotePort?.toString() ?? "-", - ]); - await printTable("TCP PROXIES", ["NAME", "STATUS", "PORT"], tcpRows); - - const httpRows: string[][] = sortProxies(httpResp.proxies || []).map((p) => [ - p.name ?? "-", - p.status ?? "-", - Array.isArray(p.conf?.customDomains) ? p.conf.customDomains.join(",") : "", - ]); - await printTable( - "HTTP PROXIES", - ["NAME", "STATUS", "CUSTOM_DOMAIN"], - httpRows, - ); -} - -main().catch((err) => { - console.error("❌ Error:", err); - process.exit(1); -});