keryx 0.1.2 → 0.2.1

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 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
@@ -21,3 +21,5 @@ export const config = {
21
21
  server: { cli: configServerCli, web: configServerWeb, mcp: configServerMcp },
22
22
  tasks: configTasks,
23
23
  };
24
+
25
+ export type KeryxConfig = typeof config;
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 { loadFromEnvIfSet } from "./util/config";
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keryx",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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 ."
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 CHANGED
@@ -1,3 +1,4 @@
1
+ import { Glob } from "bun";
1
2
  import fs from "fs";
2
3
  import Mustache from "mustache";
3
4
  import path from "path";
@@ -152,6 +153,38 @@ export async function scaffoldProject(
152
153
  await writeTemplate("keryx.ts", "keryx.ts.mustache");
153
154
  await writeTemplate(".env.example", "env.example.mustache");
154
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
+ }
155
188
 
156
189
  // Create empty directories with .gitkeep
157
190
  await write("initializers/.gitkeep", "");