tambahan
This commit is contained in:
35
bin/g3n.ts
35
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 <name>
|
||||
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<void> {
|
||||
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);
|
||||
|
||||
160
bin/src/app-create.ts
Normal file
160
bin/src/app-create.ts
Normal file
@@ -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 (
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
`
|
||||
|
||||
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 <MantineProvider>
|
||||
<AppRoutes />
|
||||
</MantineProvider>;
|
||||
}
|
||||
`
|
||||
|
||||
const homeTemplate = `
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Home</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
`
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<h1>404 Not Found</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
|
||||
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 <<EOF > postcss.config.js
|
||||
${postCssTemplate}
|
||||
EOF
|
||||
echo "postcss.config.js"
|
||||
cat <<EOF > src/App.tsx
|
||||
${appTemplate}
|
||||
EOF
|
||||
echo "src/App.tsx"
|
||||
|
||||
cat <<EOF > src/AppRoutes.tsx
|
||||
${appRoutesTemplate}
|
||||
EOF
|
||||
echo "src/AppRoutes.tsx"
|
||||
|
||||
mkdir src/pages
|
||||
echo "mkdir src/pages"
|
||||
cat <<EOF > src/pages/Home.tsx
|
||||
${homeTemplate}
|
||||
EOF
|
||||
echo "src/pages/Home.tsx"
|
||||
|
||||
cat <<EOF > src/index.tsx
|
||||
${serverTemplate}
|
||||
EOF
|
||||
echo "src/index.tsx"
|
||||
|
||||
cat <<EOF > 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());
|
||||
}
|
||||
41
bin/src/code.ts
Normal file
41
bin/src/code.ts
Normal file
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
35
bun.lock
35
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=="],
|
||||
}
|
||||
}
|
||||
|
||||
1
compose_dir.txt
Normal file
1
compose_dir.txt
Normal file
@@ -0,0 +1 @@
|
||||
"https://cld-dkr-makuro-seafile.wibudev.com/seafhttp/files/03c405b3-1ff0-4dff-9283-e18832a7f8d4/compose_dir.txt"
|
||||
2
index.ts
2
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";
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
2
x.sh
Normal file
2
x.sh
Normal file
@@ -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"
|
||||
167
x.ts
167
x.ts
@@ -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<void> {
|
||||
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<FrpConfig> {
|
||||
await ensureConfigFile();
|
||||
|
||||
const raw = await fs.readFile(CONFIG_FILE, "utf8");
|
||||
const lines = raw
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const conf: Record<string, string> = {};
|
||||
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<ProxyResponse> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user