First Commit. Based off bun-sqlite
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { Command } from "commander";
|
||||
import grabDBDir from "../../utils/grab-db-dir";
|
||||
import chalk from "chalk";
|
||||
import { select } from "@inquirer/prompts";
|
||||
import listTables from "./list-tables";
|
||||
import runSQL from "./run-sql";
|
||||
|
||||
export default function () {
|
||||
return new Command("admin")
|
||||
.description("View Tables and Data, Run SQL Queries, Etc.")
|
||||
.action(async () => {
|
||||
const config = global.CONFIG;
|
||||
const { db_file_path } = grabDBDir({ config });
|
||||
|
||||
console.log(chalk.bold(chalk.blue("\nBun MariaDB Admin\n")));
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const paradigm = await select({
|
||||
message: "Choose an action:",
|
||||
choices: [
|
||||
{ name: "Tables", value: "list_tables" },
|
||||
{ name: "SQL", value: "run_sql" },
|
||||
{ name: chalk.dim("✕ Exit"), value: "exit" },
|
||||
],
|
||||
});
|
||||
|
||||
if (paradigm === "exit") break;
|
||||
if (paradigm === "list_tables") {
|
||||
const result = await listTables();
|
||||
if (result === "__exit__") break;
|
||||
}
|
||||
if (paradigm === "run_sql") await runSQL();
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error.message);
|
||||
}
|
||||
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import chalk from "chalk";
|
||||
import { select } from "@inquirer/prompts";
|
||||
import { AppData } from "../../data/app-data";
|
||||
import showEntries from "./show-entries";
|
||||
import showFields from "./show-fields";
|
||||
import dbHandler from "../../lib/db-handler";
|
||||
|
||||
type Params = {};
|
||||
|
||||
type ColumnInfo = {
|
||||
cid: number;
|
||||
name: string;
|
||||
type: string;
|
||||
notnull: number;
|
||||
dflt_value: string | null;
|
||||
pk: number;
|
||||
};
|
||||
|
||||
export default async function listTables(
|
||||
params?: Params,
|
||||
): Promise<"__exit__" | void> {
|
||||
const tables = (
|
||||
await dbHandler({
|
||||
query: `SELECT table_name FROM ${AppData["DbSchemaManagerTableName"]}`,
|
||||
})
|
||||
).payload;
|
||||
|
||||
if (!tables?.length) {
|
||||
console.log(chalk.yellow("\nNo tables found.\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Level 1: table selection loop
|
||||
while (true) {
|
||||
const tableName = await select({
|
||||
message: "Select a table:",
|
||||
choices: [
|
||||
...tables.map((t) => ({
|
||||
name: t.table_name,
|
||||
value: t.table_name,
|
||||
})),
|
||||
{ name: chalk.dim("← Go Back"), value: "__back__" },
|
||||
{ name: chalk.dim("✕ Exit"), value: "__exit__" },
|
||||
],
|
||||
});
|
||||
|
||||
if (tableName === "__back__") break;
|
||||
if (tableName === "__exit__") return "__exit__";
|
||||
|
||||
// Level 2: action loop — stays here until "Go Back"
|
||||
while (true) {
|
||||
const action = await select({
|
||||
message: `"${tableName}" — choose an action:`,
|
||||
choices: [
|
||||
{ name: "Entries", value: "entries" },
|
||||
{ name: "Fields", value: "fields" },
|
||||
{ name: "Schema", value: "schema" },
|
||||
{ name: chalk.dim("← Go Back"), value: "__back__" },
|
||||
{ name: chalk.dim("✕ Exit"), value: "__exit__" },
|
||||
],
|
||||
});
|
||||
|
||||
if (action === "__back__") break;
|
||||
if (action === "__exit__") return "__exit__";
|
||||
|
||||
if (action === "entries") {
|
||||
const result = await showEntries({ tableName });
|
||||
if (result === "__exit__") return "__exit__";
|
||||
}
|
||||
|
||||
if (action === "fields") {
|
||||
const result = await showFields({ tableName });
|
||||
if (result === "__exit__") return "__exit__";
|
||||
}
|
||||
|
||||
// if (action === "schema") {
|
||||
// const columns = db
|
||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
||||
// .all();
|
||||
|
||||
// console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`);
|
||||
// console.table(
|
||||
// columns.map((c) => ({
|
||||
// "#": c.cid,
|
||||
// Name: c.name,
|
||||
// Type: c.type,
|
||||
// "Not Null": c.notnull ? "YES" : "NO",
|
||||
// Default: c.dflt_value ?? "(none)",
|
||||
// "Primary Key": c.pk ? "YES" : "NO",
|
||||
// })),
|
||||
// );
|
||||
// console.log();
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { input } from "@inquirer/prompts";
|
||||
import chalk from "chalk";
|
||||
import dbHandler from "../../lib/db-handler";
|
||||
|
||||
type Params = {};
|
||||
|
||||
export default async function runSQL(params?: Params) {
|
||||
const sql = await input({
|
||||
message: "Enter SQL query:",
|
||||
validate: (val) => val.trim().length > 0 || "Query cannot be empty",
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await dbHandler({ query: sql });
|
||||
|
||||
if (res.payload) {
|
||||
console.log(
|
||||
`\n${chalk.bold(`Result (${res.payload.length} row${res.payload.length !== 1 ? "s" : ""}):`)} \n`,
|
||||
);
|
||||
if (res.payload.length) console.table(res.payload);
|
||||
else console.log(chalk.yellow("No res returned.\n"));
|
||||
} else if (res.single_res) {
|
||||
console.log(
|
||||
chalk.green(
|
||||
`\nSuccess! Affected rows: ${res.single_res.changes}, Last insert ID: ${res.single_res.lastInsertRowid}\n`,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(chalk.red(`\nSQL Error: ${error.message}\n`));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import chalk from "chalk";
|
||||
import { select, input } from "@inquirer/prompts";
|
||||
import dbHandler from "../../lib/db-handler";
|
||||
|
||||
type Params = {
|
||||
tableName: string;
|
||||
};
|
||||
type ColumnInfo = { cid: number; name: string };
|
||||
|
||||
const LIMIT = 50;
|
||||
|
||||
export default async function showEntries({ tableName }: Params) {
|
||||
let page = 0;
|
||||
let searchField: string | null = null;
|
||||
let searchTerm: string | null = null;
|
||||
|
||||
while (true) {
|
||||
const offset = page * LIMIT;
|
||||
|
||||
const rows = searchTerm
|
||||
? (
|
||||
await dbHandler({
|
||||
query: `SELECT * FROM "${tableName}" WHERE "${searchField}" LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||
values: [`%${searchTerm}%`],
|
||||
})
|
||||
).payload
|
||||
: (
|
||||
await dbHandler({
|
||||
query: `SELECT * FROM "${tableName}" LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||
})
|
||||
).payload;
|
||||
|
||||
const countRow = searchTerm
|
||||
? (
|
||||
await dbHandler({
|
||||
query: `SELECT COUNT(*) as count FROM "${tableName}" WHERE "${searchField}" LIKE ?`,
|
||||
values: [`%${searchTerm}%`],
|
||||
})
|
||||
).payload
|
||||
: (
|
||||
await dbHandler({
|
||||
query: `SELECT COUNT(*) as count FROM "${tableName}"`,
|
||||
})
|
||||
).payload;
|
||||
|
||||
const total = countRow?.[0]?.count;
|
||||
const searchInfo = searchTerm
|
||||
? chalk.dim(` · searching "${searchField}" = "${searchTerm}"`)
|
||||
: "";
|
||||
|
||||
if (!rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`\n${chalk.bold(tableName)} — Page ${page + 1}${searchInfo} (${rows.length} of ${total}):\n`,
|
||||
);
|
||||
|
||||
if (rows.length) {
|
||||
console.log(rows);
|
||||
// if (rows.length) console.table(rows);
|
||||
} else {
|
||||
console.log(chalk.yellow("No rows found."));
|
||||
console.log();
|
||||
}
|
||||
|
||||
const choices: { name: string; value: string }[] = [];
|
||||
if (page > 0) choices.push({ name: "← Previous Page", value: "prev" });
|
||||
if (offset + rows.length < total)
|
||||
choices.push({ name: "Next Page →", value: "next" });
|
||||
choices.push({ name: "Search by Field", value: "search" });
|
||||
if (searchTerm)
|
||||
choices.push({ name: "Clear Search", value: "clear_search" });
|
||||
choices.push({ name: chalk.dim("← Go Back"), value: "__back__" });
|
||||
choices.push({ name: chalk.dim("✕ Exit"), value: "__exit__" });
|
||||
|
||||
const action = await select({ message: "Navigate:", choices });
|
||||
|
||||
if (action === "__back__") break;
|
||||
if (action === "__exit__") return "__exit__";
|
||||
if (action === "next") page++;
|
||||
if (action === "prev") page--;
|
||||
if (action === "clear_search") {
|
||||
searchField = null;
|
||||
searchTerm = null;
|
||||
page = 0;
|
||||
}
|
||||
// if (action === "search") {
|
||||
// const columns = db
|
||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
||||
// .all();
|
||||
// searchField = await select({
|
||||
// message: "Search by field:",
|
||||
// choices: columns.map((c) => ({ name: c.name, value: c.name })),
|
||||
// });
|
||||
// searchTerm = await input({
|
||||
// message: `Search term for "${searchField}":`,
|
||||
// validate: (v) => v.trim().length > 0 || "Cannot be empty",
|
||||
// });
|
||||
// page = 0;
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
type Params = {
|
||||
tableName: string;
|
||||
};
|
||||
|
||||
type ColumnInfo = {
|
||||
cid: number;
|
||||
name: string;
|
||||
type: string;
|
||||
notnull: number;
|
||||
dflt_value: string | null;
|
||||
pk: number;
|
||||
};
|
||||
|
||||
type IndexInfo = { name: string; unique: number; origin: string };
|
||||
type IndexColumn = { name: string };
|
||||
type ForeignKey = {
|
||||
id: number;
|
||||
from: string;
|
||||
table: string;
|
||||
to: string;
|
||||
on_update: string;
|
||||
on_delete: string;
|
||||
};
|
||||
|
||||
export default async function showFields({
|
||||
tableName,
|
||||
}: Params): Promise<"__exit__" | void> {
|
||||
// const columns = db
|
||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
||||
// .all();
|
||||
// const indexes = db
|
||||
// .query<IndexInfo, []>(`PRAGMA index_list("${tableName}")`)
|
||||
// .all();
|
||||
// const foreignKeys = db
|
||||
// .query<ForeignKey, []>(`PRAGMA foreign_key_list("${tableName}")`)
|
||||
// .all();
|
||||
// const indexedFields = new Map<string, { unique: boolean }>();
|
||||
// for (const idx of indexes) {
|
||||
// const cols = db
|
||||
// .query<IndexColumn, []>(`PRAGMA index_info("${idx.name}")`)
|
||||
// .all();
|
||||
// for (const col of cols) {
|
||||
// indexedFields.set(col.name, { unique: idx.unique === 1 });
|
||||
// }
|
||||
// }
|
||||
// while (true) {
|
||||
// const fieldName = await select({
|
||||
// message: `"${tableName}" — select a field:`,
|
||||
// choices: [
|
||||
// ...columns.map((c) => ({ name: c.name, value: c.name })),
|
||||
// { name: chalk.dim("← Go Back"), value: "__back__" },
|
||||
// { name: chalk.dim("✕ Exit"), value: "__exit__" },
|
||||
// ],
|
||||
// });
|
||||
// if (fieldName === "__back__") break;
|
||||
// if (fieldName === "__exit__") return "__exit__";
|
||||
// const col = columns.find((c) => c.name === fieldName)!;
|
||||
// const idx = indexedFields.get(fieldName);
|
||||
// const fk = foreignKeys.find((f) => f.from === fieldName);
|
||||
// console.log(`\n${chalk.bold(`Field: "${fieldName}"`)}\n`);
|
||||
// console.log(` ${chalk.dim("Table")} ${tableName}`);
|
||||
// console.log(` ${chalk.dim("Column #")} ${col.cid}`);
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Type")} ${col.type || chalk.italic("(none)")}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Primary Key")} ${col.pk ? chalk.green("YES") : "NO"}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Not Null")} ${col.notnull ? chalk.yellow("YES") : "NO"}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Default")} ${col.dflt_value ?? chalk.italic("(none)")}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Indexed")} ${idx ? chalk.cyan("YES") : "NO"}`,
|
||||
// );
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Unique")} ${idx?.unique ? chalk.cyan("YES") : "NO"}`,
|
||||
// );
|
||||
// if (fk) {
|
||||
// console.log(
|
||||
// ` ${chalk.dim("Foreign Key")} ${chalk.magenta(`${fk.table}(${fk.to})`)}`,
|
||||
// );
|
||||
// console.log(` ${chalk.dim("On Update")} ${fk.on_update}`);
|
||||
// console.log(` ${chalk.dim("On Delete")} ${fk.on_delete}`);
|
||||
// } else {
|
||||
// console.log(` ${chalk.dim("Foreign Key")} NO`);
|
||||
// }
|
||||
// console.log();
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Command } from "commander";
|
||||
import path from "path";
|
||||
import grabDBDir from "../utils/grab-db-dir";
|
||||
import fs from "fs";
|
||||
import grabDBBackupFileName from "../utils/grab-db-backup-file-name";
|
||||
import chalk from "chalk";
|
||||
import trimBackups from "../utils/trim-backups";
|
||||
|
||||
export default function () {
|
||||
return new Command("backup")
|
||||
.description("Backup Database")
|
||||
.action(async (opts) => {
|
||||
console.log(`Backing up database ...`);
|
||||
|
||||
const config = global.CONFIG;
|
||||
|
||||
const { backup_dir, db_file_path } = grabDBDir({ config });
|
||||
|
||||
const new_db_file_name = grabDBBackupFileName({ config });
|
||||
|
||||
fs.cpSync(db_file_path, path.join(backup_dir, new_db_file_name));
|
||||
|
||||
trimBackups({ config });
|
||||
|
||||
console.log(`${chalk.bold(chalk.green(`DB Backup Success!`))}`);
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { program } from "commander";
|
||||
import schema from "./schema";
|
||||
import typedef from "./typedef";
|
||||
import backup from "./backup";
|
||||
import restore from "./restore";
|
||||
import admin from "./admin";
|
||||
import type {
|
||||
BUN_MARIADB_DatabaseSchemaType,
|
||||
BunMariaDBConfig,
|
||||
} from "../types";
|
||||
import init from "../functions/init";
|
||||
|
||||
/**
|
||||
* # Declare Global Variables
|
||||
*/
|
||||
declare global {
|
||||
var CONFIG: BunMariaDBConfig;
|
||||
var DB_SCHEMA: BUN_MARIADB_DatabaseSchemaType;
|
||||
}
|
||||
|
||||
await init();
|
||||
|
||||
if (!global.CONFIG) {
|
||||
console.error(`Couldn't grab global Config.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!global.DB_SCHEMA) {
|
||||
console.error(`Couldn't grab Database Schema.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* # Describe Program
|
||||
*/
|
||||
program
|
||||
.name(`bun-mariadb`)
|
||||
.description(`MariaDB manager for Bun`)
|
||||
.version(`1.0.0`);
|
||||
|
||||
/**
|
||||
* # Declare Commands
|
||||
*/
|
||||
program.addCommand(schema());
|
||||
program.addCommand(typedef());
|
||||
program.addCommand(backup());
|
||||
program.addCommand(restore());
|
||||
program.addCommand(admin());
|
||||
|
||||
/**
|
||||
* # Handle Unavailable Commands
|
||||
*/
|
||||
program.on("command:*", () => {
|
||||
console.error(
|
||||
"Invalid command: %s\nSee --help for a list of available commands.",
|
||||
program.args.join(" "),
|
||||
);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
/**
|
||||
* # Parse Arguments
|
||||
*/
|
||||
program.parse(Bun.argv);
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Command } from "commander";
|
||||
import grabDBDir from "../utils/grab-db-dir";
|
||||
import fs from "fs";
|
||||
import chalk from "chalk";
|
||||
import grabSortedBackups from "../utils/grab-sorted-backups";
|
||||
import { select } from "@inquirer/prompts";
|
||||
import grabBackupData from "../utils/grab-backup-data";
|
||||
import path from "path";
|
||||
|
||||
export default function () {
|
||||
return new Command("restore")
|
||||
.description("Restore Database")
|
||||
.action(async (opts) => {
|
||||
console.log(`Restoring up database ...`);
|
||||
|
||||
const config = global.CONFIG;
|
||||
|
||||
const { backup_dir, db_file_path } = grabDBDir({ config });
|
||||
|
||||
const backups = grabSortedBackups({ config });
|
||||
|
||||
if (!backups?.[0]) {
|
||||
console.error(
|
||||
`No Backups to restore. Use the \`backup\` command to create a backup`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
const selected_backup = await select({
|
||||
message: "Select a backup:",
|
||||
choices: backups.map((b, i) => {
|
||||
const { backup_date } = grabBackupData({
|
||||
backup_name: b,
|
||||
});
|
||||
return {
|
||||
name: `Backup #${i + 1}: ${backup_date.toDateString()} ${backup_date.getHours()}:${backup_date.getMinutes()}:${backup_date.getSeconds().toString().padStart(2, "0")}`,
|
||||
value: b,
|
||||
};
|
||||
}),
|
||||
});
|
||||
|
||||
fs.cpSync(path.join(backup_dir, selected_backup), db_file_path);
|
||||
|
||||
console.log(
|
||||
`${chalk.bold(chalk.green(`DB Restore Success!`))}`,
|
||||
);
|
||||
|
||||
process.exit();
|
||||
} catch (error: any) {
|
||||
console.error(`Backup Restore ERROR => ${error.message}`);
|
||||
process.exit();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Command } from "commander";
|
||||
import { MariaDBSchemaManager } from "../lib/mariadb/db-schema-manager";
|
||||
import grabDirNames from "../data/grab-dir-names";
|
||||
import path from "path";
|
||||
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
|
||||
import _ from "lodash";
|
||||
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
|
||||
import chalk from "chalk";
|
||||
import { writeLiveSchema } from "../functions/live-schema";
|
||||
import grabDBDir from "../utils/grab-db-dir";
|
||||
import { cpSync } from "fs";
|
||||
|
||||
export default function () {
|
||||
return new Command("schema")
|
||||
.description("Build DB From Schema")
|
||||
.option(
|
||||
"-v, --vector",
|
||||
"Recreate Vector Tables. This will drop and rebuild all vector tables",
|
||||
)
|
||||
.option("-t, --typedef", "Generate typescript type definitions")
|
||||
.action(async (opts) => {
|
||||
console.log(`Starting process ...`);
|
||||
|
||||
const config = global.CONFIG;
|
||||
const dbSchema = global.DB_SCHEMA;
|
||||
|
||||
const { ROOT_DIR, BUN_MARIADB_TEMP_DB_FILE_PATH } = grabDirNames();
|
||||
const { db_file_path } = grabDBDir({ config });
|
||||
|
||||
cpSync(db_file_path, BUN_MARIADB_TEMP_DB_FILE_PATH);
|
||||
|
||||
try {
|
||||
const isVector = Boolean(opts.vector || opts.v);
|
||||
const isTypeDef = Boolean(opts.typedef || opts.t);
|
||||
|
||||
const finaldbSchema = appendDefaultFieldsToDbSchema({
|
||||
dbSchema,
|
||||
});
|
||||
|
||||
const manager = new MariaDBSchemaManager({
|
||||
schema: finaldbSchema,
|
||||
recreate_vector_table: isVector,
|
||||
});
|
||||
|
||||
await manager.syncSchema();
|
||||
manager.close();
|
||||
|
||||
if (isTypeDef && config.typedef_file_path) {
|
||||
const out_file = path.resolve(
|
||||
ROOT_DIR,
|
||||
config.typedef_file_path,
|
||||
);
|
||||
|
||||
dbSchemaToTypeDef({
|
||||
dbSchema: finaldbSchema,
|
||||
dst_file: out_file,
|
||||
config,
|
||||
});
|
||||
}
|
||||
|
||||
writeLiveSchema({ schema: finaldbSchema });
|
||||
|
||||
console.log(
|
||||
`${chalk.bold(chalk.green(`DB Schema setup success!`))}`,
|
||||
);
|
||||
process.exit();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
cpSync(BUN_MARIADB_TEMP_DB_FILE_PATH, db_file_path);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Command } from "commander";
|
||||
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
|
||||
import path from "path";
|
||||
import grabDirNames from "../data/grab-dir-names";
|
||||
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
|
||||
import chalk from "chalk";
|
||||
|
||||
export default function () {
|
||||
return new Command("typedef")
|
||||
.description("Build DB From Schema")
|
||||
.action(async (opts) => {
|
||||
console.log(`Creating Type Definition From DB Schema ...`);
|
||||
|
||||
const config = global.CONFIG;
|
||||
const dbSchema = global.DB_SCHEMA;
|
||||
|
||||
const { ROOT_DIR } = grabDirNames();
|
||||
|
||||
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
|
||||
|
||||
if (config.typedef_file_path) {
|
||||
const out_file = path.resolve(
|
||||
ROOT_DIR,
|
||||
config.typedef_file_path,
|
||||
);
|
||||
dbSchemaToTypeDef({
|
||||
dbSchema: finaldbSchema,
|
||||
dst_file: out_file,
|
||||
config,
|
||||
});
|
||||
} else {
|
||||
console.error(``);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`${chalk.bold(chalk.green(`Typedef gen success!`))}`);
|
||||
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user