mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 12:37:20 +00:00
public commit
This commit is contained in:
12
app/src/cli/commands/config.ts
Normal file
12
app/src/cli/commands/config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { getDefaultConfig } from "modules/ModuleManager";
|
||||
import type { CliCommand } from "../types";
|
||||
|
||||
export const config: CliCommand = (program) => {
|
||||
program
|
||||
.command("config")
|
||||
.description("get default config")
|
||||
.option("--pretty", "pretty print")
|
||||
.action((options) => {
|
||||
console.log(getDefaultConfig(options.pretty));
|
||||
});
|
||||
};
|
||||
20
app/src/cli/commands/debug.ts
Normal file
20
app/src/cli/commands/debug.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import path from "node:path";
|
||||
import url from "node:url";
|
||||
import { getDistPath, getRelativeDistPath, getRootPath } from "cli/utils/sys";
|
||||
import type { CliCommand } from "../types";
|
||||
|
||||
export const debug: CliCommand = (program) => {
|
||||
program
|
||||
.command("debug")
|
||||
.description("debug path resolution")
|
||||
.action(() => {
|
||||
console.log("paths", {
|
||||
rootpath: getRootPath(),
|
||||
distPath: getDistPath(),
|
||||
relativeDistPath: getRelativeDistPath(),
|
||||
cwd: process.cwd(),
|
||||
dir: path.dirname(url.fileURLToPath(import.meta.url)),
|
||||
resolvedPkg: path.resolve(getRootPath(), "package.json")
|
||||
});
|
||||
});
|
||||
};
|
||||
5
app/src/cli/commands/index.ts
Normal file
5
app/src/cli/commands/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { config } from "./config";
|
||||
export { schema } from "./schema";
|
||||
export { run } from "./run";
|
||||
export { debug } from "./debug";
|
||||
export { user } from "./user";
|
||||
1
app/src/cli/commands/run/index.ts
Normal file
1
app/src/cli/commands/run/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./run";
|
||||
96
app/src/cli/commands/run/platform.ts
Normal file
96
app/src/cli/commands/run/platform.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { ServeStaticOptions } from "@hono/node-server/serve-static";
|
||||
import { type Config, createClient } from "@libsql/client/node";
|
||||
import { Connection, LibsqlConnection, SqliteLocalConnection } from "data";
|
||||
import type { MiddlewareHandler } from "hono";
|
||||
import { fileExists, getDistPath, getRelativeDistPath } from "../../utils/sys";
|
||||
|
||||
export const PLATFORMS = ["node", "bun"] as const;
|
||||
export type Platform = (typeof PLATFORMS)[number];
|
||||
|
||||
export async function serveStatic(server: Platform): Promise<MiddlewareHandler> {
|
||||
switch (server) {
|
||||
case "node": {
|
||||
const m = await import("@hono/node-server/serve-static");
|
||||
return m.serveStatic({
|
||||
// somehow different for node
|
||||
root: getRelativeDistPath() + "/static"
|
||||
});
|
||||
}
|
||||
case "bun": {
|
||||
const m = await import("hono/bun");
|
||||
return m.serveStatic({
|
||||
root: path.resolve(getRelativeDistPath(), "static")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function attachServeStatic(app: any, platform: Platform) {
|
||||
app.module.server.client.get("/assets/*", await serveStatic(platform));
|
||||
}
|
||||
|
||||
export async function startServer(server: Platform, app: any, options: { port: number }) {
|
||||
const port = options.port;
|
||||
console.log("running on", server, port);
|
||||
switch (server) {
|
||||
case "node": {
|
||||
// https://github.com/honojs/node-server/blob/main/src/response.ts#L88
|
||||
const serve = await import("@hono/node-server").then((m) => m.serve);
|
||||
serve({
|
||||
fetch: (req) => app.fetch(req),
|
||||
port
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "bun": {
|
||||
Bun.serve({
|
||||
fetch: (req) => app.fetch(req),
|
||||
port
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Server listening on", "http://localhost:" + port);
|
||||
}
|
||||
|
||||
export async function getHtml() {
|
||||
return await readFile(path.resolve(getDistPath(), "static/index.html"), "utf-8");
|
||||
}
|
||||
|
||||
export function getConnection(connectionOrConfig?: Connection | Config): Connection {
|
||||
if (connectionOrConfig) {
|
||||
if (connectionOrConfig instanceof Connection) {
|
||||
return connectionOrConfig;
|
||||
}
|
||||
|
||||
if ("url" in connectionOrConfig) {
|
||||
return new LibsqlConnection(createClient(connectionOrConfig));
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Using in-memory database");
|
||||
return new LibsqlConnection(createClient({ url: ":memory:" }));
|
||||
//return new SqliteLocalConnection(new Database(":memory:"));
|
||||
}
|
||||
|
||||
export async function getConfigPath(filePath?: string) {
|
||||
if (filePath) {
|
||||
const config_path = path.resolve(process.cwd(), filePath);
|
||||
if (await fileExists(config_path)) {
|
||||
return config_path;
|
||||
}
|
||||
}
|
||||
|
||||
const paths = ["./bknd.config", "./bknd.config.ts", "./bknd.config.js"];
|
||||
for (const p of paths) {
|
||||
const _p = path.resolve(process.cwd(), p);
|
||||
if (await fileExists(_p)) {
|
||||
return _p;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
115
app/src/cli/commands/run/run.ts
Normal file
115
app/src/cli/commands/run/run.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import type { Config } from "@libsql/client/node";
|
||||
import { App } from "App";
|
||||
import type { BkndConfig } from "adapter";
|
||||
import { Option } from "commander";
|
||||
import type { Connection } from "data";
|
||||
import type { CliCommand } from "../../types";
|
||||
import {
|
||||
PLATFORMS,
|
||||
type Platform,
|
||||
attachServeStatic,
|
||||
getConfigPath,
|
||||
getConnection,
|
||||
getHtml,
|
||||
startServer
|
||||
} from "./platform";
|
||||
|
||||
const isBun = typeof Bun !== "undefined";
|
||||
|
||||
export const run: CliCommand = (program) => {
|
||||
program
|
||||
.command("run")
|
||||
.addOption(
|
||||
new Option("-p, --port <port>", "port to run on")
|
||||
.env("PORT")
|
||||
.default(1337)
|
||||
.argParser((v) => Number.parseInt(v))
|
||||
)
|
||||
.addOption(new Option("-c, --config <config>", "config file"))
|
||||
.addOption(
|
||||
new Option("--db-url <db>", "database url, can be any valid libsql url").conflicts(
|
||||
"config"
|
||||
)
|
||||
)
|
||||
.addOption(new Option("--db-token <db>", "database token").conflicts("config"))
|
||||
.addOption(
|
||||
new Option("--server <server>", "server type")
|
||||
.choices(PLATFORMS)
|
||||
.default(isBun ? "bun" : "node")
|
||||
)
|
||||
.action(action);
|
||||
};
|
||||
|
||||
type MakeAppConfig = {
|
||||
connection: Connection;
|
||||
server?: { platform?: Platform };
|
||||
setAdminHtml?: boolean;
|
||||
onBuilt?: (app: App) => Promise<void>;
|
||||
};
|
||||
|
||||
async function makeApp(config: MakeAppConfig) {
|
||||
const html = await getHtml();
|
||||
const app = new App(config.connection);
|
||||
|
||||
app.emgr.on(
|
||||
"app-built",
|
||||
async () => {
|
||||
await attachServeStatic(app, config.server?.platform ?? "node");
|
||||
app.module.server.setAdminHtml(html);
|
||||
|
||||
if (config.onBuilt) {
|
||||
await config.onBuilt(app);
|
||||
}
|
||||
},
|
||||
"sync"
|
||||
);
|
||||
|
||||
await app.build();
|
||||
return app;
|
||||
}
|
||||
|
||||
export async function makeConfigApp(config: BkndConfig, platform?: Platform) {
|
||||
const appConfig = typeof config.app === "function" ? config.app(process.env) : config.app;
|
||||
const html = await getHtml();
|
||||
const app = App.create(appConfig);
|
||||
|
||||
app.emgr.on(
|
||||
"app-built",
|
||||
async () => {
|
||||
await attachServeStatic(app, platform ?? "node");
|
||||
app.module.server.setAdminHtml(html);
|
||||
|
||||
if (config.onBuilt) {
|
||||
await config.onBuilt(app);
|
||||
}
|
||||
},
|
||||
"sync"
|
||||
);
|
||||
|
||||
await app.build();
|
||||
return app;
|
||||
}
|
||||
|
||||
async function action(options: {
|
||||
port: number;
|
||||
config?: string;
|
||||
dbUrl?: string;
|
||||
dbToken?: string;
|
||||
server: Platform;
|
||||
}) {
|
||||
const configFilePath = await getConfigPath(options.config);
|
||||
|
||||
let app: App;
|
||||
if (options.dbUrl || !configFilePath) {
|
||||
const connection = getConnection(
|
||||
options.dbUrl ? { url: options.dbUrl, authToken: options.dbToken } : undefined
|
||||
);
|
||||
app = await makeApp({ connection, server: { platform: options.server } });
|
||||
} else {
|
||||
console.log("Using config from:", configFilePath);
|
||||
const config = (await import(configFilePath).then((m) => m.default)) as BkndConfig;
|
||||
app = await makeConfigApp(config, options.server);
|
||||
}
|
||||
|
||||
await startServer(options.server, app, { port: options.port });
|
||||
}
|
||||
12
app/src/cli/commands/schema.ts
Normal file
12
app/src/cli/commands/schema.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { getDefaultSchema } from "modules/ModuleManager";
|
||||
import type { CliCommand } from "../types";
|
||||
|
||||
export const schema: CliCommand = (program) => {
|
||||
program
|
||||
.command("schema")
|
||||
.description("get schema")
|
||||
.option("--pretty", "pretty print")
|
||||
.action((options) => {
|
||||
console.log(getDefaultSchema(options.pretty));
|
||||
});
|
||||
};
|
||||
144
app/src/cli/commands/user.ts
Normal file
144
app/src/cli/commands/user.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { password as $password, text as $text } from "@clack/prompts";
|
||||
import type { PasswordStrategy } from "auth/authenticate/strategies";
|
||||
import type { App, BkndConfig } from "bknd";
|
||||
import { makeConfigApp } from "cli/commands/run";
|
||||
import { getConfigPath } from "cli/commands/run/platform";
|
||||
import type { CliCommand } from "cli/types";
|
||||
import { Argument } from "commander";
|
||||
|
||||
export const user: CliCommand = (program) => {
|
||||
program
|
||||
.command("user")
|
||||
.description("create and update user (auth)")
|
||||
.addArgument(new Argument("<action>", "action to perform").choices(["create", "update"]))
|
||||
.action(action);
|
||||
};
|
||||
|
||||
async function action(action: "create" | "update", options: any) {
|
||||
const configFilePath = await getConfigPath();
|
||||
if (!configFilePath) {
|
||||
console.error("config file not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const config = (await import(configFilePath).then((m) => m.default)) as BkndConfig;
|
||||
const app = await makeConfigApp(config, options.server);
|
||||
|
||||
switch (action) {
|
||||
case "create":
|
||||
await create(app, options);
|
||||
break;
|
||||
case "update":
|
||||
await update(app, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function create(app: App, options: any) {
|
||||
const config = app.module.auth.toJSON(true);
|
||||
const strategy = app.module.auth.authenticator.strategy("password") as PasswordStrategy;
|
||||
const users_entity = config.entity_name;
|
||||
|
||||
const email = await $text({
|
||||
message: "Enter email",
|
||||
validate: (v) => {
|
||||
if (!v.includes("@")) {
|
||||
return "Invalid email";
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const password = await $password({
|
||||
message: "Enter password",
|
||||
validate: (v) => {
|
||||
if (v.length < 3) {
|
||||
return "Invalid password";
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof email !== "string" || typeof password !== "string") {
|
||||
console.log("Cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
const mutator = app.modules.ctx().em.mutator(users_entity);
|
||||
mutator.__unstable_toggleSystemEntityCreation(true);
|
||||
const res = await mutator.insertOne({
|
||||
email,
|
||||
strategy: "password",
|
||||
strategy_value: await strategy.hash(password as string)
|
||||
});
|
||||
mutator.__unstable_toggleSystemEntityCreation(false);
|
||||
|
||||
console.log("Created:", res.data);
|
||||
} catch (e) {
|
||||
console.error("Error");
|
||||
}
|
||||
}
|
||||
|
||||
async function update(app: App, options: any) {
|
||||
const config = app.module.auth.toJSON(true);
|
||||
const strategy = app.module.auth.authenticator.strategy("password") as PasswordStrategy;
|
||||
const users_entity = config.entity_name;
|
||||
const em = app.modules.ctx().em;
|
||||
|
||||
const email = (await $text({
|
||||
message: "Which user? Enter email",
|
||||
validate: (v) => {
|
||||
if (!v.includes("@")) {
|
||||
return "Invalid email";
|
||||
}
|
||||
return;
|
||||
}
|
||||
})) as string;
|
||||
if (typeof email !== "string") {
|
||||
console.log("Cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const { data: user } = await em.repository(users_entity).findOne({ email });
|
||||
if (!user) {
|
||||
console.log("User not found");
|
||||
process.exit(0);
|
||||
}
|
||||
console.log("User found:", user);
|
||||
|
||||
const password = await $password({
|
||||
message: "New Password?",
|
||||
validate: (v) => {
|
||||
if (v.length < 3) {
|
||||
return "Invalid password";
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
if (typeof password !== "string") {
|
||||
console.log("Cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
function togglePw(visible: boolean) {
|
||||
const field = em.entity(users_entity).field("strategy_value")!;
|
||||
|
||||
field.config.hidden = !visible;
|
||||
field.config.fillable = visible;
|
||||
}
|
||||
togglePw(true);
|
||||
await app.modules
|
||||
.ctx()
|
||||
.em.mutator(users_entity)
|
||||
.updateOne(user.id, {
|
||||
strategy_value: await strategy.hash(password as string)
|
||||
});
|
||||
togglePw(false);
|
||||
|
||||
console.log("Updated:", user);
|
||||
} catch (e) {
|
||||
console.error("Error", e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user