keryx 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/classes/API.ts +24 -0
- package/config/index.ts +2 -0
- package/index.ts +2 -1
- package/keryx.ts +46 -0
- package/package.json +3 -2
- package/templates/scaffold/env.example.mustache +38 -0
- package/templates/scaffold/gitignore.mustache +3 -0
- package/templates/scaffold/hello-action.ts.mustache +16 -0
- package/templates/scaffold/index.ts.mustache +10 -0
- package/templates/scaffold/keryx.ts.mustache +62 -0
- package/templates/scaffold/migrations.ts.mustache +8 -0
- package/util/config.ts +29 -0
- package/util/scaffold.ts +211 -0
package/classes/API.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { Glob } from "bun";
|
|
2
|
+
import fs from "fs";
|
|
1
3
|
import path from "path";
|
|
2
4
|
import { config } from "../config";
|
|
5
|
+
import { deepMerge } from "../util/config";
|
|
3
6
|
import { globLoader } from "../util/glob";
|
|
4
7
|
import type { Initializer, InitializerSortKeys } from "./Initializer";
|
|
5
8
|
import { Logger } from "./Logger";
|
|
@@ -43,6 +46,7 @@ export class API {
|
|
|
43
46
|
this.logger.warn("--- 🔄 Initializing process ---");
|
|
44
47
|
this.initialized = false;
|
|
45
48
|
|
|
49
|
+
await this.loadLocalConfig();
|
|
46
50
|
await this.findInitializers();
|
|
47
51
|
this.sortInitializers("loadPriority");
|
|
48
52
|
|
|
@@ -138,6 +142,26 @@ export class API {
|
|
|
138
142
|
flapPreventer = false;
|
|
139
143
|
}
|
|
140
144
|
|
|
145
|
+
private async loadLocalConfig() {
|
|
146
|
+
if (this.rootDir === this.packageDir) return;
|
|
147
|
+
|
|
148
|
+
const configDir = path.join(this.rootDir, "config");
|
|
149
|
+
if (!fs.existsSync(configDir)) return;
|
|
150
|
+
|
|
151
|
+
const glob = new Glob("**/*.ts");
|
|
152
|
+
for await (const file of glob.scan(configDir)) {
|
|
153
|
+
if (file.startsWith(".")) continue;
|
|
154
|
+
|
|
155
|
+
const fullPath = path.join(configDir, file);
|
|
156
|
+
const mod = await import(fullPath);
|
|
157
|
+
const overrides = mod.default ?? mod;
|
|
158
|
+
if (overrides && typeof overrides === "object") {
|
|
159
|
+
deepMerge(config, overrides);
|
|
160
|
+
this.logger.debug(`Loaded user config from config/${file}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
141
165
|
private async findInitializers() {
|
|
142
166
|
// Load framework initializers from the package directory
|
|
143
167
|
const frameworkInitializers = await globLoader<Initializer>(
|
package/config/index.ts
CHANGED
package/index.ts
CHANGED
|
@@ -19,7 +19,8 @@ import "./initializers/swagger";
|
|
|
19
19
|
export * from "./api";
|
|
20
20
|
export type { ActionMiddleware } from "./classes/Action";
|
|
21
21
|
export { ErrorStatusCodes, ErrorType, TypedError } from "./classes/TypedError";
|
|
22
|
-
export {
|
|
22
|
+
export type { KeryxConfig } from "./config";
|
|
23
|
+
export { deepMerge, loadFromEnvIfSet } from "./util/config";
|
|
23
24
|
export { globLoader } from "./util/glob";
|
|
24
25
|
export {
|
|
25
26
|
isSecret,
|
package/keryx.ts
CHANGED
|
@@ -6,10 +6,56 @@ import { Action, api } from "./api";
|
|
|
6
6
|
import pkg from "./package.json";
|
|
7
7
|
import { addActionToProgram } from "./util/cli";
|
|
8
8
|
import { globLoader } from "./util/glob";
|
|
9
|
+
import {
|
|
10
|
+
interactiveScaffold,
|
|
11
|
+
scaffoldProject,
|
|
12
|
+
type ScaffoldOptions,
|
|
13
|
+
} from "./util/scaffold";
|
|
9
14
|
|
|
10
15
|
const program = new Command();
|
|
11
16
|
program.name(pkg.name).description(pkg.description).version(pkg.version);
|
|
12
17
|
|
|
18
|
+
program
|
|
19
|
+
.command("new [project-name]")
|
|
20
|
+
.summary("Create a new Keryx project")
|
|
21
|
+
.description("Scaffold a new Keryx application with project boilerplate")
|
|
22
|
+
.option("--no-interactive", "Skip prompts and use defaults")
|
|
23
|
+
.option("--no-db", "Skip database setup files")
|
|
24
|
+
.option("--no-example", "Skip example action")
|
|
25
|
+
.action(async (projectName: string | undefined, opts) => {
|
|
26
|
+
let options: ScaffoldOptions;
|
|
27
|
+
|
|
28
|
+
if (opts.interactive === false) {
|
|
29
|
+
// --no-interactive: use defaults
|
|
30
|
+
projectName = projectName || "my-keryx-app";
|
|
31
|
+
options = {
|
|
32
|
+
includeDb: opts.db !== false,
|
|
33
|
+
includeExample: opts.example !== false,
|
|
34
|
+
};
|
|
35
|
+
} else {
|
|
36
|
+
const result = await interactiveScaffold(projectName);
|
|
37
|
+
projectName = result.projectName;
|
|
38
|
+
options = result.options;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
42
|
+
|
|
43
|
+
console.log(`\nCreating new Keryx project: ${projectName}\n`);
|
|
44
|
+
|
|
45
|
+
const files = await scaffoldProject(projectName, targetDir, options);
|
|
46
|
+
files.forEach((f) => console.log(` ${f}`));
|
|
47
|
+
|
|
48
|
+
console.log(`
|
|
49
|
+
Done! To get started:
|
|
50
|
+
|
|
51
|
+
cd ${projectName}
|
|
52
|
+
cp .env.example .env
|
|
53
|
+
bun install
|
|
54
|
+
bun dev
|
|
55
|
+
`);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
13
59
|
program
|
|
14
60
|
.command("start")
|
|
15
61
|
.summary("Run the server")
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keryx",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"module": "index.ts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"description": "Keryx - the messenger of the gods. The greatest framework for building realtime AI, CLI, and web applications, and other applications too!",
|
|
8
|
-
"author": "Evan Tahler <evantahler
|
|
8
|
+
"author": "Evan Tahler <evan@evantahler.com>",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/evantahler/keryx.git",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"start": "bun keryx.ts start",
|
|
62
62
|
"dev": "bun --watch keryx.ts start",
|
|
63
63
|
"migrations": "bun run migrations.ts",
|
|
64
|
+
"test": "tsc && bun test",
|
|
64
65
|
"compile": "bun build keryx.ts --compile --outfile keryx",
|
|
65
66
|
"lint": "tsc && prettier --check .",
|
|
66
67
|
"format": "tsc && prettier --write ."
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
PROCESS_NAME={{projectName}}
|
|
2
|
+
PROCESS_NAME_TEST=test-server
|
|
3
|
+
PROCESS_SHUTDOWN_TIMEOUT=30000
|
|
4
|
+
|
|
5
|
+
LOG_LEVEL=info
|
|
6
|
+
LOG_LEVEL_TEST=fatal
|
|
7
|
+
LOG_INCLUDE_TIMESTAMPS=false
|
|
8
|
+
LOG_COLORIZE=true
|
|
9
|
+
|
|
10
|
+
WEB_SERVER_ENABLED=true
|
|
11
|
+
WEB_SERVER_PORT=8080
|
|
12
|
+
WEB_SERVER_PORT_TEST=0
|
|
13
|
+
WEB_SERVER_HOST=localhost
|
|
14
|
+
WEB_SERVER_API_ROUTE="/api"
|
|
15
|
+
WEB_SERVER_ALLOWED_ORIGINS="http://localhost:3000"
|
|
16
|
+
WEB_SERVER_ALLOWED_METHODS="GET, POST, PUT, DELETE, OPTIONS"
|
|
17
|
+
|
|
18
|
+
MCP_SERVER_ENABLED=true
|
|
19
|
+
|
|
20
|
+
SESSION_TTL=86400000
|
|
21
|
+
SESSION_COOKIE_NAME="__session"
|
|
22
|
+
|
|
23
|
+
DATABASE_URL="postgres://$USER@localhost:5432/{{projectName}}"
|
|
24
|
+
DATABASE_URL_TEST="postgres://$USER@localhost:5432/{{projectName}}-test"
|
|
25
|
+
DATABASE_AUTO_MIGRATE=true
|
|
26
|
+
|
|
27
|
+
REDIS_URL="redis://localhost:6379/0"
|
|
28
|
+
REDIS_URL_TEST="redis://localhost:6379/1"
|
|
29
|
+
|
|
30
|
+
RATE_LIMIT_ENABLED=true
|
|
31
|
+
RATE_LIMIT_WINDOW_MS=60000
|
|
32
|
+
RATE_LIMIT_UNAUTH_LIMIT=20
|
|
33
|
+
RATE_LIMIT_AUTH_LIMIT=200
|
|
34
|
+
|
|
35
|
+
TASKS_ENABLED=true
|
|
36
|
+
TASK_PROCESSORS=1
|
|
37
|
+
TASK_TIMEOUT=5000
|
|
38
|
+
TASK_TIMEOUT_TEST=100
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Action, type ActionParams } from "keryx";
|
|
3
|
+
import { HTTP_METHOD } from "keryx/classes/Action.ts";
|
|
4
|
+
|
|
5
|
+
export class Hello implements Action {
|
|
6
|
+
name = "hello";
|
|
7
|
+
description = "Say hello";
|
|
8
|
+
inputs = z.object({
|
|
9
|
+
name: z.string().default("World"),
|
|
10
|
+
});
|
|
11
|
+
web = { route: "/hello", method: HTTP_METHOD.GET };
|
|
12
|
+
|
|
13
|
+
async run(params: ActionParams<Hello>) {
|
|
14
|
+
return { message: `Hello, ${params.name}!` };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { api } from "keryx";
|
|
2
|
+
|
|
3
|
+
// Tell the framework where this project lives so it can auto-discover
|
|
4
|
+
// actions, initializers, channels, etc. from this directory.
|
|
5
|
+
// Every entry point (keryx.ts, migrations.ts, test setup) should
|
|
6
|
+
// `import "./index"` to ensure rootDir is set before anything runs.
|
|
7
|
+
api.rootDir = import.meta.dir;
|
|
8
|
+
|
|
9
|
+
// Re-export everything from keryx for convenience
|
|
10
|
+
export * from "keryx";
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#! /usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
// Set rootDir before any framework code loads actions
|
|
4
|
+
import { api } from "./index";
|
|
5
|
+
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { Action, globLoader } from "keryx";
|
|
8
|
+
import { addActionToProgram } from "keryx/util/cli.ts";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import pkg from "./package.json";
|
|
11
|
+
|
|
12
|
+
const program = new Command();
|
|
13
|
+
program.name(pkg.name).description(pkg.description).version(pkg.version);
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command("start")
|
|
17
|
+
.summary("Run the server")
|
|
18
|
+
.description("Start the Keryx server")
|
|
19
|
+
.action(async () => {
|
|
20
|
+
await api.start();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Load framework actions from the package directory
|
|
24
|
+
const frameworkActions = await globLoader<Action>(
|
|
25
|
+
path.join(api.packageDir, "actions"),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Load user project actions
|
|
29
|
+
let userActions: Action[] = [];
|
|
30
|
+
try {
|
|
31
|
+
userActions = await globLoader<Action>(path.join(api.rootDir, "actions"));
|
|
32
|
+
} catch {
|
|
33
|
+
// user project may not have actions, that's fine
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const actions = [...frameworkActions, ...userActions];
|
|
37
|
+
actions.forEach((action) => addActionToProgram(program, action));
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.command("actions")
|
|
41
|
+
.summary("List all actions")
|
|
42
|
+
.action(async () => {
|
|
43
|
+
const actionSpacing =
|
|
44
|
+
actions.map((a) => a.name.length).reduce((a, b) => Math.max(a, b), 0) +
|
|
45
|
+
2;
|
|
46
|
+
const routeSpacing =
|
|
47
|
+
actions
|
|
48
|
+
.map((a) =>
|
|
49
|
+
a.web ? a.web.route.toString().length + a.web.method.length : 0,
|
|
50
|
+
)
|
|
51
|
+
.reduce((a, b) => Math.max(a, b), 0) + 2;
|
|
52
|
+
|
|
53
|
+
actions
|
|
54
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
55
|
+
.forEach((action) => {
|
|
56
|
+
console.log(
|
|
57
|
+
`${action.name}${" ".repeat(actionSpacing - action.name.length)} ${action.web ? `[${action.web.method}] ${action.web.route}` : " "}${" ".repeat(routeSpacing - (action.web ? action.web.method.length + action.web.route.toString().length + 2 : 0))} ${action.description ?? ""}`,
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
program.parse();
|
package/util/config.ts
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Deep-merges source into target, mutating target in place.
|
|
3
|
+
Only plain objects are recursively merged; arrays and primitives are overwritten.
|
|
4
|
+
*/
|
|
5
|
+
export function deepMerge<T extends Record<string, any>>(
|
|
6
|
+
target: T,
|
|
7
|
+
source: Record<string, any>,
|
|
8
|
+
): T {
|
|
9
|
+
for (const key of Object.keys(source)) {
|
|
10
|
+
const targetVal = target[key];
|
|
11
|
+
const sourceVal = source[key];
|
|
12
|
+
|
|
13
|
+
if (
|
|
14
|
+
targetVal &&
|
|
15
|
+
sourceVal &&
|
|
16
|
+
typeof targetVal === "object" &&
|
|
17
|
+
typeof sourceVal === "object" &&
|
|
18
|
+
!Array.isArray(targetVal) &&
|
|
19
|
+
!Array.isArray(sourceVal)
|
|
20
|
+
) {
|
|
21
|
+
deepMerge(targetVal, sourceVal);
|
|
22
|
+
} else {
|
|
23
|
+
(target as any)[key] = sourceVal;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return target;
|
|
28
|
+
}
|
|
29
|
+
|
|
1
30
|
/**
|
|
2
31
|
Loads a value from the environment, if it's set, otherwise returns the default value.
|
|
3
32
|
*/
|
package/util/scaffold.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { Glob } from "bun";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import Mustache from "mustache";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import * as readline from "readline";
|
|
6
|
+
import pkg from "../package.json";
|
|
7
|
+
|
|
8
|
+
export interface ScaffoldOptions {
|
|
9
|
+
includeDb: boolean;
|
|
10
|
+
includeExample: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const templatesDir = path.join(import.meta.dir, "..", "templates", "scaffold");
|
|
14
|
+
|
|
15
|
+
async function loadTemplate(name: string): Promise<string> {
|
|
16
|
+
return Bun.file(path.join(templatesDir, name)).text();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function prompt(question: string, defaultValue: string): Promise<string> {
|
|
20
|
+
const rl = readline.createInterface({
|
|
21
|
+
input: process.stdin,
|
|
22
|
+
output: process.stdout,
|
|
23
|
+
});
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
rl.question(`${question} `, (answer) => {
|
|
26
|
+
rl.close();
|
|
27
|
+
resolve(answer.trim() || defaultValue);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function promptYesNo(
|
|
33
|
+
question: string,
|
|
34
|
+
defaultYes: boolean,
|
|
35
|
+
): Promise<boolean> {
|
|
36
|
+
const hint = defaultYes ? "Y/n" : "y/N";
|
|
37
|
+
const answer = await prompt(`${question} (${hint})`, defaultYes ? "y" : "n");
|
|
38
|
+
return answer.toLowerCase().startsWith("y");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function interactiveScaffold(
|
|
42
|
+
projectName?: string,
|
|
43
|
+
): Promise<{ projectName: string; options: ScaffoldOptions }> {
|
|
44
|
+
if (!projectName) {
|
|
45
|
+
projectName = await prompt("Project name:", "my-keryx-app");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const includeDb = await promptYesNo("Include database setup?", true);
|
|
49
|
+
const includeExample = await promptYesNo("Include example action?", true);
|
|
50
|
+
|
|
51
|
+
return { projectName, options: { includeDb, includeExample } };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function scaffoldProject(
|
|
55
|
+
projectName: string,
|
|
56
|
+
targetDir: string,
|
|
57
|
+
options: ScaffoldOptions,
|
|
58
|
+
): Promise<string[]> {
|
|
59
|
+
const keryxVersion = pkg.version;
|
|
60
|
+
const createdFiles: string[] = [];
|
|
61
|
+
|
|
62
|
+
if (fs.existsSync(targetDir)) {
|
|
63
|
+
throw new Error(`Directory "${projectName}" already exists`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
67
|
+
|
|
68
|
+
const view = { projectName, keryxVersion };
|
|
69
|
+
|
|
70
|
+
const write = async (filePath: string, content: string) => {
|
|
71
|
+
const fullPath = path.join(targetDir, filePath);
|
|
72
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
73
|
+
await Bun.write(fullPath, content);
|
|
74
|
+
createdFiles.push(filePath);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const writeTemplate = async (filePath: string, templateName: string) => {
|
|
78
|
+
const template = await loadTemplate(templateName);
|
|
79
|
+
await write(filePath, Mustache.render(template, view));
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// --- Always generated ---
|
|
83
|
+
|
|
84
|
+
// package.json is built programmatically (conditional deps/scripts)
|
|
85
|
+
await write(
|
|
86
|
+
"package.json",
|
|
87
|
+
JSON.stringify(
|
|
88
|
+
{
|
|
89
|
+
name: projectName,
|
|
90
|
+
version: "0.0.1",
|
|
91
|
+
module: "index.ts",
|
|
92
|
+
type: "module",
|
|
93
|
+
private: true,
|
|
94
|
+
license: "MIT",
|
|
95
|
+
bin: { keryx: "keryx.ts" },
|
|
96
|
+
scripts: {
|
|
97
|
+
start: "bun keryx.ts start",
|
|
98
|
+
dev: "bun --watch keryx.ts start",
|
|
99
|
+
...(options.includeDb ? { migrations: "bun run migrations.ts" } : {}),
|
|
100
|
+
lint: "tsc && prettier --check .",
|
|
101
|
+
format: "tsc && prettier --write .",
|
|
102
|
+
},
|
|
103
|
+
dependencies: {
|
|
104
|
+
keryx: `^${keryxVersion}`,
|
|
105
|
+
...(options.includeDb ? { "drizzle-zod": "^0.8.3" } : {}),
|
|
106
|
+
},
|
|
107
|
+
devDependencies: {
|
|
108
|
+
"@types/bun": "latest",
|
|
109
|
+
prettier: "^3.8.1",
|
|
110
|
+
...(options.includeDb ? { "drizzle-kit": "^0.20.18" } : {}),
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
null,
|
|
114
|
+
2,
|
|
115
|
+
) + "\n",
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// tsconfig.json is static JSON (no interpolation needed)
|
|
119
|
+
await write(
|
|
120
|
+
"tsconfig.json",
|
|
121
|
+
JSON.stringify(
|
|
122
|
+
{
|
|
123
|
+
compilerOptions: {
|
|
124
|
+
lib: ["ESNext"],
|
|
125
|
+
target: "ESNext",
|
|
126
|
+
module: "ESNext",
|
|
127
|
+
moduleResolution: "bundler",
|
|
128
|
+
types: ["bun-types"],
|
|
129
|
+
strict: true,
|
|
130
|
+
skipLibCheck: true,
|
|
131
|
+
noEmit: true,
|
|
132
|
+
esModuleInterop: true,
|
|
133
|
+
resolveJsonModule: true,
|
|
134
|
+
isolatedModules: true,
|
|
135
|
+
verbatimModuleSyntax: true,
|
|
136
|
+
noImplicitAny: true,
|
|
137
|
+
noImplicitReturns: true,
|
|
138
|
+
noUnusedLocals: true,
|
|
139
|
+
noUnusedParameters: true,
|
|
140
|
+
noFallthroughCasesInSwitch: true,
|
|
141
|
+
forceConsistentCasingInFileNames: true,
|
|
142
|
+
allowImportingTsExtensions: true,
|
|
143
|
+
},
|
|
144
|
+
include: ["**/*.ts"],
|
|
145
|
+
exclude: ["node_modules", "drizzle"],
|
|
146
|
+
},
|
|
147
|
+
null,
|
|
148
|
+
2,
|
|
149
|
+
) + "\n",
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
await writeTemplate("index.ts", "index.ts.mustache");
|
|
153
|
+
await writeTemplate("keryx.ts", "keryx.ts.mustache");
|
|
154
|
+
await writeTemplate(".env.example", "env.example.mustache");
|
|
155
|
+
await writeTemplate(".gitignore", "gitignore.mustache");
|
|
156
|
+
// Copy config files from the framework, adjusting imports for user projects
|
|
157
|
+
const configDir = path.join(import.meta.dir, "..", "config");
|
|
158
|
+
const glob = new Glob("**/*.ts");
|
|
159
|
+
for await (const file of glob.scan(configDir)) {
|
|
160
|
+
let content = await Bun.file(path.join(configDir, file)).text();
|
|
161
|
+
|
|
162
|
+
// Rewrite relative imports to package imports
|
|
163
|
+
content = content.replace(
|
|
164
|
+
/from ["']\.\.\/\.\.\/util\/config["']/g,
|
|
165
|
+
'from "keryx"',
|
|
166
|
+
);
|
|
167
|
+
content = content.replace(
|
|
168
|
+
/from ["']\.\.\/util\/config["']/g,
|
|
169
|
+
'from "keryx"',
|
|
170
|
+
);
|
|
171
|
+
content = content.replace(
|
|
172
|
+
/from ["']\.\.\/classes\/Logger["']/g,
|
|
173
|
+
'from "keryx/classes/Logger.ts"',
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// In index.ts, change `export const config` to `export default`
|
|
177
|
+
// and remove the KeryxConfig type export (it comes from the package)
|
|
178
|
+
if (file === "index.ts") {
|
|
179
|
+
content = content.replace("export const config =", "export default");
|
|
180
|
+
content = content.replace(
|
|
181
|
+
/\nexport type KeryxConfig = typeof config;\n/,
|
|
182
|
+
"\n",
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await write(`config/${file}`, content);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Create empty directories with .gitkeep
|
|
190
|
+
await write("initializers/.gitkeep", "");
|
|
191
|
+
await write("middleware/.gitkeep", "");
|
|
192
|
+
await write("channels/.gitkeep", "");
|
|
193
|
+
|
|
194
|
+
// --- Database setup ---
|
|
195
|
+
|
|
196
|
+
if (options.includeDb) {
|
|
197
|
+
await writeTemplate("migrations.ts", "migrations.ts.mustache");
|
|
198
|
+
await write("schema/.gitkeep", "");
|
|
199
|
+
await write("drizzle/.gitkeep", "");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// --- Example action ---
|
|
203
|
+
|
|
204
|
+
if (options.includeExample) {
|
|
205
|
+
await writeTemplate("actions/hello.ts", "hello-action.ts.mustache");
|
|
206
|
+
} else {
|
|
207
|
+
await write("actions/.gitkeep", "");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return createdFiles;
|
|
211
|
+
}
|