keryx 0.29.0 → 0.29.4

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.
@@ -2,6 +2,7 @@ import { Redis as RedisClient } from "ioredis";
2
2
  import { api, logger } from "../api";
3
3
  import { Initializer } from "../classes/Initializer";
4
4
  import { config } from "../config";
5
+
5
6
  import {
6
7
  formatConnectionStringForLogging,
7
8
  throwConnectionError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keryx",
3
- "version": "0.29.0",
3
+ "version": "0.29.4",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/testing/index.ts CHANGED
@@ -16,16 +16,20 @@ export {
16
16
  } from "./websocket";
17
17
 
18
18
  /**
19
- * Generous lifecycle hook timeout (15s) for `beforeAll` / `afterAll`.
19
+ * Generous lifecycle hook timeout (60s) for `beforeAll` / `afterAll`.
20
20
  *
21
21
  * `api.start()` and `api.stop()` connect to Redis, Postgres, run migrations,
22
- * etc. slower than a unit test, especially in CI. Pass this as the second
23
- * argument to `beforeAll` / `afterAll` so hooks don't time out.
22
+ * load plugins, bring up workers, and start servers. That can comfortably
23
+ * exceed 15s on a fresh CI runner with Docker service containers, especially
24
+ * with plugins loaded. If it times out Bun aborts the hook and `api.stop()`
25
+ * runs against partially-initialized state, which cascades into confusing
26
+ * "api.mcp is undefined" style errors during cleanup — always prefer a higher
27
+ * hook timeout over adding null guards to every initializer's `stop()`.
24
28
  *
25
29
  * Note: `bun:test`'s `setDefaultTimeout` and `bunfig.toml [test].timeout` only
26
30
  * apply to `test()` blocks, not lifecycle hooks.
27
31
  */
28
- export const HOOK_TIMEOUT = 15_000;
32
+ export const HOOK_TIMEOUT = 60_000;
29
33
 
30
34
  /**
31
35
  * Return the actual URL the web server bound to (with resolved port).
package/tsconfig.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "target": "ESNext",
5
5
  "module": "ESNext",
6
6
  "moduleResolution": "bundler",
7
- "types": ["bun-types"],
7
+ "types": ["bun"],
8
8
  "strict": true,
9
9
  "skipLibCheck": true,
10
10
  "noEmit": true,
package/util/cli.ts CHANGED
@@ -41,6 +41,10 @@ export async function buildProgram(opts: {
41
41
  .option("--no-interactive", "Skip prompts and use defaults")
42
42
  .option("--no-db", "Skip database setup files")
43
43
  .option("--no-example", "Skip example action")
44
+ .option(
45
+ "--force",
46
+ "Scaffold into an existing directory (skips files that already exist)",
47
+ )
44
48
  .action(async (projectName: string | undefined, cmdOpts) => {
45
49
  let options: ScaffoldOptions;
46
50
 
@@ -49,11 +53,12 @@ export async function buildProgram(opts: {
49
53
  options = {
50
54
  includeDb: cmdOpts.db !== false,
51
55
  includeExample: cmdOpts.example !== false,
56
+ force: cmdOpts.force === true,
52
57
  };
53
58
  } else {
54
59
  const result = await interactiveScaffold(projectName);
55
60
  projectName = result.projectName;
56
- options = result.options;
61
+ options = { ...result.options, force: cmdOpts.force === true };
57
62
  }
58
63
 
59
64
  const targetDir = path.resolve(process.cwd(), projectName);
package/util/scaffold.ts CHANGED
@@ -9,6 +9,12 @@ import { loadScaffoldTemplate as loadTemplate } from "./componentRegistry";
9
9
  export interface ScaffoldOptions {
10
10
  includeDb: boolean;
11
11
  includeExample: boolean;
12
+ /**
13
+ * When true, scaffold into an existing directory instead of refusing.
14
+ * Files that already exist on disk are left untouched (merge-skip); only
15
+ * missing files are created. User files are never overwritten.
16
+ */
17
+ force?: boolean;
12
18
  }
13
19
 
14
20
  async function prompt(question: string, defaultValue: string): Promise<string> {
@@ -263,9 +269,15 @@ export async function scaffoldProject(
263
269
  const keryxVersion = pkg.version;
264
270
  const createdFiles: string[] = [];
265
271
 
266
- if (fs.existsSync(targetDir)) {
272
+ const dirExists = fs.existsSync(targetDir);
273
+ if (dirExists && !options.force) {
267
274
  throw new Error(`Directory "${projectName}" already exists`);
268
275
  }
276
+ if (dirExists && options.force) {
277
+ console.log(
278
+ ` ⚠ scaffolding into existing directory — existing files will be preserved`,
279
+ );
280
+ }
269
281
 
270
282
  fs.mkdirSync(targetDir, { recursive: true });
271
283
 
@@ -273,6 +285,10 @@ export async function scaffoldProject(
273
285
 
274
286
  const write = async (filePath: string, content: string) => {
275
287
  const fullPath = path.join(targetDir, filePath);
288
+ if (options.force && fs.existsSync(fullPath)) {
289
+ console.log(` ⊘ skipped ${filePath}`);
290
+ return;
291
+ }
276
292
  fs.mkdirSync(path.dirname(fullPath), { recursive: true });
277
293
  await Bun.write(fullPath, content);
278
294
  createdFiles.push(filePath);