Compare commits
6 Commits
70e5e5eddf
...
1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 99e4a64b5a | |||
| 498eb05514 | |||
| 8a5cc34bb4 | |||
| 006a22cd7b | |||
| 2cfc9cd7fa | |||
| 2125239d42 |
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"sync": {
|
||||||
|
"region": "static-files",
|
||||||
|
"configPath": "test/static-files.toml"
|
||||||
|
},
|
||||||
"deployments": [
|
"deployments": [
|
||||||
{
|
{
|
||||||
"path": "test/static-files",
|
"path": "test/static-files",
|
||||||
|
|||||||
BIN
cli/bun.lockb
BIN
cli/bun.lockb
Binary file not shown.
0
cli/index.ts
Normal file → Executable file
0
cli/index.ts
Normal file → Executable file
@@ -1,14 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "roadsign-cli",
|
"name": "roadsign-cli",
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"repository": "https://github.com/solsynth/roadsign",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rimraf dist && rollup -c rollup.config.js"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rdcli": "./dist/index.cjs"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^28.0.0",
|
||||||
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||||
|
"@rollup/plugin-typescript": "^12.1.0",
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/cli-progress": "^3.11.6",
|
"@types/cli-progress": "^3.11.6",
|
||||||
"@types/figlet": "^1.5.8"
|
"@types/figlet": "^1.5.8",
|
||||||
|
"rimraf": "^6.0.1",
|
||||||
|
"rollup": "^4.24.0",
|
||||||
|
"rollup-plugin-typescript2": "^0.36.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5.0.0"
|
"typescript": "^5.6.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
|
|||||||
22
cli/rollup.config.js
Normal file
22
cli/rollup.config.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import resolve from "@rollup/plugin-node-resolve"
|
||||||
|
import commonjs from "@rollup/plugin-commonjs"
|
||||||
|
import typescript from "@rollup/plugin-typescript"
|
||||||
|
import json from "@rollup/plugin-json"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: "index.ts",
|
||||||
|
output: {
|
||||||
|
banner: "#!/usr/bin/env node",
|
||||||
|
file: "dist/index.cjs",
|
||||||
|
format: "cjs",
|
||||||
|
inlineDynamicImports: true,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
resolve(),
|
||||||
|
commonjs(),
|
||||||
|
json(),
|
||||||
|
typescript({
|
||||||
|
tsconfig: "./tsconfig.json"
|
||||||
|
})
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Command, Option, type Usage } from "clipanion"
|
import { Command, Option, type Usage } from "clipanion"
|
||||||
import { RsConfig } from "../utils/config.ts"
|
import { RsConfig, type RsConfigServerData } from "../utils/config.ts"
|
||||||
import { createAuthHeader } from "../utils/auth.ts"
|
import { createAuthHeader } from "../utils/auth.ts"
|
||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
import ora from "ora"
|
import ora from "ora"
|
||||||
@@ -37,6 +37,77 @@ export class InfoCommand extends Command {
|
|||||||
return uptimeParts.join(", ")
|
return uptimeParts.join(", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchOverview(server: RsConfigServerData) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${server.url}/cgi/stats`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: createAuthHeader(server.credential)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(await res.text())
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: any = await res.json()
|
||||||
|
this.context.stdout.write('\n')
|
||||||
|
this.context.stdout.write(`\nServer stats of ${chalk.bold(this.label)}\n`)
|
||||||
|
this.context.stdout.write(` • Uptime: ${chalk.bold(InfoCommand.formatUptime(data["uptime"]))}\n`)
|
||||||
|
this.context.stdout.write(` • Traffic since last startup: ${chalk.bold(data["traffic"]["total"])}\n`)
|
||||||
|
this.context.stdout.write(` • Unique clients since last startup: ${chalk.bold(data["traffic"]["unique_client"])}\n`)
|
||||||
|
this.context.stdout.write(`\nServer info of ${chalk.bold(this.label)}\n`)
|
||||||
|
this.context.stdout.write(` • Warden Applications: ${chalk.bold(data["applications"])}\n`)
|
||||||
|
this.context.stdout.write(` • Destinations: ${chalk.bold(data["destinations"])}\n`)
|
||||||
|
this.context.stdout.write(` • Locations: ${chalk.bold(data["locations"])}\n`)
|
||||||
|
this.context.stdout.write(` • Regions: ${chalk.bold(data["regions"])}\n`)
|
||||||
|
this.context.stdout.write('\n')
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchTrace(server: RsConfigServerData) {
|
||||||
|
const res = await fetch(`${server.url}/cgi/traces`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: createAuthHeader(server.credential)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(await res.text())
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: any = await res.json()
|
||||||
|
for (const trace of data) {
|
||||||
|
const ts = new Date(trace["timestamp"]).toLocaleString()
|
||||||
|
const path = [trace["region"], trace["location"], trace["destination"]].join(" ➜ ")
|
||||||
|
const uri = trace["uri"].split("?").length == 1 ? trace["uri"] : trace["uri"].split("?")[0] + ` ${chalk.grey(`w/ query parameters`)}`
|
||||||
|
this.context.stdout.write(`${chalk.bgGrey(`[${ts}]`)} ${chalk.bold(path)} ${chalk.cyan(trace["ip_address"])} ${uri}\n`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchRegions(server: RsConfigServerData) {
|
||||||
|
const res = await fetch(`${server.url}/cgi/regions`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: createAuthHeader(server.credential)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error(await res.text())
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: any = await res.json()
|
||||||
|
this.context.stdout.write("\n\n")
|
||||||
|
for (const region of data) {
|
||||||
|
this.context.stdout.write(` • ${chalk.bgGrey('region#')}${chalk.bold(region.id)} ${chalk.gray(`(${region.locations.length} locations)`)}\n`)
|
||||||
|
for (const location of region.locations) {
|
||||||
|
this.context.stdout.write(` • ${chalk.bgGrey('location#')} ${chalk.bold(location.id)} ${chalk.gray(`(${location.destinations.length} destinations)`)}\n`)
|
||||||
|
for (const destination of location.destinations) {
|
||||||
|
this.context.stdout.write(` • ${chalk.bgGrey('destination#')}${chalk.bold(destination.id)}\n`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.context.stdout.write("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async execute() {
|
async execute() {
|
||||||
const config = await RsConfig.getInstance()
|
const config = await RsConfig.getInstance()
|
||||||
|
|
||||||
@@ -56,55 +127,21 @@ export class InfoCommand extends Command {
|
|||||||
switch (this.area) {
|
switch (this.area) {
|
||||||
case "overview":
|
case "overview":
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${server.url}/cgi/stats`, {
|
await this.fetchOverview(server)
|
||||||
headers: {
|
|
||||||
Authorization: createAuthHeader(server.credential)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error(await res.text())
|
|
||||||
}
|
|
||||||
const prefTook = performance.now() - prefStart
|
const prefTook = performance.now() - prefStart
|
||||||
spinner.succeed(`Fetching completed in ${(prefTook / 1000).toFixed(2)}s 🎉`)
|
spinner.succeed(`Fetching completed in ${(prefTook / 1000).toFixed(2)}s 🎉`)
|
||||||
|
|
||||||
const data = await res.json()
|
|
||||||
this.context.stdout.write(`\nServer stats of ${chalk.bold(this.label)}\n`)
|
|
||||||
this.context.stdout.write(`Uptime: ${chalk.bold(InfoCommand.formatUptime(data["uptime"]))}\n`)
|
|
||||||
this.context.stdout.write(`Traffic since last startup: ${chalk.bold(data["traffic"]["total"])}\n`)
|
|
||||||
this.context.stdout.write(`Unique clients since last startup: ${chalk.bold(data["traffic"]["unique_client"])}\n`)
|
|
||||||
this.context.stdout.write(`\nServer info of ${chalk.bold(this.label)}\n`)
|
|
||||||
this.context.stdout.write(`Warden Applications: ${chalk.bold(data["applications"])}\n`)
|
|
||||||
this.context.stdout.write(`Destinations: ${chalk.bold(data["destinations"])}\n`)
|
|
||||||
this.context.stdout.write(`Locations: ${chalk.bold(data["locations"])}\n`)
|
|
||||||
this.context.stdout.write(`Regions: ${chalk.bold(data["regions"])}\n`)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
spinner.fail(`Server with label ${chalk.bold(this.label)} is not running! 😢`)
|
spinner.fail(`Server with label ${chalk.bold(this.label)} is not running! 😢`)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "trace":
|
case "trace":
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${server.url}/cgi/traces`, {
|
await this.fetchTrace(server)
|
||||||
headers: {
|
|
||||||
Authorization: createAuthHeader(server.credential)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error(await res.text())
|
|
||||||
}
|
|
||||||
const prefTook = performance.now() - prefStart
|
const prefTook = performance.now() - prefStart
|
||||||
if (!this.loop) {
|
if (!this.loop) {
|
||||||
spinner.succeed(`Fetching completed in ${(prefTook / 1000).toFixed(2)}s 🎉`)
|
spinner.succeed(`Fetching completed in ${(prefTook / 1000).toFixed(2)}s 🎉`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await res.json()
|
|
||||||
for (const trace of data) {
|
|
||||||
const ts = new Date(trace["timestamp"]).toLocaleString()
|
|
||||||
const path = [trace["region"], trace["location"], trace["destination"]].join(" ➜ ")
|
|
||||||
const uri = trace["uri"].split("?").length == 1 ? trace["uri"] : trace["uri"].split("?")[0] + ` ${chalk.grey(`w/ query parameters`)}`
|
|
||||||
this.context.stdout.write(`${chalk.bgGrey(`[${ts}]`)} ${chalk.bold(path)} ${chalk.cyan(trace["ip_address"])} ${uri}\n`)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
spinner.fail(`Server with label ${chalk.bold(this.label)} is not running! 😢`)
|
spinner.fail(`Server with label ${chalk.bold(this.label)} is not running! 😢`)
|
||||||
return
|
return
|
||||||
@@ -113,12 +150,22 @@ export class InfoCommand extends Command {
|
|||||||
if (!this.loop) {
|
if (!this.loop) {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
spinner.text = 'Updating...'
|
spinner.text = "Updating..."
|
||||||
await new Promise(resolve => setTimeout(resolve, 3000))
|
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||||
this.context.stdout.write('\x1Bc')
|
this.context.stdout.write("\x1Bc")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case "regions":
|
||||||
|
try {
|
||||||
|
await this.fetchRegions(server)
|
||||||
|
const prefTook = performance.now() - prefStart
|
||||||
|
spinner.succeed(`Fetching completed in ${(prefTook / 1000).toFixed(2)}s 🎉`)
|
||||||
|
} catch (e) {
|
||||||
|
spinner.fail(`Server with label ${chalk.bold(this.label)} is not running! 😢`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
spinner.fail(chalk.red(`Info area was not exists ${chalk.bold(this.area)}...`))
|
spinner.fail(chalk.red(`Info area was not exists ${chalk.bold(this.area)}...`))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class LoginCommand extends Command {
|
|||||||
if (pingRes.status !== 200) {
|
if (pingRes.status !== 200) {
|
||||||
throw new Error(await pingRes.text())
|
throw new Error(await pingRes.text())
|
||||||
} else {
|
} else {
|
||||||
const info = await pingRes.json()
|
const info: any = await pingRes.json()
|
||||||
spinner.succeed(`Connected to ${this.host}, remote version ${info["version"]}`)
|
spinner.succeed(`Connected to ${this.host}, remote version ${info["version"]}`)
|
||||||
|
|
||||||
config.config.servers.push({
|
config.config.servers.push({
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export class ProcessCommand extends Command {
|
|||||||
|
|
||||||
const statusMapping = ["Created", "Starting", "Started", "Exited", "Failed"]
|
const statusMapping = ["Created", "Starting", "Started", "Exited", "Failed"]
|
||||||
|
|
||||||
const data = await res.json()
|
const data: any = await res.json()
|
||||||
for (const app of data) {
|
for (const app of data) {
|
||||||
table.push([app["id"], statusMapping[app["status"]], app["command"].join(" ")])
|
table.push([app["id"], statusMapping[app["status"]], app["command"].join(" ")])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as path from "node:path"
|
import * as path from "node:path"
|
||||||
import * as fs from "node:fs/promises"
|
import * as fs from "node:fs"
|
||||||
|
|
||||||
interface RsLocalConfigData {
|
interface RsLocalConfigData {
|
||||||
sync?: RsLocalConfigSyncData
|
sync?: RsLocalConfigSyncData
|
||||||
@@ -42,18 +42,18 @@ class RsLocalConfig {
|
|||||||
public async readConfig() {
|
public async readConfig() {
|
||||||
const basepath = process.cwd()
|
const basepath = process.cwd()
|
||||||
const filepath = path.join(basepath, ".roadsignrc")
|
const filepath = path.join(basepath, ".roadsignrc")
|
||||||
if (!await fs.exists(filepath)) {
|
if (!fs.existsSync(filepath)) {
|
||||||
throw new Error(`.roadsignrc file was not found at ${filepath}`)
|
throw new Error(`.roadsignrc file was not found at ${filepath}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await fs.readFile(filepath, "utf8")
|
const data = fs.readFileSync(filepath, "utf8")
|
||||||
this.config = JSON.parse(data)
|
this.config = JSON.parse(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async writeConfig() {
|
public async writeConfig() {
|
||||||
const basepath = process.cwd()
|
const basepath = process.cwd()
|
||||||
const filepath = path.join(basepath, ".roadsignrc")
|
const filepath = path.join(basepath, ".roadsignrc")
|
||||||
await fs.writeFile(filepath, JSON.stringify(this.config))
|
fs.writeFileSync(filepath, JSON.stringify(this.config))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as os from "node:os"
|
import * as os from "node:os"
|
||||||
import * as path from "node:path"
|
import * as path from "node:path"
|
||||||
import * as fs from "node:fs/promises"
|
import * as fs from "node:fs"
|
||||||
|
|
||||||
interface RsConfigData {
|
interface RsConfigData {
|
||||||
servers: RsConfigServerData[]
|
servers: RsConfigServerData[]
|
||||||
@@ -33,18 +33,18 @@ class RsConfig {
|
|||||||
public async readConfig() {
|
public async readConfig() {
|
||||||
const basepath = os.homedir()
|
const basepath = os.homedir()
|
||||||
const filepath = path.join(basepath, ".roadsignrc")
|
const filepath = path.join(basepath, ".roadsignrc")
|
||||||
if (!await fs.exists(filepath)) {
|
if (!fs.existsSync(filepath)) {
|
||||||
await fs.writeFile(filepath, JSON.stringify(this.config))
|
fs.writeFileSync(filepath, JSON.stringify(this.config))
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await fs.readFile(filepath, "utf8")
|
const data = fs.readFileSync(filepath, "utf8")
|
||||||
this.config = JSON.parse(data)
|
this.config = JSON.parse(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async writeConfig() {
|
public async writeConfig() {
|
||||||
const basepath = os.homedir()
|
const basepath = os.homedir()
|
||||||
const filepath = path.join(basepath, ".roadsignrc")
|
const filepath = path.join(basepath, ".roadsignrc")
|
||||||
await fs.writeFile(filepath, JSON.stringify(this.config))
|
fs.writeFileSync(filepath, JSON.stringify(this.config))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// Enable latest features
|
"lib": ["ESNext"],
|
||||||
"lib": ["ESNext", "DOM"],
|
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "ESNext",
|
"module": "NodeNext",
|
||||||
"moduleDetection": "force",
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
|
||||||
// Bundler mode
|
// Bundler mode
|
||||||
"moduleResolution": "bundler",
|
"esModuleInterop": true,
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
|
||||||
// Best practices
|
// Best practices
|
||||||
"strict": true,
|
"strict": true,
|
||||||
@@ -22,6 +21,7 @@
|
|||||||
// Some stricter flags (disabled by default)
|
// Some stricter flags (disabled by default)
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"noPropertyAccessFromIndexSignature": false
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
|
"useUnknownInCatchVariables": false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ func main() {
|
|||||||
// Configure settings
|
// Configure settings
|
||||||
viper.AddConfigPath(".")
|
viper.AddConfigPath(".")
|
||||||
viper.AddConfigPath("..")
|
viper.AddConfigPath("..")
|
||||||
|
viper.AddConfigPath("/")
|
||||||
viper.SetConfigName("settings")
|
viper.SetConfigName("settings")
|
||||||
viper.SetConfigType("toml")
|
viper.SetConfigType("toml")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user