mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
Leave CLI as broken
This commit is contained in:
parent
b69f20ccf4
commit
f4fd16179c
24
README.md
24
README.md
|
|
@ -4,14 +4,11 @@
|
||||||
|
|
||||||
       [](code_of_conduct.md)
|
       [](code_of_conduct.md)
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> This project is **not abandoned**, my laptop merely broke and I am waiting for a new one to arrive
|
|
||||||
|
|
||||||
## What is this?
|
## What is this?
|
||||||
|
|
||||||
This is a project to create a federated social network based on the [Lysand](https://lysand.org) protocol. It is currently in alpha phase, with basic federation and API support.
|
This is a project to create a federated social network based on the [Lysand](https://lysand.org) protocol. It is currently in alpha phase, with basic federation and almost complete Mastodon API support.
|
||||||
|
|
||||||
This project aims to be a fully featured social network, with a focus on privacy, security, and performance. It will implement the Mastodon API for support with clients that already support Mastodon or Pleroma.
|
This project aims to be a fully featured social network, with a focus on privacy, security, and performance. It implements the Mastodon API for support with clients that already support Mastodon or Pleroma.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> This project is not affiliated with Mastodon or Pleroma, and is not a fork of either project. It is a new project built from the ground up.
|
> This project is not affiliated with Mastodon or Pleroma, and is not a fork of either project. It is a new project built from the ground up.
|
||||||
|
|
@ -35,7 +32,7 @@ This project aims to be a fully featured social network, with a focus on privacy
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> These benchmarks are not representative of real-world performance, and are only meant to be used as a rough guide.
|
> These benchmarks are not representative of real-world performance, and are only meant to be used as a rough guide. Load, and therefore performance, will vary depending on the server's hardware and software configuration, as well as user activity.
|
||||||
|
|
||||||
### Timeline Benchmarks
|
### Timeline Benchmarks
|
||||||
|
|
||||||
|
|
@ -67,18 +64,21 @@ $ bun run benchmarks/timelines.ts 10000
|
||||||
✓ 10000 requests fulfilled in 12.44852s
|
✓ 10000 requests fulfilled in 12.44852s
|
||||||
```
|
```
|
||||||
|
|
||||||
Lysand is extremely fast and can handle tens of thousands of HTTP requests per second on a good server.
|
Lysand is extremely fast and can handle thousands of HTTP requests per second on a good server.
|
||||||
|
|
||||||
## How do I run it?
|
## How do I run it?
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- The [Bun Runtime](https://bun.sh), version 1.0.5 or later (usage of the latest version is recommended)
|
- The [Bun Runtime](https://bun.sh), version 1.0.30 or later (usage of the latest version is recommended)
|
||||||
- A PostgreSQL database
|
- A PostgreSQL database
|
||||||
- (Optional but recommended) A Linux-based operating system
|
- (Optional but recommended) A Linux-based operating system
|
||||||
- (Optional if you want search) A working Meiliseach instance
|
- (Optional if you want search) A working Meiliseach instance
|
||||||
|
|
||||||
> **Note**: We will not be offerring support to Windows or MacOS users. If you are using one of these operating systems, please use a virtual machine or container to run Lysand.
|
> [!WARNING]
|
||||||
|
> Lysand has not been tested on Windows or MacOS. It is recommended to use a Linux-based operating system to run Lysand.
|
||||||
|
>
|
||||||
|
> We will not be offerring support to Windows or MacOS users. If you are using one of these operating systems, please use a virtual machine or container to run Lysand.
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
|
|
@ -152,6 +152,9 @@ bun start
|
||||||
|
|
||||||
### Using the CLI
|
### Using the CLI
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> The CLI is currently broken due to unknown bugs that are actively being investigated. The following instructions are for when this is fixed.
|
||||||
|
|
||||||
Lysand includes a built-in CLI for managing the server. To use it, simply run the following command:
|
Lysand includes a built-in CLI for managing the server. To use it, simply run the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -279,10 +282,12 @@ Working endpoints are:
|
||||||
- `/api/v1/blocks`
|
- `/api/v1/blocks`
|
||||||
- `/api/v1/mutes`
|
- `/api/v1/mutes`
|
||||||
- `/api/v2/media`
|
- `/api/v2/media`
|
||||||
|
- `/api/v1/notifications`
|
||||||
|
|
||||||
Tests needed but completed:
|
Tests needed but completed:
|
||||||
|
|
||||||
- `/api/v1/media/:id`
|
- `/api/v1/media/:id`
|
||||||
|
- `/api/v2/media`
|
||||||
- `/api/v1/favourites`
|
- `/api/v1/favourites`
|
||||||
- `/api/v1/accounts/:id/followers`
|
- `/api/v1/accounts/:id/followers`
|
||||||
- `/api/v1/accounts/:id/following`
|
- `/api/v1/accounts/:id/following`
|
||||||
|
|
@ -335,7 +340,6 @@ Endpoints left:
|
||||||
- `/api/v1/lists/:id` (`GET`, `PUT`, `DELETE`)
|
- `/api/v1/lists/:id` (`GET`, `PUT`, `DELETE`)
|
||||||
- `/api/v1/markers` (`GET`, `POST`)
|
- `/api/v1/markers` (`GET`, `POST`)
|
||||||
- `/api/v1/lists/:id/accounts` (`GET`, `POST`, `DELETE`)
|
- `/api/v1/lists/:id/accounts` (`GET`, `POST`, `DELETE`)
|
||||||
- `/api/v1/notifications`
|
|
||||||
- `/api/v1/notifications/:id`
|
- `/api/v1/notifications/:id`
|
||||||
- `/api/v1/notifications/clear`
|
- `/api/v1/notifications/clear`
|
||||||
- `/api/v1/notifications/:id/dismiss`
|
- `/api/v1/notifications/:id/dismiss`
|
||||||
|
|
|
||||||
127
cli.ts
127
cli.ts
|
|
@ -1,23 +1,139 @@
|
||||||
import type { Prisma } from "@prisma/client";
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { client } from "~database/datasource";
|
|
||||||
import { createNewLocalUser } from "~database/entities/User";
|
import { createNewLocalUser } from "~database/entities/User";
|
||||||
import Table from "cli-table";
|
import Table from "cli-table";
|
||||||
import { rebuildSearchIndexes, MeiliIndexType } from "@meilisearch";
|
import { rebuildSearchIndexes, MeiliIndexType } from "@meilisearch";
|
||||||
import { getConfig } from "~classes/configmanager";
|
|
||||||
import { uploadFile } from "~classes/media";
|
|
||||||
import { getUrl } from "~database/entities/Attachment";
|
import { getUrl } from "~database/entities/Attachment";
|
||||||
import { mkdir, exists } from "fs/promises";
|
import { mkdir, exists } from "fs/promises";
|
||||||
import extract from "extract-zip";
|
import extract from "extract-zip";
|
||||||
|
import { client } from "~database/datasource";
|
||||||
|
import { CliBuilder, CliCommand } from "cli-parser";
|
||||||
|
import { CliParameterType } from "~packages/cli-parser/cli-builder.type";
|
||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import { ConfigManager } from "~packages/config-manager";
|
||||||
|
|
||||||
const args = process.argv;
|
const args = process.argv;
|
||||||
|
|
||||||
|
const config = await new ConfigManager({}).getConfig();
|
||||||
|
|
||||||
|
console.error("CLI is temporarily broken, please use the Prisma CLI instead");
|
||||||
|
process.exit(1);
|
||||||
|
|
||||||
|
const cliBuilder = new CliBuilder([
|
||||||
|
new CliCommand(
|
||||||
|
["help"],
|
||||||
|
[],
|
||||||
|
() => {
|
||||||
|
cliBuilder.displayHelp();
|
||||||
|
},
|
||||||
|
"Shows help for the CLI"
|
||||||
|
),
|
||||||
|
new CliCommand<{
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
email: string;
|
||||||
|
admin: boolean;
|
||||||
|
help: boolean;
|
||||||
|
}>(
|
||||||
|
["user", "create"],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "username",
|
||||||
|
type: CliParameterType.STRING,
|
||||||
|
description: "Username of the user",
|
||||||
|
needsValue: true,
|
||||||
|
positioned: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "password",
|
||||||
|
type: CliParameterType.STRING,
|
||||||
|
description: "Password of the user",
|
||||||
|
needsValue: true,
|
||||||
|
positioned: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "email",
|
||||||
|
type: CliParameterType.STRING,
|
||||||
|
description: "Email of the user",
|
||||||
|
needsValue: true,
|
||||||
|
positioned: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "admin",
|
||||||
|
type: CliParameterType.BOOLEAN,
|
||||||
|
description: "Make the user an admin",
|
||||||
|
needsValue: false,
|
||||||
|
positioned: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "help",
|
||||||
|
shortName: "h",
|
||||||
|
type: CliParameterType.EMPTY,
|
||||||
|
description: "Show help message",
|
||||||
|
needsValue: false,
|
||||||
|
positioned: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
(instance: CliCommand, args) => {
|
||||||
|
const { username, password, email, admin, help } = args;
|
||||||
|
|
||||||
|
if (help) {
|
||||||
|
instance.displayHelp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if username, password and email are provided
|
||||||
|
if (!username || !password || !email) {
|
||||||
|
console.log(
|
||||||
|
`${chalk.red(`✗`)} Missing username, password or email`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user already exists
|
||||||
|
void client.user
|
||||||
|
.findFirst({
|
||||||
|
where: {
|
||||||
|
OR: [{ username }, { email }],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(user => {
|
||||||
|
if (user) {
|
||||||
|
console.log(`${chalk.red(`✗`)} User already exists`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Sus");
|
||||||
|
|
||||||
|
// Create user
|
||||||
|
/* const newUser = await createNewLocalUser({
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
username: username,
|
||||||
|
admin: admin,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`${chalk.green(`✓`)} Created user ${chalk.blue(
|
||||||
|
newUser.username
|
||||||
|
)}${admin ? chalk.green(" (admin)") : ""}`
|
||||||
|
); */
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"Creates a new user",
|
||||||
|
"bun cli user create --username admin --password password123 --email email@email.com"
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
cliBuilder.processArgs(args);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make the text have a width of 20 characters, padding with gray dots
|
* Make the text have a width of 20 characters, padding with gray dots
|
||||||
* Text can be a Chalk string, in which case formatting codes should not be counted in text length
|
* Text can be a Chalk string, in which case formatting codes should not be counted in text length
|
||||||
* @param text The text to align
|
* @param text The text to align
|
||||||
*/
|
*/
|
||||||
const alignDots = (text: string, length = 20) => {
|
/* const alignDots = (text: string, length = 20) => {
|
||||||
// Remove formatting codes
|
// Remove formatting codes
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
const textLength = text.replace(/\u001b\[\d+m/g, "").length;
|
const textLength = text.replace(/\u001b\[\d+m/g, "").length;
|
||||||
|
|
@ -1065,3 +1181,4 @@ switch (command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,23 @@
|
||||||
export interface CliParameter {
|
export interface CliParameter {
|
||||||
name: string;
|
name: string;
|
||||||
// If not positioned, the argument will need to be called with --name value instead of just value
|
/* Like -v for --version */
|
||||||
|
shortName?: string;
|
||||||
|
/**
|
||||||
|
* If not positioned, the argument will need to be called with --name value instead of just value
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
positioned?: boolean;
|
positioned?: boolean;
|
||||||
// Whether the argument needs a value (requires positioned to be false)
|
/* Whether the argument needs a value (requires positioned to be false) */
|
||||||
needsValue?: boolean;
|
needsValue?: boolean;
|
||||||
optional?: true;
|
optional?: true;
|
||||||
type: "string" | "number" | "boolean" | "array";
|
type: CliParameterType;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CliParameterType {
|
||||||
|
STRING = "string",
|
||||||
|
NUMBER = "number",
|
||||||
|
BOOLEAN = "boolean",
|
||||||
|
ARRAY = "array",
|
||||||
|
EMPTY = "empty",
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { CliParameter } from "./cli-builder.type";
|
import { CliParameterType, type CliParameter } from "./cli-builder.type";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
|
||||||
export function startsWithArray(fullArray: any[], startArray: any[]) {
|
export function startsWithArray(fullArray: any[], startArray: any[]) {
|
||||||
|
|
@ -193,21 +193,21 @@ export class CliBuilder {
|
||||||
if (value instanceof CliCommand) {
|
if (value instanceof CliCommand) {
|
||||||
writeBuffer += `${" ".repeat(depth)}${chalk.blue(key)}|${chalk.underline(value.description)}\n`;
|
writeBuffer += `${" ".repeat(depth)}${chalk.blue(key)}|${chalk.underline(value.description)}\n`;
|
||||||
const positionedArgs = value.argTypes.filter(
|
const positionedArgs = value.argTypes.filter(
|
||||||
arg => arg.positioned
|
arg => arg.positioned ?? true
|
||||||
);
|
);
|
||||||
const unpositionedArgs = value.argTypes.filter(
|
const unpositionedArgs = value.argTypes.filter(
|
||||||
arg => !arg.positioned
|
arg => !(arg.positioned ?? true)
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const arg of unpositionedArgs) {
|
for (const arg of positionedArgs) {
|
||||||
writeBuffer += `${" ".repeat(depth + 1)}${chalk.green(
|
writeBuffer += `${" ".repeat(depth + 1)}${chalk.green(
|
||||||
arg.name
|
arg.name
|
||||||
)}|${
|
)}|${
|
||||||
arg.description ?? "(no description)"
|
arg.description ?? "(no description)"
|
||||||
} ${arg.optional ? chalk.gray("(optional)") : ""}\n`;
|
} ${arg.optional ? chalk.gray("(optional)") : ""}\n`;
|
||||||
}
|
}
|
||||||
for (const arg of positionedArgs) {
|
for (const arg of unpositionedArgs) {
|
||||||
writeBuffer += `${" ".repeat(depth + 1)}${chalk.yellow("--" + arg.name)}|${
|
writeBuffer += `${" ".repeat(depth + 1)}${chalk.yellow("--" + arg.name)}${arg.shortName ? ", " + chalk.yellow("-" + arg.shortName) : ""}|${
|
||||||
arg.description ?? "(no description)"
|
arg.description ?? "(no description)"
|
||||||
} ${arg.optional ? chalk.gray("(optional)") : ""}\n`;
|
} ${arg.optional ? chalk.gray("(optional)") : ""}\n`;
|
||||||
}
|
}
|
||||||
|
|
@ -253,15 +253,38 @@ export class CliBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* type CliParametersToType<T extends CliParameter[]> = {
|
||||||
|
[K in T[number]["name"]]: T[number]["type"] extends CliParameterType.STRING
|
||||||
|
? string
|
||||||
|
: T[number]["type"] extends CliParameterType.NUMBER
|
||||||
|
? number
|
||||||
|
: T[number]["type"] extends CliParameterType.BOOLEAN
|
||||||
|
? boolean
|
||||||
|
: T[number]["type"] extends CliParameterType.ARRAY
|
||||||
|
? string[]
|
||||||
|
: T[number]["type"] extends CliParameterType.EMPTY
|
||||||
|
? never
|
||||||
|
: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ExecuteFunction<T extends CliParameter[]> = (
|
||||||
|
args: CliParametersToType<T>
|
||||||
|
) => void; */
|
||||||
|
|
||||||
|
type ExecuteFunction<T> = (
|
||||||
|
instance: CliCommand,
|
||||||
|
args: Partial<T>
|
||||||
|
) => Promise<void> | void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A command that can be executed from the command line
|
* A command that can be executed from the command line
|
||||||
* @param categories Example: `["user", "create"]` for the command `./cli user create --name John`
|
* @param categories Example: `["user", "create"]` for the command `./cli user create --name John`
|
||||||
*/
|
*/
|
||||||
export class CliCommand {
|
export class CliCommand<T = any> {
|
||||||
constructor(
|
constructor(
|
||||||
public categories: string[],
|
public categories: string[],
|
||||||
public argTypes: CliParameter[],
|
public argTypes: CliParameter[],
|
||||||
private execute: (args: Record<string, any>) => void,
|
private execute: ExecuteFunction<T>,
|
||||||
public description?: string,
|
public description?: string,
|
||||||
public example?: string
|
public example?: string
|
||||||
) {}
|
) {}
|
||||||
|
|
@ -271,13 +294,17 @@ export class CliCommand {
|
||||||
* formatted with Chalk and with emojis
|
* formatted with Chalk and with emojis
|
||||||
*/
|
*/
|
||||||
displayHelp() {
|
displayHelp() {
|
||||||
const positionedArgs = this.argTypes.filter(arg => arg.positioned);
|
const positionedArgs = this.argTypes.filter(
|
||||||
const unpositionedArgs = this.argTypes.filter(arg => !arg.positioned);
|
arg => arg.positioned ?? true
|
||||||
|
);
|
||||||
|
const unpositionedArgs = this.argTypes.filter(
|
||||||
|
arg => !(arg.positioned ?? true)
|
||||||
|
);
|
||||||
const helpMessage = `
|
const helpMessage = `
|
||||||
${chalk.green("📚 Command:")} ${chalk.yellow(this.categories.join(" "))}
|
${chalk.green("📚 Command:")} ${chalk.yellow(this.categories.join(" "))}
|
||||||
${this.description ? `${chalk.cyan(this.description)}\n` : ""}
|
${this.description ? `${chalk.cyan(this.description)}\n` : ""}
|
||||||
${chalk.magenta("🔧 Arguments:")}
|
${chalk.magenta("🔧 Arguments:")}
|
||||||
${unpositionedArgs
|
${positionedArgs
|
||||||
.map(
|
.map(
|
||||||
arg =>
|
arg =>
|
||||||
`${chalk.bold(arg.name)}: ${chalk.blue(arg.description ?? "(no description)")} ${
|
`${chalk.bold(arg.name)}: ${chalk.blue(arg.description ?? "(no description)")} ${
|
||||||
|
|
@ -285,10 +312,10 @@ ${unpositionedArgs
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
.join("\n")}
|
.join("\n")}
|
||||||
${positionedArgs
|
${unpositionedArgs
|
||||||
.map(
|
.map(
|
||||||
arg =>
|
arg =>
|
||||||
`--${chalk.bold(arg.name)}: ${chalk.blue(arg.description ?? "(no description)")} ${
|
`--${chalk.bold(arg.name)}${arg.shortName ? `, -${arg.shortName}` : ""}: ${chalk.blue(arg.description ?? "(no description)")} ${
|
||||||
arg.optional ? chalk.gray("(optional)") : ""
|
arg.optional ? chalk.gray("(optional)") : ""
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
|
@ -328,6 +355,20 @@ ${positionedArgs
|
||||||
i++;
|
i++;
|
||||||
currentParameter = null;
|
currentParameter = null;
|
||||||
}
|
}
|
||||||
|
} else if (arg.startsWith("-")) {
|
||||||
|
const shortName = arg.substring(1);
|
||||||
|
const argType = this.argTypes.find(
|
||||||
|
argType => argType.shortName === shortName
|
||||||
|
);
|
||||||
|
if (argType && !argType.needsValue) {
|
||||||
|
parsedArgs[argType.name] = true;
|
||||||
|
} else if (argType && argType.needsValue) {
|
||||||
|
parsedArgs[argType.name] = this.castArgValue(
|
||||||
|
argsWithoutCategories[i + 1],
|
||||||
|
argType.type
|
||||||
|
);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
} else if (currentParameter) {
|
} else if (currentParameter) {
|
||||||
parsedArgs[currentParameter.name] = this.castArgValue(
|
parsedArgs[currentParameter.name] = this.castArgValue(
|
||||||
arg,
|
arg,
|
||||||
|
|
@ -352,13 +393,13 @@ ${positionedArgs
|
||||||
|
|
||||||
private castArgValue(value: string, type: CliParameter["type"]): any {
|
private castArgValue(value: string, type: CliParameter["type"]): any {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "string":
|
case CliParameterType.STRING:
|
||||||
return value;
|
return value;
|
||||||
case "number":
|
case CliParameterType.NUMBER:
|
||||||
return Number(value);
|
return Number(value);
|
||||||
case "boolean":
|
case CliParameterType.BOOLEAN:
|
||||||
return value === "true";
|
return value === "true";
|
||||||
case "array":
|
case CliParameterType.ARRAY:
|
||||||
return value.split(",");
|
return value.split(",");
|
||||||
default:
|
default:
|
||||||
return value;
|
return value;
|
||||||
|
|
@ -370,6 +411,6 @@ ${positionedArgs
|
||||||
*/
|
*/
|
||||||
run(argsWithoutCategories: string[]) {
|
run(argsWithoutCategories: string[]) {
|
||||||
const args = this.parseArgs(argsWithoutCategories);
|
const args = this.parseArgs(argsWithoutCategories);
|
||||||
this.execute(args);
|
void this.execute(this, args as any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import { CliCommand, CliBuilder, startsWithArray } from "..";
|
import { CliCommand, CliBuilder, startsWithArray } from "..";
|
||||||
import { describe, beforeEach, it, expect, jest, spyOn } from "bun:test";
|
import { describe, beforeEach, it, expect, jest, spyOn } from "bun:test";
|
||||||
import stripAnsi from "strip-ansi";
|
import stripAnsi from "strip-ansi";
|
||||||
|
import { CliParameterType } from "../cli-builder.type";
|
||||||
|
|
||||||
describe("startsWithArray", () => {
|
describe("startsWithArray", () => {
|
||||||
it("should return true when fullArray starts with startArray", () => {
|
it("should return true when fullArray starts with startArray", () => {
|
||||||
|
|
@ -36,10 +37,27 @@ describe("CliCommand", () => {
|
||||||
cliCommand = new CliCommand(
|
cliCommand = new CliCommand(
|
||||||
["category1", "category2"],
|
["category1", "category2"],
|
||||||
[
|
[
|
||||||
{ name: "arg1", type: "string", needsValue: true },
|
{
|
||||||
{ name: "arg2", type: "number", needsValue: true },
|
name: "arg1",
|
||||||
{ name: "arg3", type: "boolean", needsValue: false },
|
type: CliParameterType.STRING,
|
||||||
{ name: "arg4", type: "array", needsValue: true },
|
needsValue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arg2",
|
||||||
|
shortName: "a",
|
||||||
|
type: CliParameterType.NUMBER,
|
||||||
|
needsValue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arg3",
|
||||||
|
type: CliParameterType.BOOLEAN,
|
||||||
|
needsValue: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arg4",
|
||||||
|
type: CliParameterType.ARRAY,
|
||||||
|
needsValue: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
|
@ -65,13 +83,34 @@ describe("CliCommand", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should cast argument values correctly", () => {
|
it("should parse short names for arguments too", () => {
|
||||||
expect(cliCommand["castArgValue"]("42", "number")).toBe(42);
|
const args = cliCommand["parseArgs"]([
|
||||||
expect(cliCommand["castArgValue"]("true", "boolean")).toBe(true);
|
"--arg1",
|
||||||
expect(cliCommand["castArgValue"]("value1,value2", "array")).toEqual([
|
|
||||||
"value1",
|
"value1",
|
||||||
"value2",
|
"-a",
|
||||||
|
"42",
|
||||||
|
"--arg3",
|
||||||
|
"--arg4",
|
||||||
|
"value1,value2",
|
||||||
]);
|
]);
|
||||||
|
expect(args).toEqual({
|
||||||
|
arg1: "value1",
|
||||||
|
arg2: 42,
|
||||||
|
arg3: true,
|
||||||
|
arg4: ["value1", "value2"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should cast argument values correctly", () => {
|
||||||
|
expect(cliCommand["castArgValue"]("42", CliParameterType.NUMBER)).toBe(
|
||||||
|
42
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
cliCommand["castArgValue"]("true", CliParameterType.BOOLEAN)
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
cliCommand["castArgValue"]("value1,value2", CliParameterType.ARRAY)
|
||||||
|
).toEqual(["value1", "value2"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should run the execute function with the parsed parameters", () => {
|
it("should run the execute function with the parsed parameters", () => {
|
||||||
|
|
@ -79,10 +118,26 @@ describe("CliCommand", () => {
|
||||||
cliCommand = new CliCommand(
|
cliCommand = new CliCommand(
|
||||||
["category1", "category2"],
|
["category1", "category2"],
|
||||||
[
|
[
|
||||||
{ name: "arg1", type: "string", needsValue: true },
|
{
|
||||||
{ name: "arg2", type: "number", needsValue: true },
|
name: "arg1",
|
||||||
{ name: "arg3", type: "boolean", needsValue: false },
|
type: CliParameterType.STRING,
|
||||||
{ name: "arg4", type: "array", needsValue: true },
|
needsValue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arg2",
|
||||||
|
type: CliParameterType.NUMBER,
|
||||||
|
needsValue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arg3",
|
||||||
|
type: CliParameterType.BOOLEAN,
|
||||||
|
needsValue: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arg4",
|
||||||
|
type: CliParameterType.ARRAY,
|
||||||
|
needsValue: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
mockExecute
|
mockExecute
|
||||||
);
|
);
|
||||||
|
|
@ -109,13 +164,29 @@ describe("CliCommand", () => {
|
||||||
cliCommand = new CliCommand(
|
cliCommand = new CliCommand(
|
||||||
["category1", "category2"],
|
["category1", "category2"],
|
||||||
[
|
[
|
||||||
{ name: "arg1", type: "string", needsValue: true },
|
{
|
||||||
{ name: "arg2", type: "number", needsValue: true },
|
name: "arg1",
|
||||||
{ name: "arg3", type: "boolean", needsValue: false },
|
type: CliParameterType.STRING,
|
||||||
{ name: "arg4", type: "array", needsValue: true },
|
needsValue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arg2",
|
||||||
|
type: CliParameterType.NUMBER,
|
||||||
|
needsValue: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arg3",
|
||||||
|
type: CliParameterType.BOOLEAN,
|
||||||
|
needsValue: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arg4",
|
||||||
|
type: CliParameterType.ARRAY,
|
||||||
|
needsValue: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "arg5",
|
name: "arg5",
|
||||||
type: "string",
|
type: CliParameterType.STRING,
|
||||||
needsValue: true,
|
needsValue: true,
|
||||||
positioned: true,
|
positioned: true,
|
||||||
},
|
},
|
||||||
|
|
@ -153,31 +224,31 @@ describe("CliCommand", () => {
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: "arg1",
|
name: "arg1",
|
||||||
type: "string",
|
type: CliParameterType.STRING,
|
||||||
needsValue: true,
|
needsValue: true,
|
||||||
description: "Argument 1",
|
description: "Argument 1",
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "arg2",
|
name: "arg2",
|
||||||
type: "number",
|
type: CliParameterType.NUMBER,
|
||||||
needsValue: true,
|
needsValue: true,
|
||||||
description: "Argument 2",
|
description: "Argument 2",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "arg3",
|
name: "arg3",
|
||||||
type: "boolean",
|
type: CliParameterType.BOOLEAN,
|
||||||
needsValue: false,
|
needsValue: false,
|
||||||
description: "Argument 3",
|
description: "Argument 3",
|
||||||
optional: true,
|
optional: true,
|
||||||
positioned: true,
|
positioned: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "arg4",
|
name: "arg4",
|
||||||
type: "array",
|
type: CliParameterType.ARRAY,
|
||||||
needsValue: true,
|
needsValue: true,
|
||||||
description: "Argument 4",
|
description: "Argument 4",
|
||||||
positioned: true,
|
positioned: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
|
|
@ -260,7 +331,7 @@ describe("CliBuilder", () => {
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: "arg1",
|
name: "arg1",
|
||||||
type: "string",
|
type: CliParameterType.STRING,
|
||||||
needsValue: true,
|
needsValue: true,
|
||||||
positioned: false,
|
positioned: false,
|
||||||
},
|
},
|
||||||
|
|
@ -352,20 +423,28 @@ describe("CliBuilder", () => {
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
name: "name",
|
name: "name",
|
||||||
type: "string",
|
type: CliParameterType.STRING,
|
||||||
needsValue: true,
|
needsValue: true,
|
||||||
description: "Name of new item",
|
description: "Name of new item",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "delete-previous",
|
name: "delete-previous",
|
||||||
type: "number",
|
type: CliParameterType.NUMBER,
|
||||||
needsValue: false,
|
needsValue: false,
|
||||||
positioned: true,
|
positioned: false,
|
||||||
optional: true,
|
optional: true,
|
||||||
description: "Also delete the previous item",
|
description: "Also delete the previous item",
|
||||||
},
|
},
|
||||||
{ name: "arg3", type: "boolean", needsValue: false },
|
{
|
||||||
{ name: "arg4", type: "array", needsValue: true },
|
name: "arg3",
|
||||||
|
type: CliParameterType.BOOLEAN,
|
||||||
|
needsValue: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arg4",
|
||||||
|
type: CliParameterType.ARRAY,
|
||||||
|
needsValue: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
|
|
||||||
41
server.ts
41
server.ts
|
|
@ -75,7 +75,11 @@ export const createServer = (
|
||||||
return errorResponse("Route not found", 500);
|
return errorResponse("Route not found", 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedRoute && file != undefined) {
|
if (
|
||||||
|
matchedRoute &&
|
||||||
|
matchedRoute.name !== "/[...404]" &&
|
||||||
|
file != undefined
|
||||||
|
) {
|
||||||
const meta = file.meta;
|
const meta = file.meta;
|
||||||
|
|
||||||
// Check for allowed requests
|
// Check for allowed requests
|
||||||
|
|
@ -133,35 +137,50 @@ export const createServer = (
|
||||||
configManager,
|
configManager,
|
||||||
parsedRequest,
|
parsedRequest,
|
||||||
});
|
});
|
||||||
} else {
|
} else if (matchedRoute?.name === "/[...404]") {
|
||||||
// Proxy response from Vite at localhost:5173 if in development mode
|
// Proxy response from Vite at localhost:5173 if in development mode
|
||||||
if (isProd) {
|
if (isProd) {
|
||||||
if (new URL(req.url).pathname.startsWith("/assets")) {
|
if (new URL(req.url).pathname.startsWith("/assets")) {
|
||||||
// Serve from pages/dist/assets
|
const file = Bun.file(
|
||||||
return new Response(
|
`./pages/dist${new URL(req.url).pathname}`
|
||||||
Bun.file(`./pages/dist${new URL(req.url).pathname}`)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Serve from pages/dist/assets
|
||||||
|
if (await file.exists()) {
|
||||||
|
return new Response(file);
|
||||||
|
} else return errorResponse("Asset not found", 404);
|
||||||
|
}
|
||||||
|
if (new URL(req.url).pathname.startsWith("/api")) {
|
||||||
|
return errorResponse("Route not found", 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const file = Bun.file(`./pages/dist/index.html`);
|
||||||
|
|
||||||
// Serve from pages/dist
|
// Serve from pages/dist
|
||||||
return new Response(Bun.file(`./pages/dist/index.html`));
|
return new Response(file);
|
||||||
} else {
|
} else {
|
||||||
const proxy = await fetch(
|
const proxy = await fetch(
|
||||||
req.url.replace(
|
req.url.replace(
|
||||||
config.http.base_url,
|
config.http.base_url,
|
||||||
"http://localhost:5173"
|
"http://localhost:5173"
|
||||||
)
|
)
|
||||||
);
|
).catch(async e => {
|
||||||
|
await logger.logError(
|
||||||
|
LogLevel.ERROR,
|
||||||
|
"Server.Proxy",
|
||||||
|
e as Error
|
||||||
|
);
|
||||||
|
return errorResponse("Route not found", 404);
|
||||||
|
});
|
||||||
|
|
||||||
if (proxy.status !== 404) {
|
if (proxy.status !== 404) {
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(undefined, {
|
return errorResponse("Route not found", 404);
|
||||||
status: 404,
|
} else {
|
||||||
statusText: "Route not found",
|
return errorResponse("Route not found", 404);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue