@korajs/cli 0.3.4 → 0.5.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.
Files changed (104) hide show
  1. package/dist/bin.cjs +1839 -829
  2. package/dist/bin.cjs.map +1 -1
  3. package/dist/bin.js +1107 -146
  4. package/dist/bin.js.map +1 -1
  5. package/dist/{chunk-HNVCNPER.js → chunk-3WNFM3QB.js} +107 -97
  6. package/dist/chunk-3WNFM3QB.js.map +1 -0
  7. package/dist/{chunk-ULTO5NFA.js → chunk-PSRM56B7.js} +2 -2
  8. package/dist/chunk-PSRM56B7.js.map +1 -0
  9. package/dist/{chunk-MIMLCXSX.js → chunk-SOTZIWIF.js} +267 -228
  10. package/dist/chunk-SOTZIWIF.js.map +1 -0
  11. package/dist/create.cjs +58 -48
  12. package/dist/create.cjs.map +1 -1
  13. package/dist/create.js +2 -2
  14. package/dist/index.cjs +322 -273
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +31 -29
  17. package/dist/index.d.ts +31 -29
  18. package/dist/index.js +2 -2
  19. package/package.json +9 -3
  20. package/templates/react-basic/README.md.hbs +10 -1
  21. package/templates/react-basic/src/App.tsx +112 -126
  22. package/templates/react-basic/src/index.css +165 -159
  23. package/templates/react-basic/src/kora-worker.ts +0 -1
  24. package/templates/react-basic/src/main.tsx +12 -12
  25. package/templates/react-basic/src/modules/todos/todo.mutations.ts +25 -0
  26. package/templates/react-basic/src/modules/todos/todo.queries.ts +5 -0
  27. package/templates/react-basic/src/modules/todos/todo.schema.ts +10 -0
  28. package/templates/react-basic/src/modules/todos/useTodos.ts +31 -0
  29. package/templates/react-basic/src/schema.ts +6 -12
  30. package/templates/react-basic/src/vite-env.d.ts +17 -0
  31. package/templates/react-basic/{tsconfig.json → tsconfig.json.hbs} +1 -2
  32. package/templates/react-basic/vite.config.ts +57 -50
  33. package/templates/react-sync/.env.example +19 -0
  34. package/templates/react-sync/README.md.hbs +43 -1
  35. package/templates/react-sync/package.json.hbs +3 -0
  36. package/templates/react-sync/server.ts +90 -26
  37. package/templates/react-sync/src/App.tsx +132 -130
  38. package/templates/react-sync/src/auth.ts +25 -0
  39. package/templates/react-sync/src/index.css +212 -165
  40. package/templates/react-sync/src/kora-worker.ts +0 -1
  41. package/templates/react-sync/src/main.tsx +34 -18
  42. package/templates/react-sync/src/modules/todos/todo.mutations.ts +25 -0
  43. package/templates/react-sync/src/modules/todos/todo.queries.ts +5 -0
  44. package/templates/react-sync/src/modules/todos/todo.schema.ts +10 -0
  45. package/templates/react-sync/src/modules/todos/useTodos.ts +31 -0
  46. package/templates/react-sync/src/schema.ts +6 -12
  47. package/templates/react-sync/src/vite-env.d.ts +17 -0
  48. package/templates/react-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
  49. package/templates/react-sync/vite.config.ts +67 -60
  50. package/templates/react-tailwind/README.md.hbs +10 -1
  51. package/templates/react-tailwind/src/App.tsx +186 -197
  52. package/templates/react-tailwind/src/index.css +3 -3
  53. package/templates/react-tailwind/src/kora-worker.ts +0 -1
  54. package/templates/react-tailwind/src/main.tsx +19 -12
  55. package/templates/react-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  56. package/templates/react-tailwind/src/modules/todos/todo.queries.ts +5 -0
  57. package/templates/react-tailwind/src/modules/todos/todo.schema.ts +10 -0
  58. package/templates/react-tailwind/src/modules/todos/useTodos.ts +31 -0
  59. package/templates/react-tailwind/src/schema.ts +6 -12
  60. package/templates/react-tailwind/src/vite-env.d.ts +17 -0
  61. package/templates/react-tailwind/{tsconfig.json → tsconfig.json.hbs} +1 -2
  62. package/templates/react-tailwind/vite.config.ts +57 -50
  63. package/templates/react-tailwind-sync/.env.example +19 -0
  64. package/templates/react-tailwind-sync/README.md.hbs +43 -1
  65. package/templates/react-tailwind-sync/package.json.hbs +3 -0
  66. package/templates/react-tailwind-sync/server.ts +90 -26
  67. package/templates/react-tailwind-sync/src/App.tsx +246 -222
  68. package/templates/react-tailwind-sync/src/auth.ts +25 -0
  69. package/templates/react-tailwind-sync/src/index.css +3 -3
  70. package/templates/react-tailwind-sync/src/kora-worker.ts +0 -1
  71. package/templates/react-tailwind-sync/src/main.tsx +48 -18
  72. package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  73. package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  74. package/templates/react-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  75. package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +31 -0
  76. package/templates/react-tailwind-sync/src/schema.ts +6 -12
  77. package/templates/react-tailwind-sync/src/vite-env.d.ts +17 -0
  78. package/templates/react-tailwind-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
  79. package/templates/react-tailwind-sync/vite.config.ts +67 -60
  80. package/templates/tauri-react/.env.example +20 -0
  81. package/templates/tauri-react/README.md.hbs +36 -1
  82. package/templates/tauri-react/dev.ts +11 -11
  83. package/templates/tauri-react/package.json.hbs +2 -0
  84. package/templates/tauri-react/server.ts +86 -12
  85. package/templates/tauri-react/src/App.tsx +769 -544
  86. package/templates/tauri-react/src/AppShell.tsx +125 -83
  87. package/templates/tauri-react/src/SetupScreen.tsx +257 -208
  88. package/templates/tauri-react/src/auth.ts +45 -0
  89. package/templates/tauri-react/src/main.tsx +3 -3
  90. package/templates/tauri-react/src/modules/todos/todo.mutations.ts +25 -0
  91. package/templates/tauri-react/src/modules/todos/todo.queries.ts +5 -0
  92. package/templates/tauri-react/src/modules/todos/todo.schema.ts +10 -0
  93. package/templates/tauri-react/src/modules/todos/useTodos.ts +31 -0
  94. package/templates/tauri-react/src/schema.ts +6 -12
  95. package/templates/tauri-react/src/sync-config.ts +47 -47
  96. package/templates/tauri-react/src/updater.ts +15 -15
  97. package/templates/tauri-react/src/vite-env.d.ts +17 -0
  98. package/templates/tauri-react/src-tauri/tauri.conf.json +2 -2
  99. package/templates/tauri-react/tsconfig.json.hbs +14 -0
  100. package/templates/tauri-react/vite.config.ts +9 -9
  101. package/dist/chunk-HNVCNPER.js.map +0 -1
  102. package/dist/chunk-MIMLCXSX.js.map +0 -1
  103. package/dist/chunk-ULTO5NFA.js.map +0 -1
  104. package/templates/tauri-react/tsconfig.json +0 -15
package/dist/bin.cjs CHANGED
@@ -23,15 +23,224 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/bin.ts
26
- var import_citty6 = require("citty");
26
+ var import_citty11 = require("citty");
27
27
 
28
- // src/commands/create/create-command.ts
29
- var import_node_child_process2 = require("child_process");
30
- var import_node_fs2 = require("fs");
31
- var import_node_path5 = require("path");
32
- var import_node_url2 = require("url");
28
+ // src/commands/backup/backup-command.ts
29
+ var import_promises = require("fs/promises");
30
+ var import_node_path = require("path");
33
31
  var import_citty = require("citty");
34
32
 
33
+ // src/utils/logger.ts
34
+ var RESET = "\x1B[0m";
35
+ var BOLD = "\x1B[1m";
36
+ var DIM = "\x1B[2m";
37
+ var GREEN = "\x1B[32m";
38
+ var YELLOW = "\x1B[33m";
39
+ var RED = "\x1B[31m";
40
+ var CYAN = "\x1B[36m";
41
+ function createLogger(options) {
42
+ const colorDisabled = options?.noColor === true || process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
43
+ function color(code, text) {
44
+ return colorDisabled ? text : `${code}${text}${RESET}`;
45
+ }
46
+ return {
47
+ info(message) {
48
+ console.log(color(CYAN, message));
49
+ },
50
+ success(message) {
51
+ console.log(color(GREEN, ` \u2713 ${message}`));
52
+ },
53
+ warn(message) {
54
+ console.warn(color(YELLOW, ` \u26A0 ${message}`));
55
+ },
56
+ error(message) {
57
+ console.error(color(RED, ` \u2717 ${message}`));
58
+ },
59
+ step(message) {
60
+ console.log(color(DIM, ` ${message}`));
61
+ },
62
+ blank() {
63
+ console.log();
64
+ },
65
+ banner() {
66
+ console.log();
67
+ console.log(
68
+ color(BOLD + CYAN, " Kora.js") + color(DIM, " \u2014 Offline-first application framework")
69
+ );
70
+ console.log();
71
+ }
72
+ };
73
+ }
74
+
75
+ // src/commands/backup/backup-command.ts
76
+ var DEFAULT_SYNC_PORT = 3001;
77
+ var backupCommand = (0, import_citty.defineCommand)({
78
+ meta: {
79
+ name: "backup",
80
+ description: "Backup and restore Kora sync server data"
81
+ },
82
+ subCommands: {
83
+ create: (0, import_citty.defineCommand)({
84
+ meta: {
85
+ name: "create",
86
+ description: "Create a backup of the sync server"
87
+ },
88
+ args: {
89
+ url: {
90
+ type: "string",
91
+ description: "Sync server URL (default: http://localhost:3001)",
92
+ default: `http://localhost:${DEFAULT_SYNC_PORT}`
93
+ },
94
+ out: {
95
+ type: "string",
96
+ description: "Output file path (default: kora-backup-<timestamp>.kora)"
97
+ },
98
+ token: {
99
+ type: "string",
100
+ description: "Backup token (defaults to KORA_BACKUP_TOKEN or KORA_ADMIN_TOKEN)"
101
+ }
102
+ },
103
+ async run({ args }) {
104
+ const logger = createLogger();
105
+ const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT}`;
106
+ const outFile = typeof args.out === "string" ? args.out : `kora-backup-${Date.now()}.kora`;
107
+ const token = typeof args.token === "string" ? args.token : process.env.KORA_BACKUP_TOKEN ?? process.env.KORA_ADMIN_TOKEN;
108
+ logger.banner();
109
+ logger.info(`Exporting backup from ${url}...`);
110
+ try {
111
+ const backupUrl = `${url.replace(/\/$/, "")}/__kora/backup/export`;
112
+ const response = await fetch(backupUrl, {
113
+ method: "POST",
114
+ headers: token ? { Authorization: `Bearer ${token}` } : void 0
115
+ });
116
+ if (!response.ok) {
117
+ const error = await response.json().catch(() => ({ message: response.statusText }));
118
+ throw new Error(
119
+ `Backup failed: ${error.message ?? response.statusText}`
120
+ );
121
+ }
122
+ const buffer = await response.arrayBuffer();
123
+ await (0, import_promises.writeFile)(outFile, new Uint8Array(buffer));
124
+ const size = (buffer.byteLength / 1024).toFixed(1);
125
+ logger.success(`Backup saved to ${outFile} (${size} KB)`);
126
+ } catch (error) {
127
+ logger.error("Backup failed");
128
+ if (error instanceof Error) logger.error(error.message);
129
+ logger.blank();
130
+ logger.step("Make sure the Kora sync server is running.");
131
+ process.exit(1);
132
+ }
133
+ }
134
+ }),
135
+ restore: (0, import_citty.defineCommand)({
136
+ meta: {
137
+ name: "restore",
138
+ description: "Restore a backup to the sync server"
139
+ },
140
+ args: {
141
+ file: {
142
+ type: "string",
143
+ description: "Backup file path",
144
+ required: true
145
+ },
146
+ url: {
147
+ type: "string",
148
+ description: "Sync server URL (default: http://localhost:3001)",
149
+ default: `http://localhost:${DEFAULT_SYNC_PORT}`
150
+ },
151
+ merge: {
152
+ type: "boolean",
153
+ description: "Merge with existing data instead of replacing",
154
+ default: false
155
+ },
156
+ token: {
157
+ type: "string",
158
+ description: "Backup token (defaults to KORA_BACKUP_TOKEN or KORA_ADMIN_TOKEN)"
159
+ }
160
+ },
161
+ async run({ args }) {
162
+ const logger = createLogger();
163
+ const filePath = typeof args.file === "string" ? args.file : "";
164
+ const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT}`;
165
+ const merge = args.merge === true;
166
+ const token = typeof args.token === "string" ? args.token : process.env.KORA_BACKUP_TOKEN ?? process.env.KORA_ADMIN_TOKEN;
167
+ logger.banner();
168
+ logger.info(`Restoring backup from ${filePath} to ${url}...`);
169
+ try {
170
+ const data = await (0, import_promises.readFile)(filePath);
171
+ const restoreUrl = `${url.replace(/\/$/, "")}/__kora/backup/import?merge=${merge}`;
172
+ const response = await fetch(restoreUrl, {
173
+ method: "POST",
174
+ headers: token ? { Authorization: `Bearer ${token}` } : void 0,
175
+ body: data
176
+ });
177
+ if (!response.ok) {
178
+ const error = await response.json().catch(() => ({ message: response.statusText }));
179
+ throw new Error(
180
+ `Restore failed: ${error.message ?? response.statusText}`
181
+ );
182
+ }
183
+ const result = await response.json();
184
+ if (result.success) {
185
+ logger.success(
186
+ `Restored ${result.operationsRestored} operations${result.duration ? ` in ${result.duration}ms` : ""}`
187
+ );
188
+ } else {
189
+ logger.error("Restore completed with errors");
190
+ }
191
+ } catch (error) {
192
+ logger.error("Restore failed");
193
+ if (error instanceof Error) logger.error(error.message);
194
+ process.exit(1);
195
+ }
196
+ }
197
+ }),
198
+ info: (0, import_citty.defineCommand)({
199
+ meta: {
200
+ name: "info",
201
+ description: "Show backup file information"
202
+ },
203
+ args: {
204
+ file: {
205
+ type: "string",
206
+ description: "Backup file path",
207
+ required: true
208
+ }
209
+ },
210
+ async run({ args }) {
211
+ const logger = createLogger();
212
+ const filePath = typeof args.file === "string" ? args.file : "";
213
+ try {
214
+ const data = await (0, import_promises.readFile)(filePath);
215
+ const { readBackupManifest } = await import("@korajs/store");
216
+ const manifest = readBackupManifest(
217
+ new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
218
+ );
219
+ logger.banner();
220
+ logger.info(`Backup: ${filePath}`);
221
+ logger.blank();
222
+ logger.step(`Created: ${new Date(manifest.createdAt).toISOString()}`);
223
+ logger.step(`Node ID: ${manifest.nodeId}`);
224
+ logger.step(`Schema: v${manifest.schemaVersion}`);
225
+ logger.step(`Operations: ${manifest.operationCount.toLocaleString()}`);
226
+ logger.step(`Collections: ${manifest.collections.join(", ") || "(none)"}`);
227
+ logger.step(`Checksum: ${manifest.checksum.slice(0, 16)}...`);
228
+ logger.blank();
229
+ logger.step(`Format version: ${manifest.version}`);
230
+ } catch (error) {
231
+ logger.error("Failed to read backup file");
232
+ if (error instanceof Error) logger.error(error.message);
233
+ process.exit(1);
234
+ }
235
+ }
236
+ })
237
+ }
238
+ });
239
+
240
+ // src/commands/compact/compact-command.ts
241
+ var import_node_path5 = require("path");
242
+ var import_citty2 = require("citty");
243
+
35
244
  // src/errors.ts
36
245
  var import_core = require("@korajs/core");
37
246
  var ProjectExistsError = class extends import_core.KoraError {
@@ -85,6 +294,330 @@ var DevServerError = class extends import_core.KoraError {
85
294
  searchPath;
86
295
  };
87
296
 
297
+ // src/utils/fs-helpers.ts
298
+ var import_promises2 = require("fs/promises");
299
+ var import_node_path2 = require("path");
300
+ async function directoryExists(path) {
301
+ try {
302
+ await (0, import_promises2.access)(path);
303
+ return true;
304
+ } catch {
305
+ return false;
306
+ }
307
+ }
308
+ async function findProjectRoot(startDir) {
309
+ let current = (0, import_node_path2.resolve)(startDir ?? process.cwd());
310
+ for (; ; ) {
311
+ const pkgPath = (0, import_node_path2.join)(current, "package.json");
312
+ try {
313
+ const content = await (0, import_promises2.readFile)(pkgPath, "utf-8");
314
+ const pkg = JSON.parse(content);
315
+ if (isKoraProject(pkg)) {
316
+ return current;
317
+ }
318
+ } catch {
319
+ }
320
+ const parent = (0, import_node_path2.dirname)(current);
321
+ if (parent === current) break;
322
+ current = parent;
323
+ }
324
+ return null;
325
+ }
326
+ async function findSchemaFile(projectRoot) {
327
+ const candidates = [
328
+ (0, import_node_path2.join)(projectRoot, "src", "schema.ts"),
329
+ (0, import_node_path2.join)(projectRoot, "schema.ts"),
330
+ (0, import_node_path2.join)(projectRoot, "src", "schema.js"),
331
+ (0, import_node_path2.join)(projectRoot, "schema.js")
332
+ ];
333
+ for (const candidate of candidates) {
334
+ try {
335
+ await (0, import_promises2.access)(candidate);
336
+ return candidate;
337
+ } catch {
338
+ }
339
+ }
340
+ return null;
341
+ }
342
+ async function hasTsxInstalled(projectRoot) {
343
+ try {
344
+ await (0, import_promises2.access)((0, import_node_path2.join)(projectRoot, "node_modules", "tsx", "package.json"));
345
+ return true;
346
+ } catch {
347
+ return false;
348
+ }
349
+ }
350
+ async function resolveProjectBinaryEntryPoint(projectRoot, packageName, binaryName) {
351
+ const pkgJsonPath = (0, import_node_path2.join)(projectRoot, "node_modules", packageName, "package.json");
352
+ try {
353
+ const content = await (0, import_promises2.readFile)(pkgJsonPath, "utf-8");
354
+ const pkg = JSON.parse(content);
355
+ let binPath;
356
+ if (typeof pkg.bin === "string") {
357
+ binPath = pkg.bin;
358
+ } else if (typeof pkg.bin === "object" && pkg.bin !== null) {
359
+ binPath = pkg.bin[binaryName];
360
+ }
361
+ if (!binPath) return null;
362
+ const fullPath = (0, import_node_path2.join)(projectRoot, "node_modules", packageName, binPath);
363
+ await (0, import_promises2.access)(fullPath);
364
+ return fullPath;
365
+ } catch {
366
+ return null;
367
+ }
368
+ }
369
+ function isKoraProject(pkg) {
370
+ if (typeof pkg !== "object" || pkg === null) return false;
371
+ const record = pkg;
372
+ return hasKoraDep(record.dependencies) || hasKoraDep(record.devDependencies);
373
+ }
374
+ function hasKoraDep(deps) {
375
+ if (typeof deps !== "object" || deps === null) return false;
376
+ return Object.keys(deps).some((key) => key === "kora" || key.startsWith("@korajs/"));
377
+ }
378
+
379
+ // src/commands/dev/kora-config.ts
380
+ var import_node_child_process = require("child_process");
381
+ var import_promises3 = require("fs/promises");
382
+ var import_node_path3 = require("path");
383
+ var import_node_url = require("url");
384
+ var CONFIG_CANDIDATES = [
385
+ "kora.config.ts",
386
+ "kora.config.mts",
387
+ "kora.config.cts",
388
+ "kora.config.js",
389
+ "kora.config.mjs",
390
+ "kora.config.cjs"
391
+ ];
392
+ async function loadKoraConfig(projectRoot) {
393
+ const configPath = await findKoraConfigFile(projectRoot);
394
+ if (!configPath) return null;
395
+ const ext = (0, import_node_path3.extname)(configPath);
396
+ if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
397
+ const loaded2 = await loadTypeScriptConfig(configPath, projectRoot);
398
+ return toConfigObject(loaded2);
399
+ }
400
+ const loaded = await import((0, import_node_url.pathToFileURL)(configPath).href);
401
+ return toConfigObject(loaded);
402
+ }
403
+ async function findKoraConfigFile(projectRoot) {
404
+ for (const file of CONFIG_CANDIDATES) {
405
+ const candidate = (0, import_node_path3.join)(projectRoot, file);
406
+ try {
407
+ await (0, import_promises3.access)(candidate);
408
+ return candidate;
409
+ } catch {
410
+ }
411
+ }
412
+ return null;
413
+ }
414
+ async function loadTypeScriptConfig(configPath, projectRoot) {
415
+ if (!await hasTsxInstalled(projectRoot)) {
416
+ throw new Error(
417
+ `Found TypeScript config at ${configPath}, but "tsx" is not installed in this project. Install tsx or use kora.config.js.`
418
+ );
419
+ }
420
+ const script = "const configPath = process.argv[process.argv.length - 1];import('node:url').then(u => import(u.pathToFileURL(configPath).href)).then(mod => { const v = mod.default ?? mod; process.stdout.write(JSON.stringify(v)) }).catch(e => { process.stderr.write(String(e)); process.exit(1) })";
421
+ const output = await runCommand(
422
+ process.execPath,
423
+ ["--import", "tsx", "--eval", script, configPath],
424
+ projectRoot
425
+ );
426
+ try {
427
+ return JSON.parse(output);
428
+ } catch {
429
+ throw new Error(`Failed to parse ${configPath} output as JSON.`);
430
+ }
431
+ }
432
+ async function runCommand(command, args, cwd) {
433
+ return await new Promise((resolve11, reject) => {
434
+ const child = (0, import_node_child_process.spawn)(command, args, {
435
+ cwd,
436
+ stdio: ["ignore", "pipe", "pipe"],
437
+ env: process.env
438
+ });
439
+ let stdout = "";
440
+ let stderr = "";
441
+ child.stdout?.on("data", (chunk) => {
442
+ stdout += chunk.toString("utf-8");
443
+ });
444
+ child.stderr?.on("data", (chunk) => {
445
+ stderr += chunk.toString("utf-8");
446
+ });
447
+ child.on("error", (error) => {
448
+ reject(error);
449
+ });
450
+ child.on("exit", (code) => {
451
+ if (code === 0) {
452
+ resolve11(stdout.trim());
453
+ return;
454
+ }
455
+ reject(new Error(`Failed to load kora config (exit ${code ?? "unknown"}): ${stderr.trim()}`));
456
+ });
457
+ });
458
+ }
459
+ function toConfigObject(mod) {
460
+ if (typeof mod !== "object" || mod === null) {
461
+ throw new Error("kora config must export an object.");
462
+ }
463
+ const value = mod.default ?? mod;
464
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
465
+ throw new Error("kora config must export an object.");
466
+ }
467
+ return value;
468
+ }
469
+
470
+ // src/commands/migrate/schema-loader.ts
471
+ var import_node_child_process2 = require("child_process");
472
+ var import_node_path4 = require("path");
473
+ var import_node_url2 = require("url");
474
+ async function loadSchemaDefinition(schemaPath, projectRoot) {
475
+ const ext = (0, import_node_path4.extname)(schemaPath);
476
+ const moduleValue = ext === ".ts" || ext === ".mts" || ext === ".cts" ? await loadTypeScriptModule(schemaPath, projectRoot) : await import(`${(0, import_node_url2.pathToFileURL)(schemaPath).href}?t=${Date.now()}-${Math.random()}`);
477
+ return extractSchema(moduleValue);
478
+ }
479
+ function extractSchema(value) {
480
+ if (typeof value !== "object" || value === null) {
481
+ throw new Error("Schema module must export an object.");
482
+ }
483
+ const moduleRecord = value;
484
+ const candidate = moduleRecord.default ?? moduleRecord;
485
+ if (!isSchemaDefinition(candidate)) {
486
+ throw new Error("Schema module must export a valid SchemaDefinition as default export.");
487
+ }
488
+ return candidate;
489
+ }
490
+ function isSchemaDefinition(value) {
491
+ if (typeof value !== "object" || value === null) return false;
492
+ const object = value;
493
+ return typeof object.version === "number" && typeof object.collections === "object" && object.collections !== null && typeof object.relations === "object" && object.relations !== null;
494
+ }
495
+ async function loadTypeScriptModule(schemaPath, projectRoot) {
496
+ if (!await hasTsxInstalled(projectRoot)) {
497
+ throw new Error(
498
+ `Schema file is TypeScript (${schemaPath}) but local "tsx" was not found. Install tsx in the project.`
499
+ );
500
+ }
501
+ const script = "const modulePath = process.argv[process.argv.length - 1];import('node:url').then(u => import(u.pathToFileURL(modulePath).href)).then(mod => { const v = mod.default ?? mod; process.stdout.write(JSON.stringify(v)) }).catch(e => { process.stderr.write(String(e)); process.exit(1) })";
502
+ const output = await runCommand2(
503
+ process.execPath,
504
+ ["--import", "tsx", "--eval", script, schemaPath],
505
+ projectRoot
506
+ );
507
+ try {
508
+ return JSON.parse(output);
509
+ } catch {
510
+ throw new Error(`Failed to parse schema module output for ${schemaPath}`);
511
+ }
512
+ }
513
+ async function runCommand2(command, args, cwd) {
514
+ return await new Promise((resolve11, reject) => {
515
+ const child = (0, import_node_child_process2.spawn)(command, args, {
516
+ cwd,
517
+ stdio: ["ignore", "pipe", "pipe"],
518
+ env: process.env
519
+ });
520
+ let stdout = "";
521
+ let stderr = "";
522
+ child.stdout?.on("data", (chunk) => {
523
+ stdout += chunk.toString("utf-8");
524
+ });
525
+ child.stderr?.on("data", (chunk) => {
526
+ stderr += chunk.toString("utf-8");
527
+ });
528
+ child.on("error", (error) => {
529
+ reject(error);
530
+ });
531
+ child.on("exit", (code) => {
532
+ if (code === 0) {
533
+ resolve11(stdout.trim());
534
+ return;
535
+ }
536
+ reject(
537
+ new Error(`Failed to load TypeScript schema (exit ${code ?? "unknown"}): ${stderr.trim()}`)
538
+ );
539
+ });
540
+ });
541
+ }
542
+
543
+ // src/commands/compact/compact-command.ts
544
+ var compactCommand = (0, import_citty2.defineCommand)({
545
+ meta: {
546
+ name: "compact",
547
+ description: "Compact the local Kora operation log after server ack"
548
+ },
549
+ args: {
550
+ db: {
551
+ type: "string",
552
+ description: "Path to the local SQLite database (required)"
553
+ },
554
+ schema: {
555
+ type: "string",
556
+ description: "Path to schema file"
557
+ },
558
+ strategy: {
559
+ type: "string",
560
+ description: "Compaction strategy: after-ack | after-days | never",
561
+ default: "after-ack"
562
+ },
563
+ days: {
564
+ type: "string",
565
+ description: "Age threshold when strategy is after-days",
566
+ default: "30"
567
+ }
568
+ },
569
+ async run({ args }) {
570
+ const logger = createLogger();
571
+ const projectRoot = await findProjectRoot();
572
+ if (!projectRoot) {
573
+ throw new InvalidProjectError(process.cwd());
574
+ }
575
+ const dbPath = typeof args.db === "string" ? (0, import_node_path5.resolve)(projectRoot, args.db) : void 0;
576
+ if (!dbPath) {
577
+ throw new Error("Missing --db <path> to the local SQLite database file.");
578
+ }
579
+ const config = await loadKoraConfig(projectRoot);
580
+ const schemaPath = typeof args.schema === "string" ? (0, import_node_path5.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path5.resolve)(projectRoot, config.schema) : void 0;
581
+ if (!schemaPath) {
582
+ throw new Error("Could not resolve schema path. Pass --schema <path>.");
583
+ }
584
+ const schema = await loadSchemaDefinition(schemaPath, projectRoot);
585
+ const { BetterSqlite3Adapter } = await import("@korajs/store/better-sqlite3");
586
+ const { Store } = await import("@korajs/store");
587
+ const strategyName = typeof args.strategy === "string" ? args.strategy : "after-ack";
588
+ const days = Number.parseInt(typeof args.days === "string" ? args.days : "30", 10);
589
+ const store = new Store({
590
+ schema,
591
+ adapter: new BetterSqlite3Adapter(dbPath)
592
+ });
593
+ await store.open();
594
+ let result;
595
+ if (strategyName === "never") {
596
+ result = await store.compact({ mode: "never" });
597
+ } else if (strategyName === "after-days") {
598
+ result = await store.compact({ mode: "after-days", days });
599
+ } else if (strategyName === "after-ack") {
600
+ result = await store.compact({ mode: "after-ack" });
601
+ } else {
602
+ await store.close();
603
+ throw new Error(`Unknown strategy "${strategyName}". Use after-ack, after-days, or never.`);
604
+ }
605
+ await store.close();
606
+ logger.banner();
607
+ logger.success(`Compacted ${result.deletedCount} operation log entries.`);
608
+ for (const [nodeId, seq] of result.watermark) {
609
+ logger.step(` ${nodeId}: seq <= ${seq}`);
610
+ }
611
+ }
612
+ });
613
+
614
+ // src/commands/create/create-command.ts
615
+ var import_node_child_process4 = require("child_process");
616
+ var import_node_fs2 = require("fs");
617
+ var import_node_path9 = require("path");
618
+ var import_node_url4 = require("url");
619
+ var import_citty3 = require("citty");
620
+
88
621
  // src/prompts/preferences.ts
89
622
  var import_conf = __toESM(require("conf"), 1);
90
623
  var DEFAULT_PREFERENCES = {
@@ -128,18 +661,18 @@ var import_prompts = require("@clack/prompts");
128
661
  // src/utils/prompt.ts
129
662
  var import_node_readline = require("readline");
130
663
  function promptText(message, defaultValue, options) {
131
- return new Promise((resolve9) => {
664
+ return new Promise((resolve11) => {
132
665
  const rl = createReadline(options);
133
666
  const suffix = defaultValue !== void 0 ? ` (${defaultValue})` : "";
134
667
  rl.question(` ? ${message}${suffix}: `, (answer) => {
135
668
  rl.close();
136
669
  const trimmed = answer.trim();
137
- resolve9(trimmed || defaultValue || "");
670
+ resolve11(trimmed || defaultValue || "");
138
671
  });
139
672
  });
140
673
  }
141
674
  function promptSelect(message, choices, options) {
142
- return new Promise((resolve9) => {
675
+ return new Promise((resolve11) => {
143
676
  const rl = createReadline(options);
144
677
  const out = options?.output ?? process.stdout;
145
678
  out.write(` ? ${message}
@@ -157,7 +690,7 @@ function promptSelect(message, choices, options) {
157
690
  const selected = choices[index];
158
691
  if (selected) {
159
692
  rl.close();
160
- resolve9(selected.value);
693
+ resolve11(selected.value);
161
694
  } else {
162
695
  out.write(` Please enter a number between 1 and ${choices.length}
163
696
  `);
@@ -169,7 +702,7 @@ function promptSelect(message, choices, options) {
169
702
  });
170
703
  }
171
704
  function promptConfirm(message, defaultValue = false, options) {
172
- return new Promise((resolve9) => {
705
+ return new Promise((resolve11) => {
173
706
  const rl = createReadline(options);
174
707
  const suffix = defaultValue ? "Y/n" : "y/N";
175
708
  const ask = () => {
@@ -177,19 +710,20 @@ function promptConfirm(message, defaultValue = false, options) {
177
710
  const normalized = answer.trim().toLowerCase();
178
711
  if (normalized.length === 0) {
179
712
  rl.close();
180
- resolve9(defaultValue);
713
+ resolve11(defaultValue);
181
714
  return;
182
715
  }
183
716
  if (normalized === "y" || normalized === "yes") {
184
717
  rl.close();
185
- resolve9(true);
718
+ resolve11(true);
186
719
  return;
187
720
  }
188
721
  if (normalized === "n" || normalized === "no") {
189
722
  rl.close();
190
- resolve9(false);
723
+ resolve11(false);
191
724
  return;
192
725
  }
726
+ ;
193
727
  (options?.output ?? process.stdout).write(" Please answer with y or n\n");
194
728
  ask();
195
729
  });
@@ -275,156 +809,32 @@ var ClackPromptClient = class {
275
809
  message,
276
810
  initialValue: defaultValue
277
811
  });
278
- if ((0, import_prompts.isCancel)(result)) {
279
- (0, import_prompts.cancel)("Operation cancelled.");
280
- throw new PromptCancelledError();
281
- }
282
- return result;
283
- }
284
- intro(message) {
285
- (0, import_prompts.intro)(message);
286
- }
287
- outro(message) {
288
- (0, import_prompts.outro)(message);
289
- }
290
- };
291
-
292
- // src/types.ts
293
- var PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
294
- var TEMPLATES = [
295
- "react-tailwind-sync",
296
- "react-tailwind",
297
- "react-sync",
298
- "react-basic",
299
- "tauri-react"
300
- ];
301
-
302
- // src/utils/fs-helpers.ts
303
- var import_promises = require("fs/promises");
304
- var import_node_path = require("path");
305
- async function directoryExists(path) {
306
- try {
307
- await (0, import_promises.access)(path);
308
- return true;
309
- } catch {
310
- return false;
311
- }
312
- }
313
- async function findProjectRoot(startDir) {
314
- let current = (0, import_node_path.resolve)(startDir ?? process.cwd());
315
- for (; ; ) {
316
- const pkgPath = (0, import_node_path.join)(current, "package.json");
317
- try {
318
- const content = await (0, import_promises.readFile)(pkgPath, "utf-8");
319
- const pkg = JSON.parse(content);
320
- if (isKoraProject(pkg)) {
321
- return current;
322
- }
323
- } catch {
324
- }
325
- const parent = (0, import_node_path.dirname)(current);
326
- if (parent === current) break;
327
- current = parent;
328
- }
329
- return null;
330
- }
331
- async function findSchemaFile(projectRoot) {
332
- const candidates = [
333
- (0, import_node_path.join)(projectRoot, "src", "schema.ts"),
334
- (0, import_node_path.join)(projectRoot, "schema.ts"),
335
- (0, import_node_path.join)(projectRoot, "src", "schema.js"),
336
- (0, import_node_path.join)(projectRoot, "schema.js")
337
- ];
338
- for (const candidate of candidates) {
339
- try {
340
- await (0, import_promises.access)(candidate);
341
- return candidate;
342
- } catch {
812
+ if ((0, import_prompts.isCancel)(result)) {
813
+ (0, import_prompts.cancel)("Operation cancelled.");
814
+ throw new PromptCancelledError();
343
815
  }
816
+ return result;
344
817
  }
345
- return null;
346
- }
347
- async function hasTsxInstalled(projectRoot) {
348
- try {
349
- await (0, import_promises.access)((0, import_node_path.join)(projectRoot, "node_modules", "tsx", "package.json"));
350
- return true;
351
- } catch {
352
- return false;
818
+ intro(message) {
819
+ (0, import_prompts.intro)(message);
353
820
  }
354
- }
355
- async function resolveProjectBinaryEntryPoint(projectRoot, packageName, binaryName) {
356
- const pkgJsonPath = (0, import_node_path.join)(projectRoot, "node_modules", packageName, "package.json");
357
- try {
358
- const content = await (0, import_promises.readFile)(pkgJsonPath, "utf-8");
359
- const pkg = JSON.parse(content);
360
- let binPath;
361
- if (typeof pkg.bin === "string") {
362
- binPath = pkg.bin;
363
- } else if (typeof pkg.bin === "object" && pkg.bin !== null) {
364
- binPath = pkg.bin[binaryName];
365
- }
366
- if (!binPath) return null;
367
- const fullPath = (0, import_node_path.join)(projectRoot, "node_modules", packageName, binPath);
368
- await (0, import_promises.access)(fullPath);
369
- return fullPath;
370
- } catch {
371
- return null;
821
+ outro(message) {
822
+ (0, import_prompts.outro)(message);
372
823
  }
373
- }
374
- function isKoraProject(pkg) {
375
- if (typeof pkg !== "object" || pkg === null) return false;
376
- const record = pkg;
377
- return hasKoraDep(record.dependencies) || hasKoraDep(record.devDependencies);
378
- }
379
- function hasKoraDep(deps) {
380
- if (typeof deps !== "object" || deps === null) return false;
381
- return Object.keys(deps).some((key) => key === "kora" || key.startsWith("@korajs/"));
382
- }
824
+ };
383
825
 
384
- // src/utils/logger.ts
385
- var RESET = "\x1B[0m";
386
- var BOLD = "\x1B[1m";
387
- var DIM = "\x1B[2m";
388
- var GREEN = "\x1B[32m";
389
- var YELLOW = "\x1B[33m";
390
- var RED = "\x1B[31m";
391
- var CYAN = "\x1B[36m";
392
- function createLogger(options) {
393
- const colorDisabled = options?.noColor === true || process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
394
- function color(code, text) {
395
- return colorDisabled ? text : `${code}${text}${RESET}`;
396
- }
397
- return {
398
- info(message) {
399
- console.log(color(CYAN, message));
400
- },
401
- success(message) {
402
- console.log(color(GREEN, ` \u2713 ${message}`));
403
- },
404
- warn(message) {
405
- console.warn(color(YELLOW, ` \u26A0 ${message}`));
406
- },
407
- error(message) {
408
- console.error(color(RED, ` \u2717 ${message}`));
409
- },
410
- step(message) {
411
- console.log(color(DIM, ` ${message}`));
412
- },
413
- blank() {
414
- console.log();
415
- },
416
- banner() {
417
- console.log();
418
- console.log(
419
- color(BOLD + CYAN, " Kora.js") + color(DIM, " \u2014 Offline-first application framework")
420
- );
421
- console.log();
422
- }
423
- };
424
- }
826
+ // src/types.ts
827
+ var PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
828
+ var TEMPLATES = [
829
+ "react-tailwind-sync",
830
+ "react-tailwind",
831
+ "react-sync",
832
+ "react-basic",
833
+ "tauri-react"
834
+ ];
425
835
 
426
836
  // src/utils/package-manager.ts
427
- var import_node_child_process = require("child_process");
837
+ var import_node_child_process3 = require("child_process");
428
838
  function detectPackageManager() {
429
839
  const userAgent = process.env.npm_config_user_agent;
430
840
  if (!userAgent) return "npm";
@@ -442,8 +852,8 @@ function getRunDevCommand(pm) {
442
852
  }
443
853
 
444
854
  // src/commands/create/environment-detection.ts
445
- var import_promises2 = require("fs/promises");
446
- var import_node_path2 = require("path");
855
+ var import_promises4 = require("fs/promises");
856
+ var import_node_path6 = require("path");
447
857
  function detectEditorFromEnvironment(env = process.env) {
448
858
  const termProgram = String(env.TERM_PROGRAM ?? "").toLowerCase();
449
859
  const editorValue = `${String(env.EDITOR ?? "")} ${String(env.VISUAL ?? "")}`.toLowerCase();
@@ -469,18 +879,18 @@ async function detectGitContext(startDir) {
469
879
  };
470
880
  }
471
881
  async function detectMonorepoContext(startDir) {
472
- let current = (0, import_node_path2.resolve)(startDir);
882
+ let current = (0, import_node_path6.resolve)(startDir);
473
883
  for (; ; ) {
474
- if (await fileExists((0, import_node_path2.join)(current, "pnpm-workspace.yaml"))) {
884
+ if (await fileExists((0, import_node_path6.join)(current, "pnpm-workspace.yaml"))) {
475
885
  return { isMonorepo: true, root: current, kind: "pnpm-workspace" };
476
886
  }
477
- if (await fileExists((0, import_node_path2.join)(current, "turbo.json"))) {
887
+ if (await fileExists((0, import_node_path6.join)(current, "turbo.json"))) {
478
888
  return { isMonorepo: true, root: current, kind: "turborepo" };
479
889
  }
480
- const packageJsonPath = (0, import_node_path2.join)(current, "package.json");
890
+ const packageJsonPath = (0, import_node_path6.join)(current, "package.json");
481
891
  if (await fileExists(packageJsonPath)) {
482
892
  try {
483
- const packageJsonRaw = await (0, import_promises2.readFile)(packageJsonPath, "utf-8");
893
+ const packageJsonRaw = await (0, import_promises4.readFile)(packageJsonPath, "utf-8");
484
894
  const parsed = JSON.parse(packageJsonRaw);
485
895
  if (Array.isArray(parsed.workspaces) || isNpmWorkspaceObject(parsed.workspaces)) {
486
896
  return { isMonorepo: true, root: current, kind: "npm-workspaces" };
@@ -488,7 +898,7 @@ async function detectMonorepoContext(startDir) {
488
898
  } catch {
489
899
  }
490
900
  }
491
- const parent = (0, import_node_path2.dirname)(current);
901
+ const parent = (0, import_node_path6.dirname)(current);
492
902
  if (parent === current) {
493
903
  return { isMonorepo: false, root: null, kind: "none" };
494
904
  }
@@ -500,9 +910,9 @@ async function applyEditorWorkspacePreset(params) {
500
910
  if (editor !== "vscode" && editor !== "cursor" && editor !== "windsurf") {
501
911
  return { applied: false, filePath: null };
502
912
  }
503
- const vscodeDir = (0, import_node_path2.join)(targetDir, ".vscode");
504
- const extensionsPath = (0, import_node_path2.join)(vscodeDir, "extensions.json");
505
- await (0, import_promises2.mkdir)(vscodeDir, { recursive: true });
913
+ const vscodeDir = (0, import_node_path6.join)(targetDir, ".vscode");
914
+ const extensionsPath = (0, import_node_path6.join)(vscodeDir, "extensions.json");
915
+ await (0, import_promises4.mkdir)(vscodeDir, { recursive: true });
506
916
  const recommendations = ["korajs.kora-devtools"];
507
917
  const existing = await readJsonObject(extensionsPath);
508
918
  const existingRecommendations = Array.isArray(existing?.recommendations) ? existing.recommendations.filter((item) => typeof item === "string") : [];
@@ -510,19 +920,19 @@ async function applyEditorWorkspacePreset(params) {
510
920
  const next = {
511
921
  recommendations: mergedRecommendations
512
922
  };
513
- await (0, import_promises2.writeFile)(extensionsPath, `${JSON.stringify(next, null, 2)}
923
+ await (0, import_promises4.writeFile)(extensionsPath, `${JSON.stringify(next, null, 2)}
514
924
  `, "utf-8");
515
925
  return { applied: true, filePath: extensionsPath };
516
926
  }
517
927
  function resolveMonorepoTargetDirectory(monorepoRoot, projectName) {
518
- return (0, import_node_path2.join)(monorepoRoot, "packages", projectName);
928
+ return (0, import_node_path6.join)(monorepoRoot, "packages", projectName);
519
929
  }
520
930
  function dedupeStrings(values) {
521
931
  return Array.from(new Set(values));
522
932
  }
523
933
  async function readJsonObject(path) {
524
934
  try {
525
- const content = await (0, import_promises2.readFile)(path, "utf-8");
935
+ const content = await (0, import_promises4.readFile)(path, "utf-8");
526
936
  const parsed = JSON.parse(content);
527
937
  if (typeof parsed === "object" && parsed !== null) {
528
938
  return parsed;
@@ -539,22 +949,22 @@ function isNpmWorkspaceObject(value) {
539
949
  }
540
950
  async function fileExists(path) {
541
951
  try {
542
- await (0, import_promises2.access)(path);
952
+ await (0, import_promises4.access)(path);
543
953
  return true;
544
954
  } catch {
545
955
  return false;
546
956
  }
547
957
  }
548
958
  async function findNearestAncestorWithEntry(startDir, entryName) {
549
- let current = (0, import_node_path2.resolve)(startDir);
959
+ let current = (0, import_node_path6.resolve)(startDir);
550
960
  for (; ; ) {
551
- const candidate = (0, import_node_path2.join)(current, entryName);
961
+ const candidate = (0, import_node_path6.join)(current, entryName);
552
962
  try {
553
- await (0, import_promises2.stat)(candidate);
963
+ await (0, import_promises4.stat)(candidate);
554
964
  return current;
555
965
  } catch {
556
966
  }
557
- const parent = (0, import_node_path2.dirname)(current);
967
+ const parent = (0, import_node_path6.dirname)(current);
558
968
  if (parent === current) {
559
969
  return null;
560
970
  }
@@ -625,10 +1035,49 @@ async function resolveCreatePreferencesFlow(params) {
625
1035
  if (isTauri) {
626
1036
  effective.framework = "react";
627
1037
  effective.tailwind = false;
628
- effective.sync = true;
629
- effective.db = "sqlite";
630
- effective.dbProvider = "none";
631
1038
  effective.auth = "none";
1039
+ if (flags.sync !== void 0) {
1040
+ effective.sync = flags.sync;
1041
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
1042
+ effective.sync = await prompts.confirm("Enable multi-device sync?", true);
1043
+ }
1044
+ if (flags.db !== void 0) {
1045
+ if (!isDatabaseValue(flags.db)) {
1046
+ throw new Error(
1047
+ `Invalid --db value "${flags.db}". Expected one of: none, sqlite, postgres.`
1048
+ );
1049
+ }
1050
+ effective.db = flags.db;
1051
+ } else if (!effective.sync) {
1052
+ effective.db = "none";
1053
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
1054
+ effective.db = await prompts.select("Sync server database:", [
1055
+ { label: "SQLite (zero-config; local, LAN, small teams)", value: "sqlite" },
1056
+ { label: "PostgreSQL (production-scale)", value: "postgres" }
1057
+ ]);
1058
+ }
1059
+ if (effective.db === "none") {
1060
+ effective.sync = false;
1061
+ }
1062
+ if (effective.db !== "postgres") {
1063
+ effective.dbProvider = "none";
1064
+ } else if (flags.dbProvider !== void 0) {
1065
+ if (!isDatabaseProviderValue(flags.dbProvider)) {
1066
+ throw new Error(
1067
+ `Invalid --db-provider value "${flags.dbProvider}". Expected one of: none, local, supabase, neon, railway, vercel-postgres, custom.`
1068
+ );
1069
+ }
1070
+ effective.dbProvider = flags.dbProvider;
1071
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
1072
+ effective.dbProvider = await prompts.select("Database provider:", [
1073
+ { label: "Local Postgres", value: "local" },
1074
+ { label: "Supabase", value: "supabase" },
1075
+ { label: "Neon", value: "neon" },
1076
+ { label: "Railway", value: "railway" },
1077
+ { label: "Vercel Postgres", value: "vercel-postgres" },
1078
+ { label: "Custom connection string", value: "custom" }
1079
+ ]);
1080
+ }
632
1081
  } else {
633
1082
  if (flags.framework !== void 0) {
634
1083
  if (!isFrameworkValue(flags.framework)) {
@@ -671,7 +1120,9 @@ async function resolveCreatePreferencesFlow(params) {
671
1120
  }
672
1121
  if (flags.db !== void 0) {
673
1122
  if (!isDatabaseValue(flags.db)) {
674
- throw new Error(`Invalid --db value "${flags.db}". Expected one of: none, sqlite, postgres.`);
1123
+ throw new Error(
1124
+ `Invalid --db value "${flags.db}". Expected one of: none, sqlite, postgres.`
1125
+ );
675
1126
  }
676
1127
  effective.db = flags.db;
677
1128
  } else if (!effective.sync) {
@@ -775,8 +1226,8 @@ function validateProjectName(name) {
775
1226
  }
776
1227
 
777
1228
  // src/commands/create/sync-provider-preset.ts
778
- var import_promises3 = require("fs/promises");
779
- var import_node_path3 = require("path");
1229
+ var import_promises5 = require("fs/promises");
1230
+ var import_node_path7 = require("path");
780
1231
  async function applySyncProviderPreset(options) {
781
1232
  if (!isSyncTemplate(options.template)) {
782
1233
  return;
@@ -786,64 +1237,32 @@ async function applySyncProviderPreset(options) {
786
1237
  }
787
1238
  const providerName = getProviderDisplayName(options.dbProvider);
788
1239
  const providerConnectionString = getProviderConnectionStringExample(options.dbProvider);
789
- const serverPath = (0, import_node_path3.join)(options.targetDir, "server.ts");
790
- const envPath = (0, import_node_path3.join)(options.targetDir, ".env.example");
791
- const readmePath = (0, import_node_path3.join)(options.targetDir, "README.md");
792
- const serverTemplate = [
793
- "import { createPostgresServerStore, createProductionServer } from '@korajs/server'",
794
- "",
795
- `// PostgreSQL provider preset: ${providerName}`,
796
- "// Ensure DATABASE_URL is set in your environment.",
797
- "",
798
- "async function start(): Promise<void> {",
799
- " const connectionString = process.env.DATABASE_URL || ''",
800
- " if (connectionString.length === 0) {",
801
- " throw new Error('DATABASE_URL is required for PostgreSQL sync server store.')",
802
- " }",
803
- "",
804
- " const store = await createPostgresServerStore({ connectionString })",
805
- " const server = createProductionServer({",
806
- " store,",
807
- " port: Number(process.env.PORT) || 3001,",
808
- " staticDir: './dist',",
809
- " syncPath: '/kora-sync',",
810
- " })",
811
- "",
812
- " const url = await server.start()",
813
- " console.log(`Kora app running at ${url}`)",
814
- "}",
815
- "",
816
- "void start()",
817
- ""
818
- ].join("\n");
819
- const envTemplate = [
820
- "# Kora Sync Server",
821
- "# WebSocket URL for the sync server (used by the client)",
822
- "VITE_SYNC_URL=ws://localhost:3001",
823
- "",
824
- "# Sync server port",
825
- "PORT=3001",
1240
+ const envPath = (0, import_node_path7.join)(options.targetDir, ".env.example");
1241
+ const readmePath = (0, import_node_path7.join)(options.targetDir, "README.md");
1242
+ const existingReadme = await (0, import_promises5.readFile)(readmePath, "utf-8");
1243
+ const existingEnv = await (0, import_promises5.readFile)(envPath, "utf-8");
1244
+ const trimmedReadme = existingReadme.trimEnd();
1245
+ const trimmedEnv = existingEnv.trimEnd();
1246
+ const envSuffix = [
826
1247
  "",
827
- `# PostgreSQL connection string (${providerName})`,
828
- `# Example: ${providerConnectionString}`,
829
- "DATABASE_URL=",
830
- ""
1248
+ `# PostgreSQL provider preset: ${providerName}`,
1249
+ `# Example: ${providerConnectionString}`
831
1250
  ].join("\n");
832
- const existingReadme = await (0, import_promises3.readFile)(readmePath, "utf-8");
833
- const trimmedReadme = existingReadme.trimEnd();
834
1251
  const readmeSuffix = [
835
1252
  "",
836
1253
  "## PostgreSQL Provider Preset",
837
1254
  "",
838
1255
  `Selected DB provider: ${options.dbProvider}`,
839
1256
  "",
840
- "This scaffold uses `createPostgresServerStore` in `server.ts` and reads `DATABASE_URL` from the environment. See `.env.example` for provider-specific examples.",
1257
+ "This scaffold keeps one sync server entrypoint. When `DATABASE_URL` is set, `server.ts` uses PostgreSQL. When it is empty, the same server uses SQLite at `KORA_SERVER_DB`.",
1258
+ "",
1259
+ "The generated server uses `createPostgresServerStore` automatically when `DATABASE_URL` is present.",
841
1260
  ""
842
1261
  ].join("\n");
843
1262
  const readmeTemplate = `${trimmedReadme}${readmeSuffix}`;
844
- await (0, import_promises3.writeFile)(serverPath, serverTemplate, "utf-8");
845
- await (0, import_promises3.writeFile)(envPath, envTemplate, "utf-8");
846
- await (0, import_promises3.writeFile)(readmePath, readmeTemplate, "utf-8");
1263
+ await (0, import_promises5.writeFile)(envPath, `${trimmedEnv}${envSuffix}
1264
+ `, "utf-8");
1265
+ await (0, import_promises5.writeFile)(readmePath, readmeTemplate, "utf-8");
847
1266
  }
848
1267
  function isSyncTemplate(template) {
849
1268
  return template === "react-sync" || template === "react-tailwind-sync" || template === "tauri-react";
@@ -887,9 +1306,9 @@ function getProviderConnectionStringExample(provider) {
887
1306
 
888
1307
  // src/templates/composer.ts
889
1308
  var import_node_fs = require("fs");
890
- var import_promises4 = require("fs/promises");
891
- var import_node_path4 = require("path");
892
- var import_node_url = require("url");
1309
+ var import_promises6 = require("fs/promises");
1310
+ var import_node_path8 = require("path");
1311
+ var import_node_url3 = require("url");
893
1312
  var import_meta = {};
894
1313
  function substituteVariables(template, context) {
895
1314
  return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
@@ -898,16 +1317,16 @@ function substituteVariables(template, context) {
898
1317
  });
899
1318
  }
900
1319
  function getTemplatePath(templateName) {
901
- let dir = (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
1320
+ let dir = (0, import_node_path8.dirname)((0, import_node_url3.fileURLToPath)(import_meta.url));
902
1321
  for (let i = 0; i < 7; i++) {
903
- const candidate = (0, import_node_path4.resolve)(dir, "templates", templateName);
1322
+ const candidate = (0, import_node_path8.resolve)(dir, "templates", templateName);
904
1323
  if ((0, import_node_fs.existsSync)(candidate)) {
905
1324
  return candidate;
906
1325
  }
907
- dir = (0, import_node_path4.dirname)(dir);
1326
+ dir = (0, import_node_path8.dirname)(dir);
908
1327
  }
909
- const currentDir = (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
910
- return (0, import_node_path4.resolve)(currentDir, "..", "..", "..", "templates", templateName);
1328
+ const currentDir = (0, import_node_path8.dirname)((0, import_node_url3.fileURLToPath)(import_meta.url));
1329
+ return (0, import_node_path8.resolve)(currentDir, "..", "..", "..", "templates", templateName);
911
1330
  }
912
1331
  function createCompatibilityLayerPlan(templateName) {
913
1332
  const baseLayer = { category: "base", name: "base", sourceTemplate: "react-basic" };
@@ -992,22 +1411,22 @@ async function composeTemplateLayers(plan, targetDir, context) {
992
1411
  }
993
1412
  }
994
1413
  async function copyDirectory(src, dest, vars) {
995
- await (0, import_promises4.mkdir)(dest, { recursive: true });
996
- const entries = await (0, import_promises4.readdir)(src);
1414
+ await (0, import_promises6.mkdir)(dest, { recursive: true });
1415
+ const entries = await (0, import_promises6.readdir)(src);
997
1416
  for (const entry of entries) {
998
- const srcPath = (0, import_node_path4.join)(src, entry);
999
- const srcStat = await (0, import_promises4.stat)(srcPath);
1417
+ const srcPath = (0, import_node_path8.join)(src, entry);
1418
+ const srcStat = await (0, import_promises6.stat)(srcPath);
1000
1419
  if (srcStat.isDirectory()) {
1001
- await copyDirectory(srcPath, (0, import_node_path4.join)(dest, entry), vars);
1420
+ await copyDirectory(srcPath, (0, import_node_path8.join)(dest, entry), vars);
1002
1421
  continue;
1003
1422
  }
1004
1423
  if (entry.endsWith(".hbs")) {
1005
- const content = await (0, import_promises4.readFile)(srcPath, "utf-8");
1424
+ const content = await (0, import_promises6.readFile)(srcPath, "utf-8");
1006
1425
  const outputName = entry.slice(0, -4);
1007
- await (0, import_promises4.writeFile)((0, import_node_path4.join)(dest, outputName), substituteVariables(content, vars), "utf-8");
1426
+ await (0, import_promises6.writeFile)((0, import_node_path8.join)(dest, outputName), substituteVariables(content, vars), "utf-8");
1008
1427
  continue;
1009
1428
  }
1010
- await (0, import_promises4.copyFile)(srcPath, (0, import_node_path4.join)(dest, entry));
1429
+ await (0, import_promises6.copyFile)(srcPath, (0, import_node_path8.join)(dest, entry));
1011
1430
  }
1012
1431
  }
1013
1432
 
@@ -1019,7 +1438,7 @@ async function scaffoldTemplate(templateName, targetDir, context) {
1019
1438
 
1020
1439
  // src/commands/create/create-command.ts
1021
1440
  var import_meta2 = {};
1022
- var createCommand = (0, import_citty.defineCommand)({
1441
+ var createCommand = (0, import_citty3.defineCommand)({
1023
1442
  meta: {
1024
1443
  name: "create",
1025
1444
  description: "Create a new Kora application"
@@ -1158,7 +1577,7 @@ var createCommand = (0, import_citty.defineCommand)({
1158
1577
  const editorDetection = detectEditorFromEnvironment();
1159
1578
  const gitContext = await detectGitContext(process.cwd());
1160
1579
  const monorepoContext = await detectMonorepoContext(process.cwd());
1161
- let targetDir = (0, import_node_path5.resolve)(process.cwd(), projectName);
1580
+ let targetDir = (0, import_node_path9.resolve)(process.cwd(), projectName);
1162
1581
  if (!useDefaults && monorepoContext.isMonorepo && monorepoContext.root !== null) {
1163
1582
  const useWorkspaceTarget = await prompts.confirm(
1164
1583
  `Detected ${formatMonorepoKind(monorepoContext.kind)} at ${monorepoContext.root}. Create app under packages/${projectName}?`,
@@ -1228,7 +1647,7 @@ var createCommand = (0, import_citty.defineCommand)({
1228
1647
  if (!args["skip-install"]) {
1229
1648
  logger.step("Installing dependencies...");
1230
1649
  try {
1231
- (0, import_node_child_process2.execSync)(getInstallCommand(pm), { cwd: targetDir, stdio: "inherit" });
1650
+ (0, import_node_child_process4.execSync)(getInstallCommand(pm), { cwd: targetDir, stdio: "inherit" });
1232
1651
  logger.success("Dependencies installed");
1233
1652
  } catch {
1234
1653
  logger.warn("Failed to install dependencies. Run install manually.");
@@ -1328,9 +1747,9 @@ function relativeToTarget(targetDir, filePath) {
1328
1747
  }
1329
1748
  function resolveKoraVersion() {
1330
1749
  try {
1331
- let dir = (0, import_node_path5.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url));
1750
+ let dir = (0, import_node_path9.dirname)((0, import_node_url4.fileURLToPath)(import_meta2.url));
1332
1751
  for (let i = 0; i < 5; i++) {
1333
- const pkgPath = (0, import_node_path5.resolve)(dir, "package.json");
1752
+ const pkgPath = (0, import_node_path9.resolve)(dir, "package.json");
1334
1753
  if ((0, import_node_fs2.existsSync)(pkgPath)) {
1335
1754
  const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
1336
1755
  if (pkg.name === "@korajs/cli") {
@@ -1339,7 +1758,7 @@ function resolveKoraVersion() {
1339
1758
  return `^${parts[0]}.${parts[1]}.0`;
1340
1759
  }
1341
1760
  }
1342
- dir = (0, import_node_path5.dirname)(dir);
1761
+ dir = (0, import_node_path9.dirname)(dir);
1343
1762
  }
1344
1763
  return "latest";
1345
1764
  } catch {
@@ -1348,24 +1767,33 @@ function resolveKoraVersion() {
1348
1767
  }
1349
1768
 
1350
1769
  // src/commands/deploy/deploy-command.ts
1351
- var import_node_path16 = require("path");
1352
- var import_citty2 = require("citty");
1770
+ var import_node_fs4 = require("fs");
1771
+ var import_node_path20 = require("path");
1772
+ var import_citty4 = require("citty");
1353
1773
 
1354
1774
  // src/commands/deploy/adapters/adapter.ts
1355
- var DEPLOY_PLATFORMS = ["fly", "railway", "aws-ecs", "aws-lightsail", "render", "docker", "kora-cloud"];
1775
+ var DEPLOY_PLATFORMS = [
1776
+ "fly",
1777
+ "railway",
1778
+ "aws-ecs",
1779
+ "aws-lightsail",
1780
+ "render",
1781
+ "docker",
1782
+ "kora-cloud"
1783
+ ];
1356
1784
  function isDeployPlatform(value) {
1357
1785
  return DEPLOY_PLATFORMS.includes(value);
1358
1786
  }
1359
1787
 
1360
1788
  // src/commands/deploy/adapters/aws-ecs-adapter.ts
1361
- var import_node_child_process4 = require("child_process");
1362
- var import_node_path8 = require("path");
1789
+ var import_node_child_process6 = require("child_process");
1790
+ var import_node_path12 = require("path");
1363
1791
 
1364
1792
  // src/commands/deploy/builder/client-builder.ts
1365
- var import_node_child_process3 = require("child_process");
1366
- var import_promises5 = require("fs/promises");
1793
+ var import_node_child_process5 = require("child_process");
1367
1794
  var import_node_fs3 = require("fs");
1368
- var import_node_path6 = require("path");
1795
+ var import_promises7 = require("fs/promises");
1796
+ var import_node_path10 = require("path");
1369
1797
  async function buildClient(options) {
1370
1798
  const viteEntryPoint = await resolveProjectBinaryEntryPoint(options.projectRoot, "vite", "vite");
1371
1799
  if (!viteEntryPoint) {
@@ -1386,15 +1814,15 @@ async function buildClient(options) {
1386
1814
  return { outDir: options.outDir };
1387
1815
  }
1388
1816
  async function patchSqliteWasmAssets(projectRoot, outDir) {
1389
- const assetsDir = (0, import_node_path6.join)(outDir, "assets");
1817
+ const assetsDir = (0, import_node_path10.join)(outDir, "assets");
1390
1818
  if (!(0, import_node_fs3.existsSync)(assetsDir)) return;
1391
- const files = await (0, import_promises5.readdir)(assetsDir);
1819
+ const files = await (0, import_promises7.readdir)(assetsDir);
1392
1820
  const hashedWasm = files.find((f) => /^sqlite3-.+\.wasm$/.test(f));
1393
1821
  if (hashedWasm && !files.includes("sqlite3.wasm")) {
1394
- await (0, import_promises5.copyFile)((0, import_node_path6.join)(assetsDir, hashedWasm), (0, import_node_path6.join)(assetsDir, "sqlite3.wasm"));
1822
+ await (0, import_promises7.copyFile)((0, import_node_path10.join)(assetsDir, hashedWasm), (0, import_node_path10.join)(assetsDir, "sqlite3.wasm"));
1395
1823
  }
1396
1824
  if (!files.includes("sqlite3-opfs-async-proxy.js")) {
1397
- const proxyFile = (0, import_node_path6.resolve)(
1825
+ const proxyFile = (0, import_node_path10.resolve)(
1398
1826
  projectRoot,
1399
1827
  "node_modules",
1400
1828
  "@sqlite.org",
@@ -1404,13 +1832,13 @@ async function patchSqliteWasmAssets(projectRoot, outDir) {
1404
1832
  "sqlite3-opfs-async-proxy.js"
1405
1833
  );
1406
1834
  if ((0, import_node_fs3.existsSync)(proxyFile)) {
1407
- await (0, import_promises5.copyFile)(proxyFile, (0, import_node_path6.join)(assetsDir, "sqlite3-opfs-async-proxy.js"));
1835
+ await (0, import_promises7.copyFile)(proxyFile, (0, import_node_path10.join)(assetsDir, "sqlite3-opfs-async-proxy.js"));
1408
1836
  }
1409
1837
  }
1410
1838
  }
1411
1839
  async function runProcess(command, args, cwd) {
1412
- await new Promise((resolve9, reject) => {
1413
- const child = (0, import_node_child_process3.spawn)(command, args, {
1840
+ await new Promise((resolve11, reject) => {
1841
+ const child = (0, import_node_child_process5.spawn)(command, args, {
1414
1842
  cwd,
1415
1843
  stdio: "inherit",
1416
1844
  env: process.env
@@ -1420,7 +1848,7 @@ async function runProcess(command, args, cwd) {
1420
1848
  });
1421
1849
  child.on("exit", (code) => {
1422
1850
  if (code === 0) {
1423
- resolve9();
1851
+ resolve11();
1424
1852
  return;
1425
1853
  }
1426
1854
  reject(new Error(`Client build failed with exit code ${String(code ?? "unknown")}.`));
@@ -1429,9 +1857,9 @@ async function runProcess(command, args, cwd) {
1429
1857
  }
1430
1858
 
1431
1859
  // src/commands/deploy/builder/server-bundler.ts
1432
- var import_promises6 = require("fs/promises");
1433
- var import_promises7 = require("fs/promises");
1434
- var import_node_path7 = require("path");
1860
+ var import_promises8 = require("fs/promises");
1861
+ var import_promises9 = require("fs/promises");
1862
+ var import_node_path11 = require("path");
1435
1863
  var import_esbuild = require("esbuild");
1436
1864
  var DEFAULT_ENTRY_CANDIDATES = [
1437
1865
  "server.ts",
@@ -1447,8 +1875,8 @@ async function bundleServer(options) {
1447
1875
  `Could not find a server entry file in ${options.projectRoot}. Looked for: ${candidates.join(", ")}`
1448
1876
  );
1449
1877
  }
1450
- await (0, import_promises6.mkdir)(options.deployDirectory, { recursive: true });
1451
- const outputFilePath = (0, import_node_path7.join)(options.deployDirectory, "server-bundled.js");
1878
+ await (0, import_promises8.mkdir)(options.deployDirectory, { recursive: true });
1879
+ const outputFilePath = (0, import_node_path11.join)(options.deployDirectory, "server-bundled.js");
1452
1880
  await (0, import_esbuild.build)({
1453
1881
  entryPoints: [entryFilePath],
1454
1882
  outfile: outputFilePath,
@@ -1470,9 +1898,9 @@ async function bundleServer(options) {
1470
1898
  }
1471
1899
  async function resolveServerEntry(projectRoot, candidates) {
1472
1900
  for (const candidate of candidates) {
1473
- const fullPath = (0, import_node_path7.join)(projectRoot, candidate);
1901
+ const fullPath = (0, import_node_path11.join)(projectRoot, candidate);
1474
1902
  try {
1475
- await (0, import_promises7.access)(fullPath);
1903
+ await (0, import_promises9.access)(fullPath);
1476
1904
  return fullPath;
1477
1905
  } catch {
1478
1906
  }
@@ -1522,37 +1950,46 @@ var AwsEcsAdapter = class {
1522
1950
  };
1523
1951
  const region = config.region ?? "us-east-1";
1524
1952
  const repoName = `kora/${config.appName}`;
1525
- const createRepo = await this.runner.run("aws", [
1526
- "ecr",
1527
- "create-repository",
1528
- "--repository-name",
1529
- repoName,
1530
- "--region",
1531
- region,
1532
- "--image-scanning-configuration",
1533
- "scanOnPush=true"
1534
- ], config.projectRoot);
1953
+ const createRepo = await this.runner.run(
1954
+ "aws",
1955
+ [
1956
+ "ecr",
1957
+ "create-repository",
1958
+ "--repository-name",
1959
+ repoName,
1960
+ "--region",
1961
+ region,
1962
+ "--image-scanning-configuration",
1963
+ "scanOnPush=true"
1964
+ ],
1965
+ config.projectRoot
1966
+ );
1535
1967
  if (createRepo.exitCode !== 0 && !createRepo.stderr.includes("RepositoryAlreadyExistsException")) {
1536
1968
  throw new Error(`Failed to create ECR repository: ${createRepo.stderr}`);
1537
1969
  }
1538
- const identity = await this.runner.run("aws", ["sts", "get-caller-identity", "--query", "Account", "--output", "text"], config.projectRoot);
1970
+ const identity = await this.runner.run(
1971
+ "aws",
1972
+ ["sts", "get-caller-identity", "--query", "Account", "--output", "text"],
1973
+ config.projectRoot
1974
+ );
1539
1975
  const accountId = identity.stdout.trim();
1540
- await this.runner.run("aws", [
1541
- "ecs",
1542
- "create-cluster",
1543
- "--cluster-name",
1544
- config.appName,
1545
- "--region",
1546
- region
1547
- ], config.projectRoot);
1548
- await this.runner.run("aws", [
1549
- "logs",
1550
- "create-log-group",
1551
- "--log-group-name",
1552
- `/ecs/${config.appName}`,
1553
- "--region",
1554
- region
1555
- ], config.projectRoot);
1976
+ await this.runner.run(
1977
+ "aws",
1978
+ ["ecs", "create-cluster", "--cluster-name", config.appName, "--region", region],
1979
+ config.projectRoot
1980
+ );
1981
+ await this.runner.run(
1982
+ "aws",
1983
+ [
1984
+ "logs",
1985
+ "create-log-group",
1986
+ "--log-group-name",
1987
+ `/ecs/${config.appName}`,
1988
+ "--region",
1989
+ region
1990
+ ],
1991
+ config.projectRoot
1992
+ );
1556
1993
  return {
1557
1994
  applicationId: `${accountId}.dkr.ecr.${region}.amazonaws.com/${repoName}`,
1558
1995
  databaseId: null,
@@ -1565,19 +2002,19 @@ var AwsEcsAdapter = class {
1565
2002
  appName: config.appName,
1566
2003
  region: config.region
1567
2004
  };
1568
- const deployDirectory = (0, import_node_path8.join)(config.projectRoot, ".kora", "deploy");
2005
+ const deployDirectory = (0, import_node_path12.join)(config.projectRoot, ".kora", "deploy");
1569
2006
  await bundleServer({
1570
2007
  projectRoot: config.projectRoot,
1571
2008
  deployDirectory
1572
2009
  });
1573
- const client = await buildClient({
2010
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
1574
2011
  projectRoot: config.projectRoot,
1575
- outDir: (0, import_node_path8.join)(deployDirectory, "dist"),
2012
+ outDir: (0, import_node_path12.join)(deployDirectory, "dist"),
1576
2013
  mode: "production"
1577
2014
  });
1578
2015
  return {
1579
- clientDirectory: client.outDir,
1580
- serverBundlePath: (0, import_node_path8.join)(deployDirectory, "server-bundled.js"),
2016
+ clientDirectory: client?.outDir ?? null,
2017
+ serverBundlePath: (0, import_node_path12.join)(deployDirectory, "server-bundled.js"),
1581
2018
  deployDirectory
1582
2019
  };
1583
2020
  }
@@ -1585,50 +2022,42 @@ var AwsEcsAdapter = class {
1585
2022
  const context = this.requireContext();
1586
2023
  const region = context.region ?? "us-east-1";
1587
2024
  const repoName = `kora/${context.appName}`;
1588
- const loginPassword = await this.runner.run("aws", [
1589
- "ecr",
1590
- "get-login-password",
1591
- "--region",
1592
- region
1593
- ], context.projectRoot);
2025
+ const loginPassword = await this.runner.run(
2026
+ "aws",
2027
+ ["ecr", "get-login-password", "--region", region],
2028
+ context.projectRoot
2029
+ );
1594
2030
  if (loginPassword.exitCode !== 0) {
1595
2031
  throw new Error(`ECR login failed: ${loginPassword.stderr}`);
1596
2032
  }
1597
- const identity = await this.runner.run("aws", [
1598
- "sts",
1599
- "get-caller-identity",
1600
- "--query",
1601
- "Account",
1602
- "--output",
1603
- "text"
1604
- ], context.projectRoot);
2033
+ const identity = await this.runner.run(
2034
+ "aws",
2035
+ ["sts", "get-caller-identity", "--query", "Account", "--output", "text"],
2036
+ context.projectRoot
2037
+ );
1605
2038
  const accountId = identity.stdout.trim();
1606
2039
  const ecrUri = `${accountId}.dkr.ecr.${region}.amazonaws.com`;
1607
2040
  const imageUri = `${ecrUri}/${repoName}:latest`;
1608
- const dockerLogin = await this.runner.run("docker", [
1609
- "login",
1610
- "--username",
1611
- "AWS",
1612
- "--password-stdin",
1613
- ecrUri
1614
- ], artifacts.deployDirectory);
2041
+ const dockerLogin = await this.runner.run(
2042
+ "docker",
2043
+ ["login", "--username", "AWS", "--password-stdin", ecrUri],
2044
+ artifacts.deployDirectory
2045
+ );
1615
2046
  this.logger.step("Building Docker image...");
1616
- const dockerBuild = await this.runner.run("docker", [
1617
- "build",
1618
- "--platform",
1619
- "linux/amd64",
1620
- "-t",
1621
- imageUri,
1622
- "."
1623
- ], artifacts.deployDirectory);
2047
+ const dockerBuild = await this.runner.run(
2048
+ "docker",
2049
+ ["build", "--platform", "linux/amd64", "-t", imageUri, "."],
2050
+ artifacts.deployDirectory
2051
+ );
1624
2052
  if (dockerBuild.exitCode !== 0) {
1625
2053
  throw new Error(`Docker build failed: ${dockerBuild.stderr}`);
1626
2054
  }
1627
2055
  this.logger.step("Pushing image to ECR...");
1628
- const dockerPush = await this.runner.run("docker", [
1629
- "push",
1630
- imageUri
1631
- ], artifacts.deployDirectory);
2056
+ const dockerPush = await this.runner.run(
2057
+ "docker",
2058
+ ["push", imageUri],
2059
+ artifacts.deployDirectory
2060
+ );
1632
2061
  if (dockerPush.exitCode !== 0) {
1633
2062
  throw new Error(`Docker push failed: ${dockerPush.stderr}`);
1634
2063
  }
@@ -1639,52 +2068,55 @@ var AwsEcsAdapter = class {
1639
2068
  cpu: "256",
1640
2069
  memory: "512",
1641
2070
  executionRoleArn: `arn:aws:iam::${accountId}:role/ecsTaskExecutionRole`,
1642
- containerDefinitions: [{
1643
- name: context.appName,
1644
- image: imageUri,
1645
- essential: true,
1646
- portMappings: [{ containerPort: 3001, protocol: "tcp" }],
1647
- logConfiguration: {
1648
- logDriver: "awslogs",
1649
- options: {
1650
- "awslogs-group": `/ecs/${context.appName}`,
1651
- "awslogs-region": region,
1652
- "awslogs-stream-prefix": "ecs"
2071
+ containerDefinitions: [
2072
+ {
2073
+ name: context.appName,
2074
+ image: imageUri,
2075
+ essential: true,
2076
+ portMappings: [{ containerPort: 3001, protocol: "tcp" }],
2077
+ logConfiguration: {
2078
+ logDriver: "awslogs",
2079
+ options: {
2080
+ "awslogs-group": `/ecs/${context.appName}`,
2081
+ "awslogs-region": region,
2082
+ "awslogs-stream-prefix": "ecs"
2083
+ }
2084
+ },
2085
+ healthCheck: {
2086
+ command: ["CMD-SHELL", "curl -f http://localhost:3001/health || exit 1"],
2087
+ interval: 30,
2088
+ timeout: 5,
2089
+ retries: 3,
2090
+ startPeriod: 60
1653
2091
  }
1654
- },
1655
- healthCheck: {
1656
- command: ["CMD-SHELL", "curl -f http://localhost:3001/health || exit 1"],
1657
- interval: 30,
1658
- timeout: 5,
1659
- retries: 3,
1660
- startPeriod: 60
1661
2092
  }
1662
- }]
2093
+ ]
1663
2094
  });
1664
- const registerTask = await this.runner.run("aws", [
1665
- "ecs",
1666
- "register-task-definition",
1667
- "--cli-input-json",
1668
- taskDef,
1669
- "--region",
1670
- region
1671
- ], context.projectRoot);
2095
+ const registerTask = await this.runner.run(
2096
+ "aws",
2097
+ ["ecs", "register-task-definition", "--cli-input-json", taskDef, "--region", region],
2098
+ context.projectRoot
2099
+ );
1672
2100
  if (registerTask.exitCode !== 0) {
1673
2101
  throw new Error(`Task definition registration failed: ${registerTask.stderr}`);
1674
2102
  }
1675
- const updateService = await this.runner.run("aws", [
1676
- "ecs",
1677
- "update-service",
1678
- "--cluster",
1679
- context.appName,
1680
- "--service",
1681
- context.appName,
1682
- "--task-definition",
1683
- context.appName,
1684
- "--force-new-deployment",
1685
- "--region",
1686
- region
1687
- ], context.projectRoot);
2103
+ const updateService = await this.runner.run(
2104
+ "aws",
2105
+ [
2106
+ "ecs",
2107
+ "update-service",
2108
+ "--cluster",
2109
+ context.appName,
2110
+ "--service",
2111
+ context.appName,
2112
+ "--task-definition",
2113
+ context.appName,
2114
+ "--force-new-deployment",
2115
+ "--region",
2116
+ region
2117
+ ],
2118
+ context.projectRoot
2119
+ );
1688
2120
  const deploymentId = (/* @__PURE__ */ new Date()).toISOString();
1689
2121
  if (updateService.exitCode !== 0) {
1690
2122
  this.logger.step("Service not found, creating new service...");
@@ -1709,19 +2141,23 @@ var AwsEcsAdapter = class {
1709
2141
  async rollback(deploymentId) {
1710
2142
  const context = this.requireContext();
1711
2143
  const region = context.region ?? "us-east-1";
1712
- const result = await this.runner.run("aws", [
1713
- "ecs",
1714
- "update-service",
1715
- "--cluster",
1716
- context.appName,
1717
- "--service",
1718
- context.appName,
1719
- "--task-definition",
1720
- `${context.appName}:${deploymentId}`,
1721
- "--force-new-deployment",
1722
- "--region",
1723
- region
1724
- ], context.projectRoot);
2144
+ const result = await this.runner.run(
2145
+ "aws",
2146
+ [
2147
+ "ecs",
2148
+ "update-service",
2149
+ "--cluster",
2150
+ context.appName,
2151
+ "--service",
2152
+ context.appName,
2153
+ "--task-definition",
2154
+ `${context.appName}:${deploymentId}`,
2155
+ "--force-new-deployment",
2156
+ "--region",
2157
+ region
2158
+ ],
2159
+ context.projectRoot
2160
+ );
1725
2161
  if (result.exitCode !== 0) {
1726
2162
  throw new Error(`ECS rollback failed: ${result.stderr}`);
1727
2163
  }
@@ -1764,16 +2200,20 @@ var AwsEcsAdapter = class {
1764
2200
  async status() {
1765
2201
  const context = this.requireContext();
1766
2202
  const region = context.region ?? "us-east-1";
1767
- const result = await this.runner.run("aws", [
1768
- "ecs",
1769
- "describe-services",
1770
- "--cluster",
1771
- context.appName,
1772
- "--services",
1773
- context.appName,
1774
- "--region",
1775
- region
1776
- ], context.projectRoot);
2203
+ const result = await this.runner.run(
2204
+ "aws",
2205
+ [
2206
+ "ecs",
2207
+ "describe-services",
2208
+ "--cluster",
2209
+ context.appName,
2210
+ "--services",
2211
+ context.appName,
2212
+ "--region",
2213
+ region
2214
+ ],
2215
+ context.projectRoot
2216
+ );
1777
2217
  if (result.exitCode !== 0) {
1778
2218
  return { state: "failed", message: result.stderr };
1779
2219
  }
@@ -1806,8 +2246,8 @@ var AwsEcsAdapter = class {
1806
2246
  };
1807
2247
  var NodeAwsCommandRunner = class {
1808
2248
  async run(command, args, cwd) {
1809
- return new Promise((resolve9) => {
1810
- const child = (0, import_node_child_process4.spawn)(command, args, {
2249
+ return new Promise((resolve11) => {
2250
+ const child = (0, import_node_child_process6.spawn)(command, args, {
1811
2251
  cwd,
1812
2252
  env: process.env,
1813
2253
  stdio: ["ignore", "pipe", "pipe"]
@@ -1821,11 +2261,11 @@ var NodeAwsCommandRunner = class {
1821
2261
  stderr += chunk.toString("utf-8");
1822
2262
  });
1823
2263
  child.on("error", (error) => {
1824
- resolve9({ exitCode: 1, stdout, stderr: `${stderr}
2264
+ resolve11({ exitCode: 1, stdout, stderr: `${stderr}
1825
2265
  ${error.message}` });
1826
2266
  });
1827
2267
  child.on("exit", (code) => {
1828
- resolve9({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
2268
+ resolve11({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
1829
2269
  });
1830
2270
  });
1831
2271
  }
@@ -1839,8 +2279,8 @@ function inferLogLevel(line) {
1839
2279
  }
1840
2280
 
1841
2281
  // src/commands/deploy/adapters/aws-lightsail-adapter.ts
1842
- var import_node_child_process5 = require("child_process");
1843
- var import_node_path9 = require("path");
2282
+ var import_node_child_process7 = require("child_process");
2283
+ var import_node_path13 = require("path");
1844
2284
  var AwsLightsailAdapter = class {
1845
2285
  name = "aws-lightsail";
1846
2286
  logger = createLogger();
@@ -1882,18 +2322,22 @@ var AwsLightsailAdapter = class {
1882
2322
  };
1883
2323
  const region = config.region ?? "us-east-1";
1884
2324
  const serviceName = sanitizeLightsailName(config.appName);
1885
- const createService = await this.runner.run("aws", [
1886
- "lightsail",
1887
- "create-container-service",
1888
- "--service-name",
1889
- serviceName,
1890
- "--power",
1891
- "nano",
1892
- "--scale",
1893
- "1",
1894
- "--region",
1895
- region
1896
- ], config.projectRoot);
2325
+ const createService = await this.runner.run(
2326
+ "aws",
2327
+ [
2328
+ "lightsail",
2329
+ "create-container-service",
2330
+ "--service-name",
2331
+ serviceName,
2332
+ "--power",
2333
+ "nano",
2334
+ "--scale",
2335
+ "1",
2336
+ "--region",
2337
+ region
2338
+ ],
2339
+ config.projectRoot
2340
+ );
1897
2341
  if (createService.exitCode !== 0 && !createService.stderr.includes("already exists")) {
1898
2342
  throw new Error(`Failed to create Lightsail container service: ${createService.stderr}`);
1899
2343
  }
@@ -1909,19 +2353,19 @@ var AwsLightsailAdapter = class {
1909
2353
  appName: config.appName,
1910
2354
  region: config.region
1911
2355
  };
1912
- const deployDirectory = (0, import_node_path9.join)(config.projectRoot, ".kora", "deploy");
2356
+ const deployDirectory = (0, import_node_path13.join)(config.projectRoot, ".kora", "deploy");
1913
2357
  await bundleServer({
1914
2358
  projectRoot: config.projectRoot,
1915
2359
  deployDirectory
1916
2360
  });
1917
- const client = await buildClient({
2361
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
1918
2362
  projectRoot: config.projectRoot,
1919
- outDir: (0, import_node_path9.join)(deployDirectory, "dist"),
2363
+ outDir: (0, import_node_path13.join)(deployDirectory, "dist"),
1920
2364
  mode: "production"
1921
2365
  });
1922
2366
  return {
1923
- clientDirectory: client.outDir,
1924
- serverBundlePath: (0, import_node_path9.join)(deployDirectory, "server-bundled.js"),
2367
+ clientDirectory: client?.outDir ?? null,
2368
+ serverBundlePath: (0, import_node_path13.join)(deployDirectory, "server-bundled.js"),
1925
2369
  deployDirectory
1926
2370
  };
1927
2371
  }
@@ -1931,30 +2375,31 @@ var AwsLightsailAdapter = class {
1931
2375
  const serviceName = sanitizeLightsailName(context.appName);
1932
2376
  const imageTag = `${serviceName}:latest`;
1933
2377
  this.logger.step("Building Docker image...");
1934
- const dockerBuild = await this.runner.run("docker", [
1935
- "build",
1936
- "--platform",
1937
- "linux/amd64",
1938
- "-t",
1939
- imageTag,
1940
- "."
1941
- ], artifacts.deployDirectory);
2378
+ const dockerBuild = await this.runner.run(
2379
+ "docker",
2380
+ ["build", "--platform", "linux/amd64", "-t", imageTag, "."],
2381
+ artifacts.deployDirectory
2382
+ );
1942
2383
  if (dockerBuild.exitCode !== 0) {
1943
2384
  throw new Error(`Docker build failed: ${dockerBuild.stderr}`);
1944
2385
  }
1945
2386
  this.logger.step("Pushing image to Lightsail...");
1946
- const pushImage = await this.runner.run("aws", [
1947
- "lightsail",
1948
- "push-container-image",
1949
- "--service-name",
1950
- serviceName,
1951
- "--label",
1952
- "latest",
1953
- "--image",
1954
- imageTag,
1955
- "--region",
1956
- region
1957
- ], artifacts.deployDirectory);
2387
+ const pushImage = await this.runner.run(
2388
+ "aws",
2389
+ [
2390
+ "lightsail",
2391
+ "push-container-image",
2392
+ "--service-name",
2393
+ serviceName,
2394
+ "--label",
2395
+ "latest",
2396
+ "--image",
2397
+ imageTag,
2398
+ "--region",
2399
+ region
2400
+ ],
2401
+ artifacts.deployDirectory
2402
+ );
1958
2403
  if (pushImage.exitCode !== 0) {
1959
2404
  throw new Error(`Lightsail image push failed: ${pushImage.stderr}`);
1960
2405
  }
@@ -1985,29 +2430,30 @@ var AwsLightsailAdapter = class {
1985
2430
  unhealthyThreshold: 3
1986
2431
  }
1987
2432
  });
1988
- const createDeploy = await this.runner.run("aws", [
1989
- "lightsail",
1990
- "create-container-service-deployment",
1991
- "--service-name",
1992
- serviceName,
1993
- "--containers",
1994
- containers,
1995
- "--public-endpoint",
1996
- publicEndpoint,
1997
- "--region",
1998
- region
1999
- ], context.projectRoot);
2433
+ const createDeploy = await this.runner.run(
2434
+ "aws",
2435
+ [
2436
+ "lightsail",
2437
+ "create-container-service-deployment",
2438
+ "--service-name",
2439
+ serviceName,
2440
+ "--containers",
2441
+ containers,
2442
+ "--public-endpoint",
2443
+ publicEndpoint,
2444
+ "--region",
2445
+ region
2446
+ ],
2447
+ context.projectRoot
2448
+ );
2000
2449
  if (createDeploy.exitCode !== 0) {
2001
2450
  throw new Error(`Lightsail deployment failed: ${createDeploy.stderr}`);
2002
2451
  }
2003
- const serviceInfo = await this.runner.run("aws", [
2004
- "lightsail",
2005
- "get-container-services",
2006
- "--service-name",
2007
- serviceName,
2008
- "--region",
2009
- region
2010
- ], context.projectRoot);
2452
+ const serviceInfo = await this.runner.run(
2453
+ "aws",
2454
+ ["lightsail", "get-container-services", "--service-name", serviceName, "--region", region],
2455
+ context.projectRoot
2456
+ );
2011
2457
  const rawUrl = parseLightsailUrl(serviceInfo.stdout) ?? `https://${serviceName}.${region}.cs.amazonlightsail.com`;
2012
2458
  const serviceUrl = rawUrl.replace(/\/+$/, "");
2013
2459
  const deploymentId = (/* @__PURE__ */ new Date()).toISOString();
@@ -2021,14 +2467,18 @@ var AwsLightsailAdapter = class {
2021
2467
  const context = this.requireContext();
2022
2468
  const region = context.region ?? "us-east-1";
2023
2469
  const serviceName = sanitizeLightsailName(context.appName);
2024
- const deployments = await this.runner.run("aws", [
2025
- "lightsail",
2026
- "get-container-service-deployments",
2027
- "--service-name",
2028
- serviceName,
2029
- "--region",
2030
- region
2031
- ], context.projectRoot);
2470
+ const deployments = await this.runner.run(
2471
+ "aws",
2472
+ [
2473
+ "lightsail",
2474
+ "get-container-service-deployments",
2475
+ "--service-name",
2476
+ serviceName,
2477
+ "--region",
2478
+ region
2479
+ ],
2480
+ context.projectRoot
2481
+ );
2032
2482
  if (deployments.exitCode !== 0) {
2033
2483
  throw new Error(`Lightsail rollback failed: ${deployments.stderr}`);
2034
2484
  }
@@ -2036,18 +2486,22 @@ var AwsLightsailAdapter = class {
2036
2486
  if (!previousDeployment) {
2037
2487
  throw new Error("No previous deployment found to rollback to.");
2038
2488
  }
2039
- const redeploy = await this.runner.run("aws", [
2040
- "lightsail",
2041
- "create-container-service-deployment",
2042
- "--service-name",
2043
- serviceName,
2044
- "--containers",
2045
- JSON.stringify(previousDeployment.containers),
2046
- "--public-endpoint",
2047
- JSON.stringify(previousDeployment.publicEndpoint),
2048
- "--region",
2049
- region
2050
- ], context.projectRoot);
2489
+ const redeploy = await this.runner.run(
2490
+ "aws",
2491
+ [
2492
+ "lightsail",
2493
+ "create-container-service-deployment",
2494
+ "--service-name",
2495
+ serviceName,
2496
+ "--containers",
2497
+ JSON.stringify(previousDeployment.containers),
2498
+ "--public-endpoint",
2499
+ JSON.stringify(previousDeployment.publicEndpoint),
2500
+ "--region",
2501
+ region
2502
+ ],
2503
+ context.projectRoot
2504
+ );
2051
2505
  if (redeploy.exitCode !== 0) {
2052
2506
  throw new Error(`Lightsail rollback deployment failed: ${redeploy.stderr}`);
2053
2507
  }
@@ -2094,14 +2548,11 @@ var AwsLightsailAdapter = class {
2094
2548
  const context = this.requireContext();
2095
2549
  const region = context.region ?? "us-east-1";
2096
2550
  const serviceName = sanitizeLightsailName(context.appName);
2097
- const result = await this.runner.run("aws", [
2098
- "lightsail",
2099
- "get-container-services",
2100
- "--service-name",
2101
- serviceName,
2102
- "--region",
2103
- region
2104
- ], context.projectRoot);
2551
+ const result = await this.runner.run(
2552
+ "aws",
2553
+ ["lightsail", "get-container-services", "--service-name", serviceName, "--region", region],
2554
+ context.projectRoot
2555
+ );
2105
2556
  if (result.exitCode !== 0) {
2106
2557
  return { state: "failed", message: result.stderr };
2107
2558
  }
@@ -2142,8 +2593,8 @@ var AwsLightsailAdapter = class {
2142
2593
  };
2143
2594
  var NodeAwsLightsailCommandRunner = class {
2144
2595
  async run(command, args, cwd) {
2145
- return new Promise((resolve9) => {
2146
- const child = (0, import_node_child_process5.spawn)(command, args, {
2596
+ return new Promise((resolve11) => {
2597
+ const child = (0, import_node_child_process7.spawn)(command, args, {
2147
2598
  cwd,
2148
2599
  env: process.env,
2149
2600
  stdio: ["ignore", "pipe", "pipe"]
@@ -2157,21 +2608,16 @@ var NodeAwsLightsailCommandRunner = class {
2157
2608
  stderr += chunk.toString("utf-8");
2158
2609
  });
2159
2610
  child.on("error", (error) => {
2160
- resolve9({ exitCode: 1, stdout, stderr: `${stderr}
2611
+ resolve11({ exitCode: 1, stdout, stderr: `${stderr}
2161
2612
  ${error.message}` });
2162
2613
  });
2163
2614
  child.on("exit", (code) => {
2164
- resolve9({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
2615
+ resolve11({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
2165
2616
  });
2166
2617
  });
2167
2618
  }
2168
2619
  };
2169
- var PASSTHROUGH_ENV_VARS = [
2170
- "DATABASE_URL",
2171
- "AUTH_SECRET",
2172
- "PUBLIC_URL",
2173
- "NODE_ENV"
2174
- ];
2620
+ var PASSTHROUGH_ENV_VARS = ["DATABASE_URL", "AUTH_SECRET", "PUBLIC_URL", "NODE_ENV"];
2175
2621
  function sanitizeLightsailName(name) {
2176
2622
  return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "").slice(0, 255) || "kora-app";
2177
2623
  }
@@ -2218,12 +2664,12 @@ function inferLogLevel2(line) {
2218
2664
  }
2219
2665
 
2220
2666
  // src/commands/deploy/adapters/fly-adapter.ts
2221
- var import_node_child_process6 = require("child_process");
2222
- var import_node_path11 = require("path");
2667
+ var import_node_child_process8 = require("child_process");
2668
+ var import_node_path15 = require("path");
2223
2669
 
2224
2670
  // src/commands/deploy/artifacts/fly-toml-generator.ts
2225
- var import_promises8 = require("fs/promises");
2226
- var import_node_path10 = require("path");
2671
+ var import_promises10 = require("fs/promises");
2672
+ var import_node_path14 = require("path");
2227
2673
  var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
2228
2674
  function generateFlyToml(options) {
2229
2675
  const internalPort = options.internalPort ?? 3e3;
@@ -2248,14 +2694,14 @@ function generateFlyToml(options) {
2248
2694
  ' interval = "30s"',
2249
2695
  ' method = "GET"',
2250
2696
  ' timeout = "5s"',
2251
- ' path = "/"',
2697
+ ' path = "/health"',
2252
2698
  ""
2253
2699
  ].join("\n");
2254
2700
  }
2255
2701
  async function writeFlyTomlArtifact(deployDirectory, options) {
2256
- await (0, import_promises8.mkdir)(deployDirectory, { recursive: true });
2257
- const filePath = (0, import_node_path10.join)(deployDirectory, "fly.toml");
2258
- await (0, import_promises8.writeFile)(filePath, generateFlyToml(options), "utf-8");
2702
+ await (0, import_promises10.mkdir)(deployDirectory, { recursive: true });
2703
+ const filePath = (0, import_node_path14.join)(deployDirectory, "fly.toml");
2704
+ await (0, import_promises10.writeFile)(filePath, generateFlyToml(options), "utf-8");
2259
2705
  return filePath;
2260
2706
  }
2261
2707
 
@@ -2349,7 +2795,7 @@ var FlyAdapter = class {
2349
2795
  appName: config.appName,
2350
2796
  region: config.region
2351
2797
  };
2352
- const deployDirectory = (0, import_node_path11.join)(config.projectRoot, ".kora", "deploy");
2798
+ const deployDirectory = (0, import_node_path15.join)(config.projectRoot, ".kora", "deploy");
2353
2799
  await writeFlyTomlArtifact(deployDirectory, {
2354
2800
  appName: config.appName,
2355
2801
  region: config.region ?? "iad"
@@ -2358,14 +2804,14 @@ var FlyAdapter = class {
2358
2804
  projectRoot: config.projectRoot,
2359
2805
  deployDirectory
2360
2806
  });
2361
- const client = await buildClient({
2807
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
2362
2808
  projectRoot: config.projectRoot,
2363
- outDir: (0, import_node_path11.join)(deployDirectory, "dist"),
2809
+ outDir: (0, import_node_path15.join)(deployDirectory, "dist"),
2364
2810
  mode: "production"
2365
2811
  });
2366
2812
  return {
2367
- clientDirectory: client.outDir,
2368
- serverBundlePath: (0, import_node_path11.join)(deployDirectory, "server-bundled.js"),
2813
+ clientDirectory: client?.outDir ?? null,
2814
+ serverBundlePath: (0, import_node_path15.join)(deployDirectory, "server-bundled.js"),
2369
2815
  deployDirectory
2370
2816
  };
2371
2817
  }
@@ -2491,8 +2937,8 @@ var FlyAdapter = class {
2491
2937
  };
2492
2938
  var NodeFlyCommandRunner = class {
2493
2939
  async run(command, args, cwd) {
2494
- return await new Promise((resolve9) => {
2495
- const child = (0, import_node_child_process6.spawn)(command, args, {
2940
+ return await new Promise((resolve11) => {
2941
+ const child = (0, import_node_child_process8.spawn)(command, args, {
2496
2942
  cwd,
2497
2943
  env: process.env,
2498
2944
  stdio: ["ignore", "pipe", "pipe"]
@@ -2506,7 +2952,7 @@ var NodeFlyCommandRunner = class {
2506
2952
  stderr += chunk.toString("utf-8");
2507
2953
  });
2508
2954
  child.on("error", (error) => {
2509
- resolve9({
2955
+ resolve11({
2510
2956
  exitCode: 1,
2511
2957
  stdout,
2512
2958
  stderr: `${stderr}
@@ -2514,7 +2960,7 @@ ${error.message}`
2514
2960
  });
2515
2961
  });
2516
2962
  child.on("exit", (code) => {
2517
- resolve9({
2963
+ resolve11({
2518
2964
  exitCode: code ?? 1,
2519
2965
  stdout: stdout.trim(),
2520
2966
  stderr: stderr.trim()
@@ -2586,12 +3032,12 @@ ${stdout}`.toLowerCase();
2586
3032
  }
2587
3033
 
2588
3034
  // src/commands/deploy/adapters/railway-adapter.ts
2589
- var import_node_child_process7 = require("child_process");
2590
- var import_node_path13 = require("path");
3035
+ var import_node_child_process9 = require("child_process");
3036
+ var import_node_path17 = require("path");
2591
3037
 
2592
3038
  // src/commands/deploy/artifacts/railway-json-generator.ts
2593
- var import_promises9 = require("fs/promises");
2594
- var import_node_path12 = require("path");
3039
+ var import_promises11 = require("fs/promises");
3040
+ var import_node_path16 = require("path");
2595
3041
  var GENERATED_HEADER2 = "Generated by kora deploy - do not edit manually.";
2596
3042
  function generateRailwayJson(options) {
2597
3043
  const file = {
@@ -2616,9 +3062,9 @@ function generateRailwayJson(options) {
2616
3062
  `;
2617
3063
  }
2618
3064
  async function writeRailwayJsonArtifact(deployDirectory, options) {
2619
- await (0, import_promises9.mkdir)(deployDirectory, { recursive: true });
2620
- const filePath = (0, import_node_path12.join)(deployDirectory, "railway.json");
2621
- await (0, import_promises9.writeFile)(filePath, generateRailwayJson(options), "utf-8");
3065
+ await (0, import_promises11.mkdir)(deployDirectory, { recursive: true });
3066
+ const filePath = (0, import_node_path16.join)(deployDirectory, "railway.json");
3067
+ await (0, import_promises11.writeFile)(filePath, generateRailwayJson(options), "utf-8");
2622
3068
  return filePath;
2623
3069
  }
2624
3070
 
@@ -2707,7 +3153,7 @@ var RailwayAdapter = class {
2707
3153
  appName: config.appName,
2708
3154
  region: config.region
2709
3155
  };
2710
- const deployDirectory = (0, import_node_path13.join)(config.projectRoot, ".kora", "deploy");
3156
+ const deployDirectory = (0, import_node_path17.join)(config.projectRoot, ".kora", "deploy");
2711
3157
  await writeRailwayJsonArtifact(deployDirectory, {
2712
3158
  appName: config.appName,
2713
3159
  environment: config.environment
@@ -2716,14 +3162,14 @@ var RailwayAdapter = class {
2716
3162
  projectRoot: config.projectRoot,
2717
3163
  deployDirectory
2718
3164
  });
2719
- const client = await buildClient({
3165
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
2720
3166
  projectRoot: config.projectRoot,
2721
- outDir: (0, import_node_path13.join)(deployDirectory, "dist"),
3167
+ outDir: (0, import_node_path17.join)(deployDirectory, "dist"),
2722
3168
  mode: "production"
2723
3169
  });
2724
3170
  return {
2725
- clientDirectory: client.outDir,
2726
- serverBundlePath: (0, import_node_path13.join)(deployDirectory, "server-bundled.js"),
3171
+ clientDirectory: client?.outDir ?? null,
3172
+ serverBundlePath: (0, import_node_path17.join)(deployDirectory, "server-bundled.js"),
2727
3173
  deployDirectory
2728
3174
  };
2729
3175
  }
@@ -2827,8 +3273,8 @@ var RailwayAdapter = class {
2827
3273
  };
2828
3274
  var NodeRailwayCommandRunner = class {
2829
3275
  async run(command, args, cwd) {
2830
- return await new Promise((resolve9) => {
2831
- const child = (0, import_node_child_process7.spawn)(command, args, {
3276
+ return await new Promise((resolve11) => {
3277
+ const child = (0, import_node_child_process9.spawn)(command, args, {
2832
3278
  cwd,
2833
3279
  env: process.env,
2834
3280
  stdio: ["ignore", "pipe", "pipe"]
@@ -2842,7 +3288,7 @@ var NodeRailwayCommandRunner = class {
2842
3288
  stderr += chunk.toString("utf-8");
2843
3289
  });
2844
3290
  child.on("error", (error) => {
2845
- resolve9({
3291
+ resolve11({
2846
3292
  exitCode: 1,
2847
3293
  stdout,
2848
3294
  stderr: `${stderr}
@@ -2850,7 +3296,7 @@ ${error.message}`
2850
3296
  });
2851
3297
  });
2852
3298
  child.on("exit", (code) => {
2853
- resolve9({
3299
+ resolve11({
2854
3300
  exitCode: code ?? 1,
2855
3301
  stdout: stdout.trim(),
2856
3302
  stderr: stderr.trim()
@@ -3008,13 +3454,13 @@ function createDeployAdapter(platform) {
3008
3454
  }
3009
3455
 
3010
3456
  // src/commands/deploy/artifacts/dockerfile-generator.ts
3011
- var import_promises10 = require("fs/promises");
3012
- var import_node_path14 = require("path");
3457
+ var import_promises12 = require("fs/promises");
3458
+ var import_node_path18 = require("path");
3013
3459
  var GENERATED_HEADER3 = "# Generated by kora deploy \u2014 do not edit manually";
3014
3460
  function generateDockerfile(options = {}) {
3015
3461
  const nodeVersion = options.nodeVersion ?? "20-alpine";
3016
3462
  const port = options.port ?? 3e3;
3017
- const clientDirectory = options.clientDirectory ?? "dist";
3463
+ const clientDirectory = options.clientDirectory === void 0 ? "dist" : options.clientDirectory;
3018
3464
  const serverBundleFile = options.serverBundleFile ?? "server-bundled.js";
3019
3465
  const hasNativeDeps = options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0;
3020
3466
  const lines = [
@@ -3034,7 +3480,9 @@ function generateDockerfile(options = {}) {
3034
3480
  lines.push("RUN npm install --omit=dev");
3035
3481
  lines.push("");
3036
3482
  }
3037
- lines.push(`COPY ${clientDirectory} ./dist`);
3483
+ if (clientDirectory !== null) {
3484
+ lines.push(`COPY ${clientDirectory} ./dist`);
3485
+ }
3038
3486
  lines.push(`COPY ${serverBundleFile} ./server-bundled.js`);
3039
3487
  lines.push("");
3040
3488
  lines.push(`EXPOSE ${String(port)}`);
@@ -3050,7 +3498,8 @@ function generateDeployPackageJson(nativeDependencies) {
3050
3498
  type: "module",
3051
3499
  dependencies: nativeDependencies
3052
3500
  };
3053
- return JSON.stringify(pkg, null, 2) + "\n";
3501
+ return `${JSON.stringify(pkg, null, 2)}
3502
+ `;
3054
3503
  }
3055
3504
  function generateDockerIgnore() {
3056
3505
  return [
@@ -3072,37 +3521,37 @@ function generateDockerIgnore() {
3072
3521
  ].join("\n");
3073
3522
  }
3074
3523
  async function writeDockerfileArtifact(deployDirectory, options = {}) {
3075
- await (0, import_promises10.mkdir)(deployDirectory, { recursive: true });
3076
- const dockerfilePath = (0, import_node_path14.join)(deployDirectory, "Dockerfile");
3077
- await (0, import_promises10.writeFile)(dockerfilePath, generateDockerfile(options), "utf-8");
3524
+ await (0, import_promises12.mkdir)(deployDirectory, { recursive: true });
3525
+ const dockerfilePath = (0, import_node_path18.join)(deployDirectory, "Dockerfile");
3526
+ await (0, import_promises12.writeFile)(dockerfilePath, generateDockerfile(options), "utf-8");
3078
3527
  if (options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0) {
3079
- const pkgJsonPath = (0, import_node_path14.join)(deployDirectory, "package.json");
3080
- await (0, import_promises10.writeFile)(pkgJsonPath, generateDeployPackageJson(options.nativeDependencies), "utf-8");
3528
+ const pkgJsonPath = (0, import_node_path18.join)(deployDirectory, "package.json");
3529
+ await (0, import_promises12.writeFile)(pkgJsonPath, generateDeployPackageJson(options.nativeDependencies), "utf-8");
3081
3530
  }
3082
3531
  return dockerfilePath;
3083
3532
  }
3084
3533
  async function writeDockerIgnoreArtifact(deployDirectory) {
3085
- await (0, import_promises10.mkdir)(deployDirectory, { recursive: true });
3086
- const dockerIgnorePath = (0, import_node_path14.join)(deployDirectory, ".dockerignore");
3087
- await (0, import_promises10.writeFile)(dockerIgnorePath, generateDockerIgnore(), "utf-8");
3534
+ await (0, import_promises12.mkdir)(deployDirectory, { recursive: true });
3535
+ const dockerIgnorePath = (0, import_node_path18.join)(deployDirectory, ".dockerignore");
3536
+ await (0, import_promises12.writeFile)(dockerIgnorePath, generateDockerIgnore(), "utf-8");
3088
3537
  return dockerIgnorePath;
3089
3538
  }
3090
3539
 
3091
3540
  // src/commands/deploy/state/deploy-state.ts
3092
- var import_promises11 = require("fs/promises");
3093
- var import_node_path15 = require("path");
3094
- var KORA_DEPLOY_DIRECTORY = (0, import_node_path15.join)(".kora", "deploy");
3541
+ var import_promises13 = require("fs/promises");
3542
+ var import_node_path19 = require("path");
3543
+ var KORA_DEPLOY_DIRECTORY = (0, import_node_path19.join)(".kora", "deploy");
3095
3544
  var DEPLOY_STATE_FILENAME = "deploy.json";
3096
3545
  function resolveDeployDirectory(projectRoot) {
3097
- return (0, import_node_path15.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
3546
+ return (0, import_node_path19.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
3098
3547
  }
3099
3548
  function resolveDeployStatePath(projectRoot) {
3100
- return (0, import_node_path15.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
3549
+ return (0, import_node_path19.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
3101
3550
  }
3102
3551
  async function readDeployState(projectRoot) {
3103
3552
  const statePath = resolveDeployStatePath(projectRoot);
3104
3553
  try {
3105
- const source = await (0, import_promises11.readFile)(statePath, "utf-8");
3554
+ const source = await (0, import_promises13.readFile)(statePath, "utf-8");
3106
3555
  const parsed = JSON.parse(source);
3107
3556
  return parseDeployState(parsed);
3108
3557
  } catch (error) {
@@ -3149,13 +3598,13 @@ async function updateDeployState(projectRoot, patch, now = /* @__PURE__ */ new D
3149
3598
  return nextState;
3150
3599
  }
3151
3600
  async function resetDeployState(projectRoot) {
3152
- await (0, import_promises11.rm)(resolveDeployDirectory(projectRoot), { recursive: true, force: true });
3601
+ await (0, import_promises13.rm)(resolveDeployDirectory(projectRoot), { recursive: true, force: true });
3153
3602
  }
3154
3603
  async function persistDeployState(projectRoot, state) {
3155
3604
  const deployDir = resolveDeployDirectory(projectRoot);
3156
3605
  const statePath = resolveDeployStatePath(projectRoot);
3157
- await (0, import_promises11.mkdir)(deployDir, { recursive: true });
3158
- await (0, import_promises11.writeFile)(statePath, `${JSON.stringify(state, null, 2)}
3606
+ await (0, import_promises13.mkdir)(deployDir, { recursive: true });
3607
+ await (0, import_promises13.writeFile)(statePath, `${JSON.stringify(state, null, 2)}
3159
3608
  `, "utf-8");
3160
3609
  }
3161
3610
  function parseDeployState(value) {
@@ -3205,7 +3654,7 @@ function readOptionalString(value, field) {
3205
3654
  }
3206
3655
 
3207
3656
  // src/commands/deploy/deploy-command.ts
3208
- var deployCommand = (0, import_citty2.defineCommand)({
3657
+ var deployCommand = (0, import_citty4.defineCommand)({
3209
3658
  meta: {
3210
3659
  name: "deploy",
3211
3660
  description: "Deploy a Kora project to your selected platform"
@@ -3240,7 +3689,7 @@ var deployCommand = (0, import_citty2.defineCommand)({
3240
3689
  }
3241
3690
  },
3242
3691
  subCommands: {
3243
- status: (0, import_citty2.defineCommand)({
3692
+ status: (0, import_citty4.defineCommand)({
3244
3693
  meta: {
3245
3694
  name: "status",
3246
3695
  description: "Show current deployment status"
@@ -3271,7 +3720,7 @@ var deployCommand = (0, import_citty2.defineCommand)({
3271
3720
  logger.step(`Sync URL: ${state.syncUrl ?? "n/a"}`);
3272
3721
  }
3273
3722
  }),
3274
- rollback: (0, import_citty2.defineCommand)({
3723
+ rollback: (0, import_citty4.defineCommand)({
3275
3724
  meta: {
3276
3725
  name: "rollback",
3277
3726
  description: "Rollback the current deployment"
@@ -3302,7 +3751,7 @@ var deployCommand = (0, import_citty2.defineCommand)({
3302
3751
  logger.success(`Rolled back ${state.platform} deployment to ${deploymentId}.`);
3303
3752
  }
3304
3753
  }),
3305
- logs: (0, import_citty2.defineCommand)({
3754
+ logs: (0, import_citty4.defineCommand)({
3306
3755
  meta: {
3307
3756
  name: "logs",
3308
3757
  description: "Read deployment logs"
@@ -3358,12 +3807,14 @@ var deployCommand = (0, import_citty2.defineCommand)({
3358
3807
  const region = resolveRegion(args.region, existingState?.region, confirmMode);
3359
3808
  const deployDirectory = resolveDeployDirectory(projectRoot);
3360
3809
  const environment = args.prod === true ? "production" : "preview";
3810
+ const deployTarget = detectDeployTarget(projectRoot);
3361
3811
  const config = {
3362
3812
  projectRoot,
3363
3813
  appName,
3364
3814
  region,
3365
3815
  environment,
3366
- confirm: confirmMode
3816
+ confirm: confirmMode,
3817
+ deployTarget
3367
3818
  };
3368
3819
  const adapter = createDeployAdapter(platform);
3369
3820
  configureAdapterContext(adapter, {
@@ -3375,14 +3826,18 @@ var deployCommand = (0, import_citty2.defineCommand)({
3375
3826
  logger.info(
3376
3827
  `Deploying to ${platform} (${appName}${region ? `, ${region}` : ""}, ${environment})`
3377
3828
  );
3829
+ if (deployTarget === "sync-server") {
3830
+ logger.step("Detected Tauri project. Deploying the sync server only.");
3831
+ }
3378
3832
  if (confirmMode) {
3379
3833
  logger.step("Running in --confirm mode (non-interactive, fail-fast).");
3380
3834
  }
3381
3835
  await writeDockerfileArtifact(deployDirectory, {
3836
+ clientDirectory: deployTarget === "sync-server" ? null : void 0,
3382
3837
  nativeDependencies: {
3383
3838
  "better-sqlite3": "^11.0.0",
3384
3839
  "drizzle-orm": "^0.45.2",
3385
- "postgres": "^3.4.0"
3840
+ postgres: "^3.4.0"
3386
3841
  }
3387
3842
  });
3388
3843
  await writeDockerIgnoreArtifact(deployDirectory);
@@ -3483,7 +3938,7 @@ function resolveAppName(argValue, storedValue, projectRoot, confirm) {
3483
3938
  "Missing app name in --confirm mode. Provide --app or run an interactive deploy first."
3484
3939
  );
3485
3940
  }
3486
- return sanitizeAppName((0, import_node_path16.basename)(projectRoot));
3941
+ return sanitizeAppName((0, import_node_path20.basename)(projectRoot));
3487
3942
  }
3488
3943
  function resolveRegion(argValue, storedValue, confirm) {
3489
3944
  if (typeof argValue === "string" && argValue.length > 0) return argValue;
@@ -3517,109 +3972,21 @@ function configureAdapterContext(adapter, context) {
3517
3972
  adapter.setContext(context);
3518
3973
  }
3519
3974
  }
3975
+ function detectDeployTarget(projectRoot) {
3976
+ return (0, import_node_fs4.existsSync)((0, import_node_path20.join)(projectRoot, "src-tauri")) ? "sync-server" : "full-stack";
3977
+ }
3520
3978
  function hasContextSetter(adapter) {
3521
3979
  return typeof adapter.setContext === "function";
3522
3980
  }
3523
3981
 
3524
3982
  // src/commands/dev/dev-command.ts
3525
- var import_promises13 = require("fs/promises");
3526
- var import_node_path19 = require("path");
3527
- var import_node_path20 = require("path");
3528
- var import_citty3 = require("citty");
3529
-
3530
- // src/commands/dev/kora-config.ts
3531
- var import_node_child_process8 = require("child_process");
3532
- var import_promises12 = require("fs/promises");
3533
- var import_node_path17 = require("path");
3534
- var import_node_url3 = require("url");
3535
- var CONFIG_CANDIDATES = [
3536
- "kora.config.ts",
3537
- "kora.config.mts",
3538
- "kora.config.cts",
3539
- "kora.config.js",
3540
- "kora.config.mjs",
3541
- "kora.config.cjs"
3542
- ];
3543
- async function loadKoraConfig(projectRoot) {
3544
- const configPath = await findKoraConfigFile(projectRoot);
3545
- if (!configPath) return null;
3546
- const ext = (0, import_node_path17.extname)(configPath);
3547
- if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
3548
- const loaded2 = await loadTypeScriptConfig(configPath, projectRoot);
3549
- return toConfigObject(loaded2);
3550
- }
3551
- const loaded = await import((0, import_node_url3.pathToFileURL)(configPath).href);
3552
- return toConfigObject(loaded);
3553
- }
3554
- async function findKoraConfigFile(projectRoot) {
3555
- for (const file of CONFIG_CANDIDATES) {
3556
- const candidate = (0, import_node_path17.join)(projectRoot, file);
3557
- try {
3558
- await (0, import_promises12.access)(candidate);
3559
- return candidate;
3560
- } catch {
3561
- }
3562
- }
3563
- return null;
3564
- }
3565
- async function loadTypeScriptConfig(configPath, projectRoot) {
3566
- if (!await hasTsxInstalled(projectRoot)) {
3567
- throw new Error(
3568
- `Found TypeScript config at ${configPath}, but "tsx" is not installed in this project. Install tsx or use kora.config.js.`
3569
- );
3570
- }
3571
- const script = "const configPath = process.argv[process.argv.length - 1];import('node:url').then(u => import(u.pathToFileURL(configPath).href)).then(mod => { const v = mod.default ?? mod; process.stdout.write(JSON.stringify(v)) }).catch(e => { process.stderr.write(String(e)); process.exit(1) })";
3572
- const output = await runCommand(
3573
- process.execPath,
3574
- ["--import", "tsx", "--eval", script, configPath],
3575
- projectRoot
3576
- );
3577
- try {
3578
- return JSON.parse(output);
3579
- } catch {
3580
- throw new Error(`Failed to parse ${configPath} output as JSON.`);
3581
- }
3582
- }
3583
- async function runCommand(command, args, cwd) {
3584
- return await new Promise((resolve9, reject) => {
3585
- const child = (0, import_node_child_process8.spawn)(command, args, {
3586
- cwd,
3587
- stdio: ["ignore", "pipe", "pipe"],
3588
- env: process.env
3589
- });
3590
- let stdout = "";
3591
- let stderr = "";
3592
- child.stdout?.on("data", (chunk) => {
3593
- stdout += chunk.toString("utf-8");
3594
- });
3595
- child.stderr?.on("data", (chunk) => {
3596
- stderr += chunk.toString("utf-8");
3597
- });
3598
- child.on("error", (error) => {
3599
- reject(error);
3600
- });
3601
- child.on("exit", (code) => {
3602
- if (code === 0) {
3603
- resolve9(stdout.trim());
3604
- return;
3605
- }
3606
- reject(new Error(`Failed to load kora config (exit ${code ?? "unknown"}): ${stderr.trim()}`));
3607
- });
3608
- });
3609
- }
3610
- function toConfigObject(mod) {
3611
- if (typeof mod !== "object" || mod === null) {
3612
- throw new Error("kora config must export an object.");
3613
- }
3614
- const value = mod.default ?? mod;
3615
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
3616
- throw new Error("kora config must export an object.");
3617
- }
3618
- return value;
3619
- }
3983
+ var import_promises14 = require("fs/promises");
3984
+ var import_node_path22 = require("path");
3985
+ var import_node_path23 = require("path");
3986
+ var import_citty5 = require("citty");
3620
3987
 
3621
3988
  // src/commands/dev/process-manager.ts
3622
- var import_node_child_process9 = require("child_process");
3989
+ var import_node_child_process10 = require("child_process");
3623
3990
  var ProcessManager = class {
3624
3991
  processes = /* @__PURE__ */ new Map();
3625
3992
  spawn(config) {
@@ -3628,12 +3995,12 @@ var ProcessManager = class {
3628
3995
  env: { ...process.env, ...config.env },
3629
3996
  stdio: ["ignore", "pipe", "pipe"]
3630
3997
  };
3631
- const child = (0, import_node_child_process9.spawn)(config.command, config.args, options);
3998
+ const child = (0, import_node_child_process10.spawn)(config.command, config.args, options);
3632
3999
  let resolveExit;
3633
4000
  const runningProcess = {
3634
4001
  child,
3635
- exitPromise: new Promise((resolve9) => {
3636
- resolveExit = resolve9;
4002
+ exitPromise: new Promise((resolve11) => {
4003
+ resolveExit = resolve11;
3637
4004
  }),
3638
4005
  stdoutBuffer: "",
3639
4006
  stderrBuffer: ""
@@ -3700,15 +4067,15 @@ var ProcessManager = class {
3700
4067
  }
3701
4068
  };
3702
4069
  function delay(ms) {
3703
- return new Promise((resolve9) => {
3704
- setTimeout(resolve9, ms);
4070
+ return new Promise((resolve11) => {
4071
+ setTimeout(resolve11, ms);
3705
4072
  });
3706
4073
  }
3707
4074
 
3708
4075
  // src/commands/dev/schema-watcher.ts
3709
- var import_node_child_process10 = require("child_process");
3710
- var import_node_fs4 = require("fs");
3711
- var import_node_path18 = require("path");
4076
+ var import_node_child_process11 = require("child_process");
4077
+ var import_node_fs5 = require("fs");
4078
+ var import_node_path21 = require("path");
3712
4079
  var SchemaWatcher = class {
3713
4080
  constructor(config) {
3714
4081
  this.config = config;
@@ -3720,7 +4087,7 @@ var SchemaWatcher = class {
3720
4087
  debounceTimer = null;
3721
4088
  start() {
3722
4089
  if (this.watcher) return;
3723
- this.watcher = (0, import_node_fs4.watch)(this.config.schemaPath, () => {
4090
+ this.watcher = (0, import_node_fs5.watch)(this.config.schemaPath, () => {
3724
4091
  this.scheduleRegeneration();
3725
4092
  });
3726
4093
  this.watcher.on("error", (error) => {
@@ -3736,7 +4103,14 @@ var SchemaWatcher = class {
3736
4103
  this.watcher = null;
3737
4104
  }
3738
4105
  async regenerate() {
3739
- const koraBinJs = (0, import_node_path18.join)(this.config.projectRoot, "node_modules", "@korajs", "cli", "dist", "bin.js");
4106
+ const koraBinJs = (0, import_node_path21.join)(
4107
+ this.config.projectRoot,
4108
+ "node_modules",
4109
+ "@korajs",
4110
+ "cli",
4111
+ "dist",
4112
+ "bin.js"
4113
+ );
3740
4114
  const hasTsx = await hasTsxInstalled(this.config.projectRoot);
3741
4115
  const command = process.execPath;
3742
4116
  const args = hasTsx ? ["--import", "tsx", koraBinJs, "generate", "types", "--schema", this.config.schemaPath] : [koraBinJs, "generate", "types", "--schema", this.config.schemaPath];
@@ -3756,8 +4130,8 @@ var SchemaWatcher = class {
3756
4130
  }
3757
4131
  };
3758
4132
  async function spawnCommand(command, args, cwd) {
3759
- await new Promise((resolve9, reject) => {
3760
- const child = (0, import_node_child_process10.spawn)(command, args, {
4133
+ await new Promise((resolve11, reject) => {
4134
+ const child = (0, import_node_child_process11.spawn)(command, args, {
3761
4135
  cwd,
3762
4136
  stdio: ["ignore", "pipe", "pipe"],
3763
4137
  env: process.env
@@ -3773,7 +4147,7 @@ async function spawnCommand(command, args, cwd) {
3773
4147
  });
3774
4148
  child.on("exit", (code) => {
3775
4149
  if (code === 0) {
3776
- resolve9();
4150
+ resolve11();
3777
4151
  return;
3778
4152
  }
3779
4153
  reject(new Error(`Type generation exited with code ${code ?? "unknown"}.`));
@@ -3795,7 +4169,7 @@ function toError(error) {
3795
4169
  }
3796
4170
 
3797
4171
  // src/commands/dev/dev-command.ts
3798
- var devCommand = (0, import_citty3.defineCommand)({
4172
+ var devCommand = (0, import_citty5.defineCommand)({
3799
4173
  meta: {
3800
4174
  name: "dev",
3801
4175
  description: "Start the Kora development environment"
@@ -3835,7 +4209,7 @@ var devCommand = (0, import_citty3.defineCommand)({
3835
4209
  const watchDebounceMs = typeof config?.dev?.watch === "object" && typeof config.dev.watch.debounceMs === "number" ? config.dev.watch.debounceMs : 300;
3836
4210
  const viteEntryPoint = await resolveProjectBinaryEntryPoint(projectRoot, "vite", "vite");
3837
4211
  if (!viteEntryPoint) {
3838
- throw new DevServerError("vite", (0, import_node_path19.join)(projectRoot, "node_modules", ".bin", "vite"));
4212
+ throw new DevServerError("vite", (0, import_node_path22.join)(projectRoot, "node_modules", ".bin", "vite"));
3839
4213
  }
3840
4214
  const syncServerFile = await findSyncServerFile(projectRoot);
3841
4215
  let managedSyncStore = normalizeManagedSyncStore(config, projectRoot);
@@ -3851,7 +4225,7 @@ var devCommand = (0, import_citty3.defineCommand)({
3851
4225
  }
3852
4226
  if (shouldStartSync && syncServerFile === null && managedSyncStore) {
3853
4227
  const hasServerPackage = await fileExists2(
3854
- (0, import_node_path19.join)(projectRoot, "node_modules", "@korajs", "server", "package.json")
4228
+ (0, import_node_path22.join)(projectRoot, "node_modules", "@korajs", "server", "package.json")
3855
4229
  );
3856
4230
  if (!hasServerPackage) {
3857
4231
  logger.warn(
@@ -3868,11 +4242,13 @@ var devCommand = (0, import_citty3.defineCommand)({
3868
4242
  }
3869
4243
  let configuredSchemaPath = null;
3870
4244
  if (typeof config?.schema === "string") {
3871
- const candidate = (0, import_node_path20.resolve)(projectRoot, config.schema);
4245
+ const candidate = (0, import_node_path23.resolve)(projectRoot, config.schema);
3872
4246
  if (await fileExists2(candidate)) {
3873
4247
  configuredSchemaPath = candidate;
3874
4248
  } else {
3875
- logger.warn(`Configured schema file not found: ${candidate}. Falling back to auto-detection.`);
4249
+ logger.warn(
4250
+ `Configured schema file not found: ${candidate}. Falling back to auto-detection.`
4251
+ );
3876
4252
  }
3877
4253
  }
3878
4254
  const schemaPath = configuredSchemaPath ?? await findSchemaFile(projectRoot);
@@ -3881,8 +4257,8 @@ var devCommand = (0, import_citty3.defineCommand)({
3881
4257
  let schemaWatcher = null;
3882
4258
  let shuttingDown = false;
3883
4259
  let resolveFinished;
3884
- const finished = new Promise((resolve9) => {
3885
- resolveFinished = resolve9;
4260
+ const finished = new Promise((resolve11) => {
4261
+ resolveFinished = resolve11;
3886
4262
  });
3887
4263
  const onManagedProcessExit = () => {
3888
4264
  if (!processManager.hasRunning() && !shuttingDown) {
@@ -3925,11 +4301,15 @@ var devCommand = (0, import_citty3.defineCommand)({
3925
4301
  logger.step(" Schema watcher disabled (schema.ts not found)");
3926
4302
  }
3927
4303
  logger.blank();
4304
+ logger.step(" Kora DevTools overlay: Ctrl+Shift+K (Cmd+Shift+K on macOS) when devtools: true");
3928
4305
  processManager.spawn({
3929
4306
  label: "vite",
3930
4307
  command: process.execPath,
3931
4308
  args: [viteEntryPoint, "--port", String(vitePort)],
3932
4309
  cwd: projectRoot,
4310
+ env: {
4311
+ KORA_DEV: "1"
4312
+ },
3933
4313
  onExit: onManagedProcessExit
3934
4314
  });
3935
4315
  if (shouldStartSync && hasTsx && syncServerFile) {
@@ -3980,10 +4360,10 @@ var devCommand = (0, import_citty3.defineCommand)({
3980
4360
  }
3981
4361
  });
3982
4362
  async function findSyncServerFile(projectRoot) {
3983
- const candidates = [(0, import_node_path19.join)(projectRoot, "server.ts"), (0, import_node_path19.join)(projectRoot, "server.js")];
4363
+ const candidates = [(0, import_node_path22.join)(projectRoot, "server.ts"), (0, import_node_path22.join)(projectRoot, "server.js")];
3984
4364
  for (const candidate of candidates) {
3985
4365
  try {
3986
- await (0, import_promises13.access)(candidate);
4366
+ await (0, import_promises14.access)(candidate);
3987
4367
  return candidate;
3988
4368
  } catch {
3989
4369
  }
@@ -3992,7 +4372,7 @@ async function findSyncServerFile(projectRoot) {
3992
4372
  }
3993
4373
  async function fileExists2(path) {
3994
4374
  try {
3995
- await (0, import_promises13.access)(path);
4375
+ await (0, import_promises14.access)(path);
3996
4376
  return true;
3997
4377
  } catch {
3998
4378
  return false;
@@ -4004,7 +4384,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
4004
4384
  const store = sync.store;
4005
4385
  if (store === void 0) return { type: "memory" };
4006
4386
  if (store === "memory") return { type: "memory" };
4007
- if (store === "sqlite") return { type: "sqlite", filename: (0, import_node_path19.join)(projectRoot, "kora-sync.db") };
4387
+ if (store === "sqlite") return { type: "sqlite", filename: (0, import_node_path22.join)(projectRoot, "kora-sync.db") };
4008
4388
  if (store === "postgres") {
4009
4389
  const connectionString = process.env.DATABASE_URL;
4010
4390
  if (!connectionString) return null;
@@ -4013,7 +4393,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
4013
4393
  if (typeof store === "object" && store !== null) {
4014
4394
  if (store.type === "memory") return { type: "memory" };
4015
4395
  if (store.type === "sqlite") {
4016
- const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0, import_node_path20.resolve)(projectRoot, store.filename) : (0, import_node_path19.join)(projectRoot, "kora-sync.db");
4396
+ const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0, import_node_path23.resolve)(projectRoot, store.filename) : (0, import_node_path22.join)(projectRoot, "kora-sync.db");
4017
4397
  return { type: "sqlite", filename };
4018
4398
  }
4019
4399
  if (store.type === "postgres" && typeof store.connectionString === "string") {
@@ -4026,7 +4406,8 @@ function isPostgresEnvRequested(config) {
4026
4406
  const sync = config?.dev?.sync;
4027
4407
  if (typeof sync !== "object" || sync === null) return false;
4028
4408
  if (sync.store === "postgres") return true;
4029
- if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "postgres") return true;
4409
+ if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "postgres")
4410
+ return true;
4030
4411
  return false;
4031
4412
  }
4032
4413
  var MANAGED_SYNC_BOOTSTRAP_SCRIPT = `
@@ -4050,35 +4431,403 @@ if (storeConfig.type === 'memory') {
4050
4431
  if (typeof storeConfig.connectionString !== 'string' || storeConfig.connectionString.length === 0) {
4051
4432
  throw new Error('Managed postgres sync requires a connectionString');
4052
4433
  }
4053
- store = await createPostgresServerStore({
4054
- connectionString: storeConfig.connectionString,
4055
- });
4056
- } else {
4057
- throw new Error('Unsupported managed sync store type: ' + String(storeConfig.type));
4434
+ store = await createPostgresServerStore({
4435
+ connectionString: storeConfig.connectionString,
4436
+ });
4437
+ } else {
4438
+ throw new Error('Unsupported managed sync store type: ' + String(storeConfig.type));
4439
+ }
4440
+ const server = createKoraServer({ store, port: Number(config.port ?? 3001) });
4441
+ const shutdown = async () => {
4442
+ try {
4443
+ await server.stop();
4444
+ } catch {
4445
+ }
4446
+ process.exit(0);
4447
+ };
4448
+ process.on('SIGINT', () => {
4449
+ void shutdown();
4450
+ });
4451
+ process.on('SIGTERM', () => {
4452
+ void shutdown();
4453
+ });
4454
+ await server.start();
4455
+ process.stdout.write('Managed sync server running on ws://localhost:' + String(config.port ?? 3001) + '\\n');
4456
+ await new Promise(() => {});
4457
+ `;
4458
+
4459
+ // src/commands/doctor/doctor-command.ts
4460
+ var import_citty6 = require("citty");
4461
+
4462
+ // src/commands/doctor/doctor-checks.ts
4463
+ var import_promises15 = require("fs/promises");
4464
+ var import_node_path24 = require("path");
4465
+ var STATUS_ENDPOINT = "/__kora/status";
4466
+ async function runDoctorChecks(options) {
4467
+ const { projectRoot } = options;
4468
+ const results = [];
4469
+ results.push({
4470
+ id: "project-root",
4471
+ title: "Project root",
4472
+ status: "pass",
4473
+ message: projectRoot
4474
+ });
4475
+ const config = await loadKoraConfig(projectRoot);
4476
+ if (!config) {
4477
+ results.push({
4478
+ id: "kora-config",
4479
+ title: "kora.config",
4480
+ status: "warn",
4481
+ message: "No kora.config.* file found.",
4482
+ fix: "Add kora.config.ts with schema path and dev settings (see create-kora-app templates)."
4483
+ });
4484
+ } else {
4485
+ results.push({
4486
+ id: "kora-config",
4487
+ title: "kora.config",
4488
+ status: "pass",
4489
+ message: "Configuration file loaded."
4490
+ });
4491
+ }
4492
+ const schemaPath = await resolveSchemaPath(projectRoot, config?.schema);
4493
+ if (!schemaPath) {
4494
+ results.push({
4495
+ id: "schema",
4496
+ title: "Schema",
4497
+ status: "fail",
4498
+ message: "Could not find schema.ts in src/ or project root.",
4499
+ fix: "Add src/schema.ts or set schema in kora.config.ts."
4500
+ });
4501
+ } else {
4502
+ try {
4503
+ const schema = await loadSchemaDefinition(schemaPath, projectRoot);
4504
+ results.push({
4505
+ id: "schema",
4506
+ title: "Schema",
4507
+ status: "pass",
4508
+ message: `Loaded schema v${schema.version} (${Object.keys(schema.collections).length} collection(s)).`
4509
+ });
4510
+ const workerCheck = await checkWorkerFile(projectRoot);
4511
+ results.push(workerCheck);
4512
+ const depsCheck = await checkDependencies(projectRoot);
4513
+ results.push(depsCheck);
4514
+ results.push(opfsBrowserNote());
4515
+ if (!options.skipNetwork) {
4516
+ const syncUrl = options.syncHttpUrl ?? await resolveSyncHttpUrl(projectRoot, config);
4517
+ const syncChecks = await checkSyncServer(syncUrl, schema);
4518
+ results.push(...syncChecks);
4519
+ } else {
4520
+ results.push({
4521
+ id: "sync-server",
4522
+ title: "Sync server",
4523
+ status: "skip",
4524
+ message: "Network checks skipped."
4525
+ });
4526
+ }
4527
+ } catch (error) {
4528
+ const message = error instanceof Error ? error.message : String(error);
4529
+ results.push({
4530
+ id: "schema",
4531
+ title: "Schema",
4532
+ status: "fail",
4533
+ message: `Failed to load schema: ${message}`,
4534
+ fix: "Ensure the schema exports defineSchema() as default and TypeScript can be evaluated (install tsx)."
4535
+ });
4536
+ }
4537
+ }
4538
+ return results;
4539
+ }
4540
+ async function resolveSchemaPath(projectRoot, configSchema) {
4541
+ if (typeof configSchema === "string") {
4542
+ const resolved = (0, import_node_path24.resolve)(projectRoot, configSchema);
4543
+ try {
4544
+ await (0, import_promises15.access)(resolved);
4545
+ return resolved;
4546
+ } catch {
4547
+ }
4548
+ }
4549
+ return findSchemaFile(projectRoot);
4550
+ }
4551
+ async function checkWorkerFile(projectRoot) {
4552
+ const candidates = [
4553
+ (0, import_node_path24.join)(projectRoot, "src", "kora-worker.ts"),
4554
+ (0, import_node_path24.join)(projectRoot, "src", "kora-worker.js")
4555
+ ];
4556
+ for (const path of candidates) {
4557
+ try {
4558
+ await (0, import_promises15.access)(path);
4559
+ return {
4560
+ id: "worker",
4561
+ title: "SQLite WASM worker",
4562
+ status: "pass",
4563
+ message: `Found ${path.replace(projectRoot, ".")}.`
4564
+ };
4565
+ } catch {
4566
+ }
4567
+ }
4568
+ return {
4569
+ id: "worker",
4570
+ title: "SQLite WASM worker",
4571
+ status: "warn",
4572
+ message: "No src/kora-worker.ts found.",
4573
+ fix: "Add kora-worker.ts and pass store.workerUrl from Vite (?worker&url) in createApp()."
4574
+ };
4575
+ }
4576
+ async function checkDependencies(projectRoot) {
4577
+ try {
4578
+ const raw = await (0, import_promises15.readFile)((0, import_node_path24.join)(projectRoot, "package.json"), "utf-8");
4579
+ const pkg = JSON.parse(raw);
4580
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
4581
+ const hasKora = Boolean(deps.korajs || deps.kora);
4582
+ const hasStore = Boolean(deps["@korajs/store"]);
4583
+ if (hasKora || hasStore) {
4584
+ return {
4585
+ id: "dependencies",
4586
+ title: "Dependencies",
4587
+ status: "pass",
4588
+ message: hasKora ? "korajs is listed in package.json." : "@korajs/store is listed in package.json."
4589
+ };
4590
+ }
4591
+ return {
4592
+ id: "dependencies",
4593
+ title: "Dependencies",
4594
+ status: "fail",
4595
+ message: "No korajs or @korajs/store dependency found.",
4596
+ fix: "Run pnpm add korajs @korajs/react (or your package manager equivalent)."
4597
+ };
4598
+ } catch {
4599
+ return {
4600
+ id: "dependencies",
4601
+ title: "Dependencies",
4602
+ status: "fail",
4603
+ message: "Could not read package.json.",
4604
+ fix: "Run this command from a Kora app directory with a valid package.json."
4605
+ };
4606
+ }
4607
+ }
4608
+ function opfsBrowserNote() {
4609
+ return {
4610
+ id: "opfs",
4611
+ title: "OPFS persistence",
4612
+ status: "skip",
4613
+ message: "OPFS availability is checked in the browser at runtime. Open the app and confirm no IndexedDB fallback warning in the console.",
4614
+ fix: "Use a Chromium-based browser with storage access; avoid private mode if persistence fails."
4615
+ };
4616
+ }
4617
+ async function resolveSyncHttpUrl(projectRoot, config) {
4618
+ const envUrl = await readEnvSyncUrl(projectRoot);
4619
+ if (envUrl) {
4620
+ return toSyncStatusHttpBase(envUrl);
4621
+ }
4622
+ const syncPort = typeof config?.dev?.sync === "object" && typeof config.dev.sync.port === "number" ? config.dev.sync.port : 3001;
4623
+ return `http://localhost:${syncPort}`;
4624
+ }
4625
+ async function readEnvSyncUrl(projectRoot) {
4626
+ for (const file of [".env", ".env.local"]) {
4627
+ const path = (0, import_node_path24.join)(projectRoot, file);
4628
+ try {
4629
+ const content = await (0, import_promises15.readFile)(path, "utf-8");
4630
+ for (const line of content.split("\n")) {
4631
+ const trimmed = line.trim();
4632
+ if (!trimmed || trimmed.startsWith("#")) continue;
4633
+ const match = trimmed.match(/^VITE_SYNC_URL=(.+)$/);
4634
+ if (match?.[1]) {
4635
+ return match[1].trim().replace(/^["']|["']$/g, "");
4636
+ }
4637
+ }
4638
+ } catch {
4639
+ }
4640
+ }
4641
+ return null;
4642
+ }
4643
+ function toSyncStatusHttpBase(url) {
4644
+ try {
4645
+ const parsed = new URL(url);
4646
+ if (parsed.protocol === "ws:") {
4647
+ parsed.protocol = "http:";
4648
+ } else if (parsed.protocol === "wss:") {
4649
+ parsed.protocol = "https:";
4650
+ }
4651
+ parsed.pathname = "";
4652
+ parsed.search = "";
4653
+ parsed.hash = "";
4654
+ return parsed.toString().replace(/\/$/, "");
4655
+ } catch {
4656
+ return "http://localhost:3001";
4657
+ }
4658
+ }
4659
+ async function checkSyncServer(httpBase, schema) {
4660
+ const statusUrl = `${httpBase.replace(/\/$/, "")}${STATUS_ENDPOINT}`;
4661
+ try {
4662
+ const response = await fetch(statusUrl, {
4663
+ signal: AbortSignal.timeout(5e3)
4664
+ });
4665
+ if (!response.ok) {
4666
+ return [
4667
+ {
4668
+ id: "sync-server",
4669
+ title: "Sync server",
4670
+ status: "fail",
4671
+ message: `GET ${statusUrl} returned ${response.status}.`,
4672
+ fix: "Start the sync server with `kora dev` or `pnpm run dev` in a sync template."
4673
+ }
4674
+ ];
4675
+ }
4676
+ const body = await response.json();
4677
+ const results = [
4678
+ {
4679
+ id: "sync-server",
4680
+ title: "Sync server",
4681
+ status: "pass",
4682
+ message: `Reachable at ${statusUrl} (server v${body.version ?? "unknown"}).`
4683
+ }
4684
+ ];
4685
+ if (typeof body.schemaVersion === "number" && body.schemaVersion !== schema.version) {
4686
+ results.push({
4687
+ id: "schema-version",
4688
+ title: "Schema version",
4689
+ status: "warn",
4690
+ message: `Client schema v${schema.version} differs from server schema v${body.schemaVersion}.`,
4691
+ fix: "Run migrations on the server and client, or align schema.version before syncing."
4692
+ });
4693
+ } else {
4694
+ results.push({
4695
+ id: "schema-version",
4696
+ title: "Schema version",
4697
+ status: "pass",
4698
+ message: `Client and server both use schema v${schema.version}.`
4699
+ });
4700
+ }
4701
+ return results;
4702
+ } catch (error) {
4703
+ const detail = error instanceof Error ? error.message : String(error);
4704
+ return [
4705
+ {
4706
+ id: "sync-server",
4707
+ title: "Sync server",
4708
+ status: "warn",
4709
+ message: `Could not reach ${statusUrl}: ${detail}`,
4710
+ fix: "Start the sync server, or pass --skip-network for local-only checks."
4711
+ }
4712
+ ];
4713
+ }
4058
4714
  }
4059
- const server = createKoraServer({ store, port: Number(config.port ?? 3001) });
4060
- const shutdown = async () => {
4061
- try {
4062
- await server.stop();
4063
- } catch {
4715
+ function doctorHasFailures(results) {
4716
+ return results.some((r) => r.status === "fail");
4717
+ }
4718
+
4719
+ // src/commands/doctor/doctor-command.ts
4720
+ var doctorCommand = (0, import_citty6.defineCommand)({
4721
+ meta: {
4722
+ name: "doctor",
4723
+ description: "Check Kora project setup (schema, worker, sync server, versions)"
4724
+ },
4725
+ args: {
4726
+ url: {
4727
+ type: "string",
4728
+ description: "Sync server HTTP base URL for status probe (default: from .env or localhost:3001)"
4729
+ },
4730
+ "skip-network": {
4731
+ type: "boolean",
4732
+ description: "Skip sync server and schema version network checks",
4733
+ default: false
4734
+ }
4735
+ },
4736
+ async run({ args }) {
4737
+ const logger = createLogger();
4738
+ const projectRoot = await findProjectRoot();
4739
+ if (!projectRoot) {
4740
+ throw new InvalidProjectError(process.cwd());
4741
+ }
4742
+ const syncHttpUrl = typeof args.url === "string" ? args.url : void 0;
4743
+ const skipNetwork = args["skip-network"] === true;
4744
+ logger.banner();
4745
+ logger.info(`Checking project at ${projectRoot}`);
4746
+ logger.blank();
4747
+ const results = await runDoctorChecks({
4748
+ projectRoot,
4749
+ syncHttpUrl,
4750
+ skipNetwork
4751
+ });
4752
+ for (const check of results) {
4753
+ const prefix = check.status === "pass" ? "success" : check.status === "warn" ? "warn" : check.status === "fail" ? "error" : "step";
4754
+ logger[prefix](`${check.title}: ${check.message}`);
4755
+ if (check.fix && check.status !== "pass" && check.status !== "skip") {
4756
+ logger.step(` Fix: ${check.fix}`);
4757
+ }
4758
+ }
4759
+ logger.blank();
4760
+ if (doctorHasFailures(results)) {
4761
+ logger.error("Doctor found blocking issues.");
4762
+ process.exitCode = 1;
4763
+ return;
4764
+ }
4765
+ const warnings = results.filter((r) => r.status === "warn").length;
4766
+ if (warnings > 0) {
4767
+ logger.warn(`Doctor finished with ${warnings} warning(s).`);
4768
+ } else {
4769
+ logger.success("All checks passed.");
4770
+ }
4064
4771
  }
4065
- process.exit(0);
4066
- };
4067
- process.on('SIGINT', () => {
4068
- void shutdown();
4069
- });
4070
- process.on('SIGTERM', () => {
4071
- void shutdown();
4072
4772
  });
4073
- await server.start();
4074
- process.stdout.write('Managed sync server running on ws://localhost:' + String(config.port ?? 3001) + '\\n');
4075
- await new Promise(() => {});
4076
- `;
4077
4773
 
4078
4774
  // src/commands/generate/generate-command.ts
4079
- var import_promises14 = require("fs/promises");
4080
- var import_node_path21 = require("path");
4081
- var import_citty4 = require("citty");
4775
+ var import_promises16 = require("fs/promises");
4776
+ var import_node_path25 = require("path");
4777
+ var import_citty7 = require("citty");
4778
+
4779
+ // src/commands/generate/hook-generator.ts
4780
+ function generateCollectionHooks(schema) {
4781
+ const files = /* @__PURE__ */ new Map();
4782
+ for (const collectionName of Object.keys(schema.collections)) {
4783
+ const pascal = toPascalCase(collectionName);
4784
+ const hookName = `use${pascal}`;
4785
+ const content = [
4786
+ "// Auto-generated by `kora generate hooks` \u2014 safe to edit",
4787
+ `// Schema v${String(schema.version)}`,
4788
+ "",
4789
+ "import { useCollection, useMutation, useQuery } from '@korajs/react'",
4790
+ `import type { ${pascal}InsertInput, ${pascal}Record, ${pascal}UpdateInput } from '../types'`,
4791
+ "",
4792
+ "/**",
4793
+ ` * Collection hook for \`${collectionName}\`.`,
4794
+ " * Add useQuery(...) calls with your QueryBuilder factories.",
4795
+ " */",
4796
+ `export function ${hookName}() {`,
4797
+ ` const ${collectionName} = useCollection('${collectionName}')`,
4798
+ "",
4799
+ ` const insert = useMutation((data: ${pascal}InsertInput) => ${collectionName}.insert(data))`,
4800
+ ` const update = useMutation((id: string, data: ${pascal}UpdateInput) =>`,
4801
+ ` ${collectionName}.update(id, data),`,
4802
+ " )",
4803
+ ` const remove = useMutation((id: string) => ${collectionName}.delete(id))`,
4804
+ "",
4805
+ " return {",
4806
+ ` ${collectionName},`,
4807
+ " insert,",
4808
+ " update,",
4809
+ " remove,",
4810
+ " }",
4811
+ "}",
4812
+ ""
4813
+ ].join("\n");
4814
+ files.set(`${collectionName}.ts`, content);
4815
+ }
4816
+ const indexLines = [
4817
+ "// Auto-generated by `kora generate hooks`",
4818
+ "",
4819
+ ...Object.keys(schema.collections).map((name) => {
4820
+ const pascal = toPascalCase(name);
4821
+ return `export { use${pascal} } from './${name}'`;
4822
+ }),
4823
+ ""
4824
+ ];
4825
+ files.set("index.ts", indexLines.join("\n"));
4826
+ return files;
4827
+ }
4828
+ function toPascalCase(value) {
4829
+ return value.split(/[-_]/).filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
4830
+ }
4082
4831
 
4083
4832
  // src/commands/generate/type-generator.ts
4084
4833
  function generateTypes(schema) {
@@ -4092,7 +4841,7 @@ function generateTypes(schema) {
4092
4841
  return lines.join("\n");
4093
4842
  }
4094
4843
  for (const [name, collection] of Object.entries(schema.collections)) {
4095
- const pascal = toPascalCase(name);
4844
+ const pascal = toPascalCase2(name);
4096
4845
  const fields = collection.fields;
4097
4846
  lines.push(`export interface ${pascal}Record {`);
4098
4847
  lines.push(" readonly id: string");
@@ -4169,7 +4918,7 @@ function itemKindToTypeScript(kind) {
4169
4918
  return "unknown";
4170
4919
  }
4171
4920
  }
4172
- function toPascalCase(name) {
4921
+ function toPascalCase2(name) {
4173
4922
  return name.split(/[_-]/).map((part) => {
4174
4923
  if (part.length === 0) return "";
4175
4924
  const first = part[0];
@@ -4178,13 +4927,13 @@ function toPascalCase(name) {
4178
4927
  }
4179
4928
 
4180
4929
  // src/commands/generate/generate-command.ts
4181
- var generateCommand = (0, import_citty4.defineCommand)({
4930
+ var generateCommand = (0, import_citty7.defineCommand)({
4182
4931
  meta: {
4183
4932
  name: "generate",
4184
4933
  description: "Generate code from your Kora schema"
4185
4934
  },
4186
4935
  subCommands: {
4187
- types: (0, import_citty4.defineCommand)({
4936
+ types: (0, import_citty7.defineCommand)({
4188
4937
  meta: {
4189
4938
  name: "types",
4190
4939
  description: "Generate TypeScript types from your schema"
@@ -4208,7 +4957,7 @@ var generateCommand = (0, import_citty4.defineCommand)({
4208
4957
  }
4209
4958
  let schemaPath;
4210
4959
  if (args.schema && typeof args.schema === "string") {
4211
- schemaPath = (0, import_node_path21.resolve)(args.schema);
4960
+ schemaPath = (0, import_node_path25.resolve)(args.schema);
4212
4961
  } else {
4213
4962
  const found = await findSchemaFile(projectRoot);
4214
4963
  if (!found) {
@@ -4223,7 +4972,7 @@ var generateCommand = (0, import_citty4.defineCommand)({
4223
4972
  }
4224
4973
  logger.step(`Reading schema from ${schemaPath}...`);
4225
4974
  const schemaModule = await import(schemaPath);
4226
- const schema = extractSchema(schemaModule);
4975
+ const schema = extractSchema2(schemaModule);
4227
4976
  if (!schema) {
4228
4977
  logger.error("Schema file must export a SchemaDefinition as the default export.");
4229
4978
  process.exitCode = 1;
@@ -4231,31 +4980,193 @@ var generateCommand = (0, import_citty4.defineCommand)({
4231
4980
  }
4232
4981
  const output = generateTypes(schema);
4233
4982
  const outputFile = typeof args.output === "string" ? args.output : "kora/generated/types.ts";
4234
- const outputPath = (0, import_node_path21.resolve)(projectRoot, outputFile);
4235
- await (0, import_promises14.mkdir)((0, import_node_path21.dirname)(outputPath), { recursive: true });
4236
- await (0, import_promises14.writeFile)(outputPath, output, "utf-8");
4983
+ const outputPath = (0, import_node_path25.resolve)(projectRoot, outputFile);
4984
+ await (0, import_promises16.mkdir)((0, import_node_path25.dirname)(outputPath), { recursive: true });
4985
+ await (0, import_promises16.writeFile)(outputPath, output, "utf-8");
4237
4986
  logger.success(`Generated types at ${outputPath}`);
4238
4987
  }
4988
+ }),
4989
+ hooks: (0, import_citty7.defineCommand)({
4990
+ meta: {
4991
+ name: "hooks",
4992
+ description: "Generate per-collection React hook stubs"
4993
+ },
4994
+ args: {
4995
+ schema: {
4996
+ type: "string",
4997
+ description: "Path to schema file"
4998
+ },
4999
+ output: {
5000
+ type: "string",
5001
+ description: "Output directory for hooks",
5002
+ default: "kora/generated/hooks"
5003
+ },
5004
+ types: {
5005
+ type: "string",
5006
+ description: "Relative import path to generated types from hook files",
5007
+ default: "../types"
5008
+ }
5009
+ },
5010
+ async run({ args }) {
5011
+ const logger = createLogger();
5012
+ const projectRoot = await findProjectRoot();
5013
+ if (!projectRoot) {
5014
+ throw new InvalidProjectError(process.cwd());
5015
+ }
5016
+ let schemaPath;
5017
+ if (args.schema && typeof args.schema === "string") {
5018
+ schemaPath = (0, import_node_path25.resolve)(projectRoot, args.schema);
5019
+ } else {
5020
+ const found = await findSchemaFile(projectRoot);
5021
+ if (!found) {
5022
+ throw new SchemaNotFoundError([
5023
+ "src/schema.ts",
5024
+ "schema.ts",
5025
+ "src/schema.js",
5026
+ "schema.js"
5027
+ ]);
5028
+ }
5029
+ schemaPath = found;
5030
+ }
5031
+ const schema = await loadSchemaDefinition(schemaPath, projectRoot);
5032
+ const outputDir = typeof args.output === "string" ? (0, import_node_path25.resolve)(projectRoot, args.output) : (0, import_node_path25.resolve)(projectRoot, "kora/generated/hooks");
5033
+ const files = generateCollectionHooks(schema);
5034
+ for (const [name, content] of files) {
5035
+ const adjusted = typeof args.types === "string" && args.types !== "../types" ? content.replace("from '../types'", `from '${args.types}'`) : content;
5036
+ const filePath = (0, import_node_path25.resolve)(outputDir, name);
5037
+ await (0, import_promises16.mkdir)((0, import_node_path25.dirname)(filePath), { recursive: true });
5038
+ await (0, import_promises16.writeFile)(filePath, adjusted, "utf-8");
5039
+ }
5040
+ logger.success(`Generated ${String(files.size)} hook file(s) at ${outputDir}`);
5041
+ logger.step("Run `kora generate types` first if ../types is missing.");
5042
+ }
4239
5043
  })
4240
5044
  }
4241
5045
  });
4242
- function extractSchema(mod) {
5046
+ function extractSchema2(mod) {
4243
5047
  if (typeof mod !== "object" || mod === null) return null;
4244
5048
  const record = mod;
4245
5049
  const candidate = record.default ?? record;
4246
- if (isSchemaDefinition(candidate)) return candidate;
5050
+ if (isSchemaDefinition2(candidate)) return candidate;
4247
5051
  return null;
4248
5052
  }
4249
- function isSchemaDefinition(value) {
5053
+ function isSchemaDefinition2(value) {
4250
5054
  if (typeof value !== "object" || value === null) return false;
4251
5055
  const obj = value;
4252
5056
  return typeof obj.version === "number" && typeof obj.collections === "object" && obj.collections !== null;
4253
5057
  }
4254
5058
 
5059
+ // src/commands/logs/logs-command.ts
5060
+ var import_citty8 = require("citty");
5061
+ var DEFAULT_SYNC_PORT2 = 3001;
5062
+ var EVENTS_ENDPOINT = "/__kora/events";
5063
+ var logsCommand = (0, import_citty8.defineCommand)({
5064
+ meta: {
5065
+ name: "logs",
5066
+ description: "Stream real-time events from a Kora sync server"
5067
+ },
5068
+ args: {
5069
+ url: {
5070
+ type: "string",
5071
+ description: "Sync server URL (default: http://localhost:3001)",
5072
+ default: `http://localhost:${DEFAULT_SYNC_PORT2}`
5073
+ },
5074
+ follow: {
5075
+ type: "boolean",
5076
+ description: "Follow log output (like tail -f)",
5077
+ default: true,
5078
+ alias: "f"
5079
+ },
5080
+ level: {
5081
+ type: "string",
5082
+ description: "Filter by log level (info, warn, error)"
5083
+ },
5084
+ token: {
5085
+ type: "string",
5086
+ description: "Admin token (defaults to KORA_ADMIN_TOKEN)"
5087
+ }
5088
+ },
5089
+ async run({ args }) {
5090
+ const logger = createLogger();
5091
+ const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT2}`;
5092
+ const follow = args.follow !== false;
5093
+ const levelFilter = typeof args.level === "string" ? args.level : null;
5094
+ const token = typeof args.token === "string" ? args.token : process.env.KORA_ADMIN_TOKEN ?? void 0;
5095
+ const eventsUrl = `${url.replace(/\/$/, "")}${EVENTS_ENDPOINT}`;
5096
+ logger.banner();
5097
+ logger.info(`Connecting to ${eventsUrl}...`);
5098
+ logger.blank();
5099
+ try {
5100
+ const response = await fetch(eventsUrl, {
5101
+ headers: token ? { Authorization: `Bearer ${token}` } : void 0
5102
+ });
5103
+ if (!response.ok) {
5104
+ throw new Error(
5105
+ `Failed to connect to ${eventsUrl}: ${response.status} ${response.statusText}`
5106
+ );
5107
+ }
5108
+ const reader = response.body?.getReader();
5109
+ if (!reader) {
5110
+ throw new Error("Response body is not readable");
5111
+ }
5112
+ const decoder = new TextDecoder();
5113
+ let buffer = "";
5114
+ const processLine = (line) => {
5115
+ if (line.startsWith("event: ")) {
5116
+ return;
5117
+ }
5118
+ if (line.startsWith("data: ")) {
5119
+ const data = line.slice(6);
5120
+ try {
5121
+ const parsed = JSON.parse(data);
5122
+ const level = String(parsed.level ?? "info");
5123
+ if (levelFilter && level !== levelFilter) return;
5124
+ const timestamp = parsed.timestamp ? new Date(parsed.timestamp).toISOString() : "";
5125
+ const event = String(parsed.event ?? "unknown");
5126
+ const nodeId = parsed.nodeId ? ` [${parsed.nodeId}]` : "";
5127
+ const session = parsed.sessionId ? ` <${parsed.sessionId}>` : "";
5128
+ const count = parsed.count ? ` (${parsed.count})` : "";
5129
+ const error = parsed.error ? ` \u2014 ${parsed.error}` : "";
5130
+ const prefix = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : "\u25CF";
5131
+ const color = level === "error" ? "\x1B[31m" : level === "warn" ? "\x1B[33m" : "\x1B[36m";
5132
+ console.log(
5133
+ `${color}${prefix}${"\x1B[0m"} ${timestamp.slice(11, 23)} ${event}${nodeId}${session}${count}${error}`
5134
+ );
5135
+ } catch {
5136
+ }
5137
+ }
5138
+ };
5139
+ const read = async () => {
5140
+ while (true) {
5141
+ const { done, value } = await reader.read();
5142
+ if (done) break;
5143
+ buffer += decoder.decode(value, { stream: true });
5144
+ const lines = buffer.split("\n");
5145
+ buffer = lines.pop() ?? "";
5146
+ for (const line of lines) {
5147
+ processLine(line.trim());
5148
+ }
5149
+ if (!follow) break;
5150
+ }
5151
+ };
5152
+ await read();
5153
+ } catch (error) {
5154
+ logger.error(`Failed to connect to ${eventsUrl}`);
5155
+ if (error instanceof Error) {
5156
+ logger.error(error.message);
5157
+ }
5158
+ logger.blank();
5159
+ logger.step("Make sure the Kora sync server is running.");
5160
+ logger.step("Start it with: kora dev");
5161
+ process.exit(1);
5162
+ }
5163
+ }
5164
+ });
5165
+
4255
5166
  // src/commands/migrate/migrate-command.ts
4256
- var import_promises15 = require("fs/promises");
4257
- var import_node_path23 = require("path");
4258
- var import_citty5 = require("citty");
5167
+ var import_promises17 = require("fs/promises");
5168
+ var import_node_path26 = require("path");
5169
+ var import_citty9 = require("citty");
4259
5170
 
4260
5171
  // src/commands/migrate/migration-generator.ts
4261
5172
  var import_core2 = require("@korajs/core");
@@ -4347,7 +5258,8 @@ function isBreakingChange(change) {
4347
5258
  if (change.type === "field-changed") {
4348
5259
  if (change.before.kind !== change.after.kind) return true;
4349
5260
  if (change.before.itemKind !== change.after.itemKind) return true;
4350
- if (serializeEnum(change.before.enumValues) !== serializeEnum(change.after.enumValues)) return true;
5261
+ if (serializeEnum(change.before.enumValues) !== serializeEnum(change.after.enumValues))
5262
+ return true;
4351
5263
  if (change.before.required !== change.after.required && change.after.required) return true;
4352
5264
  return false;
4353
5265
  }
@@ -4731,9 +5643,7 @@ async function runSqliteMigration(path, statements, migrationId, fromVersion, to
4731
5643
  async function loadSqliteDriver(projectRoot) {
4732
5644
  try {
4733
5645
  const { createRequire } = await import("module");
4734
- const requireFrom = createRequire(
4735
- projectRoot ? `${projectRoot}/package.json` : import_meta3.url
4736
- );
5646
+ const requireFrom = createRequire(projectRoot ? `${projectRoot}/package.json` : import_meta3.url);
4737
5647
  const Database = requireFrom("better-sqlite3");
4738
5648
  return {
4739
5649
  open(path) {
@@ -4822,83 +5732,34 @@ async function loadPostgresModule() {
4822
5732
  }
4823
5733
  }
4824
5734
 
4825
- // src/commands/migrate/schema-loader.ts
4826
- var import_node_child_process11 = require("child_process");
4827
- var import_node_path22 = require("path");
4828
- var import_node_url4 = require("url");
4829
- async function loadSchemaDefinition(schemaPath, projectRoot) {
4830
- const ext = (0, import_node_path22.extname)(schemaPath);
4831
- const moduleValue = ext === ".ts" || ext === ".mts" || ext === ".cts" ? await loadTypeScriptModule(schemaPath, projectRoot) : await import(`${(0, import_node_url4.pathToFileURL)(schemaPath).href}?t=${Date.now()}-${Math.random()}`);
4832
- return extractSchema2(moduleValue);
4833
- }
4834
- function extractSchema2(value) {
4835
- if (typeof value !== "object" || value === null) {
4836
- throw new Error("Schema module must export an object.");
4837
- }
4838
- const moduleRecord = value;
4839
- const candidate = moduleRecord.default ?? moduleRecord;
4840
- if (!isSchemaDefinition2(candidate)) {
4841
- throw new Error("Schema module must export a valid SchemaDefinition as default export.");
4842
- }
4843
- return candidate;
4844
- }
4845
- function isSchemaDefinition2(value) {
4846
- if (typeof value !== "object" || value === null) return false;
4847
- const object = value;
4848
- return typeof object.version === "number" && typeof object.collections === "object" && object.collections !== null && typeof object.relations === "object" && object.relations !== null;
4849
- }
4850
- async function loadTypeScriptModule(schemaPath, projectRoot) {
4851
- if (!await hasTsxInstalled(projectRoot)) {
4852
- throw new Error(
4853
- `Schema file is TypeScript (${schemaPath}) but local "tsx" was not found. Install tsx in the project.`
4854
- );
4855
- }
4856
- const script = "const modulePath = process.argv[process.argv.length - 1];import('node:url').then(u => import(u.pathToFileURL(modulePath).href)).then(mod => { const v = mod.default ?? mod; process.stdout.write(JSON.stringify(v)) }).catch(e => { process.stderr.write(String(e)); process.exit(1) })";
4857
- const output = await runCommand2(
4858
- process.execPath,
4859
- ["--import", "tsx", "--eval", script, schemaPath],
4860
- projectRoot
4861
- );
4862
- try {
4863
- return JSON.parse(output);
4864
- } catch {
4865
- throw new Error(`Failed to parse schema module output for ${schemaPath}`);
4866
- }
4867
- }
4868
- async function runCommand2(command, args, cwd) {
4869
- return await new Promise((resolve9, reject) => {
4870
- const child = (0, import_node_child_process11.spawn)(command, args, {
4871
- cwd,
4872
- stdio: ["ignore", "pipe", "pipe"],
4873
- env: process.env
4874
- });
4875
- let stdout = "";
4876
- let stderr = "";
4877
- child.stdout?.on("data", (chunk) => {
4878
- stdout += chunk.toString("utf-8");
4879
- });
4880
- child.stderr?.on("data", (chunk) => {
4881
- stderr += chunk.toString("utf-8");
4882
- });
4883
- child.on("error", (error) => {
4884
- reject(error);
4885
- });
4886
- child.on("exit", (code) => {
4887
- if (code === 0) {
4888
- resolve9(stdout.trim());
4889
- return;
4890
- }
4891
- reject(
4892
- new Error(`Failed to load TypeScript schema (exit ${code ?? "unknown"}): ${stderr.trim()}`)
4893
- );
4894
- });
4895
- });
5735
+ // src/commands/migrate/operation-transform-generator.ts
5736
+ function generateOperationTransformModule(fromVersion, toVersion) {
5737
+ return [
5738
+ "import type { Operation, OperationTransform } from '@korajs/core'",
5739
+ "",
5740
+ "/**",
5741
+ ` * Operation transforms for schema v${fromVersion} \u2192 v${toVersion}.`,
5742
+ " * Implement `transform` to rewrite or drop legacy operations during sync.",
5743
+ " * @see https://korajs.dev/docs/migrations",
5744
+ " */",
5745
+ "export const operationTransforms: OperationTransform[] = [",
5746
+ " {",
5747
+ ` fromVersion: ${fromVersion},`,
5748
+ ` toVersion: ${toVersion},`,
5749
+ " transform(operation: Operation): Operation | null {",
5750
+ " // Return null to drop unmigratable operations.",
5751
+ " return operation",
5752
+ " },",
5753
+ " },",
5754
+ "]",
5755
+ ""
5756
+ ].join("\n");
4896
5757
  }
4897
5758
 
4898
5759
  // src/commands/migrate/migrate-command.ts
4899
5760
  var SNAPSHOT_PATH = "kora/schema.snapshot.json";
4900
5761
  var MIGRATIONS_DIR = "kora/migrations";
4901
- var migrateCommand = (0, import_citty5.defineCommand)({
5762
+ var migrateCommand = (0, import_citty9.defineCommand)({
4902
5763
  meta: {
4903
5764
  name: "migrate",
4904
5765
  description: "Detect schema changes and generate/apply migrations"
@@ -4940,12 +5801,12 @@ var migrateCommand = (0, import_citty5.defineCommand)({
4940
5801
  throw new InvalidProjectError(process.cwd());
4941
5802
  }
4942
5803
  const config = await loadKoraConfig(projectRoot);
4943
- const resolvedSchemaPath = typeof args.schema === "string" ? (0, import_node_path23.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path23.resolve)(projectRoot, config.schema) : await findSchemaFile(projectRoot);
5804
+ const resolvedSchemaPath = typeof args.schema === "string" ? (0, import_node_path26.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path26.resolve)(projectRoot, config.schema) : await findSchemaFile(projectRoot);
4944
5805
  if (!resolvedSchemaPath) {
4945
5806
  throw new SchemaNotFoundError(["src/schema.ts", "schema.ts", "src/schema.js", "schema.js"]);
4946
5807
  }
4947
5808
  const currentSchema = await loadSchemaDefinition(resolvedSchemaPath, projectRoot);
4948
- const snapshotFile = (0, import_node_path23.join)(projectRoot, SNAPSHOT_PATH);
5809
+ const snapshotFile = (0, import_node_path26.join)(projectRoot, SNAPSHOT_PATH);
4949
5810
  const previousSchema = await readSchemaSnapshot(snapshotFile);
4950
5811
  if (!previousSchema) {
4951
5812
  if (args["dry-run"] === true) {
@@ -4958,7 +5819,7 @@ var migrateCommand = (0, import_citty5.defineCommand)({
4958
5819
  return;
4959
5820
  }
4960
5821
  const diff = diffSchemas(previousSchema, currentSchema);
4961
- const outputDir = typeof args["output-dir"] === "string" ? (0, import_node_path23.resolve)(projectRoot, args["output-dir"]) : (0, import_node_path23.resolve)(projectRoot, MIGRATIONS_DIR);
5822
+ const outputDir = typeof args["output-dir"] === "string" ? (0, import_node_path26.resolve)(projectRoot, args["output-dir"]) : (0, import_node_path26.resolve)(projectRoot, MIGRATIONS_DIR);
4962
5823
  if (!diff.hasChanges) {
4963
5824
  logger.success("No schema changes detected.");
4964
5825
  if (args.apply === true) {
@@ -5010,8 +5871,13 @@ var migrateCommand = (0, import_citty5.defineCommand)({
5010
5871
  logger.warn("Dry run enabled: no files written, no migrations applied.");
5011
5872
  return;
5012
5873
  }
5013
- await (0, import_promises15.mkdir)(outputDir, { recursive: true });
5014
- const migrationPath = await writeMigrationFile(outputDir, diff.fromVersion, diff.toVersion, generated);
5874
+ await (0, import_promises17.mkdir)(outputDir, { recursive: true });
5875
+ const migrationPath = await writeMigrationFile(
5876
+ outputDir,
5877
+ diff.fromVersion,
5878
+ diff.toVersion,
5879
+ generated
5880
+ );
5015
5881
  await writeSchemaSnapshot(snapshotFile, currentSchema);
5016
5882
  logger.blank();
5017
5883
  logger.success(`Generated migration: ${migrationPath}`);
@@ -5041,7 +5907,7 @@ var migrateCommand = (0, import_citty5.defineCommand)({
5041
5907
  });
5042
5908
  async function readSchemaSnapshot(path) {
5043
5909
  try {
5044
- const content = await (0, import_promises15.readFile)(path, "utf-8");
5910
+ const content = await (0, import_promises17.readFile)(path, "utf-8");
5045
5911
  return JSON.parse(content);
5046
5912
  } catch {
5047
5913
  return null;
@@ -5062,15 +5928,15 @@ function isInteractiveTerminal2() {
5062
5928
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
5063
5929
  }
5064
5930
  async function writeSchemaSnapshot(path, schema) {
5065
- await (0, import_promises15.mkdir)((0, import_node_path23.dirname)(path), { recursive: true });
5066
- await (0, import_promises15.writeFile)(path, `${JSON.stringify(schema, null, 2)}
5931
+ await (0, import_promises17.mkdir)((0, import_node_path26.dirname)(path), { recursive: true });
5932
+ await (0, import_promises17.writeFile)(path, `${JSON.stringify(schema, null, 2)}
5067
5933
  `, "utf-8");
5068
5934
  }
5069
5935
  async function writeMigrationFile(outputDir, fromVersion, toVersion, generated) {
5070
- const existing = await (0, import_promises15.readdir)(outputDir).catch(() => []);
5936
+ const existing = await (0, import_promises17.readdir)(outputDir).catch(() => []);
5071
5937
  const sequence = existing.filter((file) => /^\d{3}-/.test(file)).length + 1;
5072
5938
  const filename = `${String(sequence).padStart(3, "0")}-v${fromVersion}-to-v${toVersion}.ts`;
5073
- const path = (0, import_node_path23.join)(outputDir, filename);
5939
+ const path = (0, import_node_path26.join)(outputDir, filename);
5074
5940
  const migrationId = filename.replace(/\.ts$/, "");
5075
5941
  const fileContent = [
5076
5942
  `export const up = ${JSON.stringify(generated.up, null, 2)} as const`,
@@ -5082,8 +5948,10 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
5082
5948
  `export const containsBreakingChanges = ${generated.containsBreakingChanges}`,
5083
5949
  ""
5084
5950
  ].join("\n");
5085
- await (0, import_promises15.writeFile)(path, fileContent, "utf-8");
5086
- await writeMigrationManifest((0, import_node_path23.join)(outputDir, `${migrationId}.json`), {
5951
+ await (0, import_promises17.writeFile)(path, fileContent, "utf-8");
5952
+ const transformsPath = (0, import_node_path26.join)(outputDir, `${migrationId}.transforms.ts`);
5953
+ await (0, import_promises17.writeFile)(transformsPath, generateOperationTransformModule(fromVersion, toVersion), "utf-8");
5954
+ await writeMigrationManifest((0, import_node_path26.join)(outputDir, `${migrationId}.json`), {
5087
5955
  id: migrationId,
5088
5956
  fromVersion,
5089
5957
  toVersion,
@@ -5095,22 +5963,22 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
5095
5963
  return path;
5096
5964
  }
5097
5965
  async function writeMigrationManifest(path, manifest) {
5098
- await (0, import_promises15.writeFile)(path, `${JSON.stringify(manifest, null, 2)}
5966
+ await (0, import_promises17.writeFile)(path, `${JSON.stringify(manifest, null, 2)}
5099
5967
  `, "utf-8");
5100
5968
  }
5101
5969
  async function listMigrationManifests(outputDir) {
5102
- const files = await (0, import_promises15.readdir)(outputDir).catch(() => []);
5970
+ const files = await (0, import_promises17.readdir)(outputDir).catch(() => []);
5103
5971
  const migrationFiles = files.filter((file) => /^\d{3}-.*\.ts$/.test(file)).sort((left, right) => left.localeCompare(right));
5104
5972
  const manifests = [];
5105
5973
  for (const file of migrationFiles) {
5106
5974
  const id = file.replace(/\.ts$/, "");
5107
- const manifestPath = (0, import_node_path23.join)(outputDir, `${id}.json`);
5975
+ const manifestPath = (0, import_node_path26.join)(outputDir, `${id}.json`);
5108
5976
  const jsonManifest = await readMigrationManifest(manifestPath);
5109
5977
  if (jsonManifest) {
5110
5978
  manifests.push({ ...jsonManifest, id });
5111
5979
  continue;
5112
5980
  }
5113
- const migrationPath = (0, import_node_path23.join)(outputDir, file);
5981
+ const migrationPath = (0, import_node_path26.join)(outputDir, file);
5114
5982
  const sourceManifest = await readMigrationManifestFromSource(migrationPath, id);
5115
5983
  manifests.push(sourceManifest);
5116
5984
  }
@@ -5118,7 +5986,7 @@ async function listMigrationManifests(outputDir) {
5118
5986
  }
5119
5987
  async function readMigrationManifest(path) {
5120
5988
  try {
5121
- const content = await (0, import_promises15.readFile)(path, "utf-8");
5989
+ const content = await (0, import_promises17.readFile)(path, "utf-8");
5122
5990
  return JSON.parse(content);
5123
5991
  } catch (error) {
5124
5992
  const code = error.code;
@@ -5129,7 +5997,7 @@ async function readMigrationManifest(path) {
5129
5997
  }
5130
5998
  }
5131
5999
  async function readMigrationManifestFromSource(path, id) {
5132
- const content = await (0, import_promises15.readFile)(path, "utf-8");
6000
+ const content = await (0, import_promises17.readFile)(path, "utf-8");
5133
6001
  const versions = parseVersionsFromMigrationId(id);
5134
6002
  return {
5135
6003
  id,
@@ -5146,9 +6014,14 @@ function parseVersionsFromMigrationId(id) {
5146
6014
  if (!match) {
5147
6015
  throw new Error(`Migration id "${id}" does not include a vX-to-vY version suffix.`);
5148
6016
  }
6017
+ const fromVersionText = match[1];
6018
+ const toVersionText = match[2];
6019
+ if (fromVersionText === void 0 || toVersionText === void 0) {
6020
+ throw new Error(`Migration id "${id}" does not include a vX-to-vY version suffix.`);
6021
+ }
5149
6022
  return {
5150
- fromVersion: Number.parseInt(match[1], 10),
5151
- toVersion: Number.parseInt(match[2], 10)
6023
+ fromVersion: Number.parseInt(fromVersionText, 10),
6024
+ toVersion: Number.parseInt(toVersionText, 10)
5152
6025
  };
5153
6026
  }
5154
6027
  function parseStringArrayExport(source, exportName) {
@@ -5186,18 +6059,18 @@ function parseLiteralExport(source, exportName) {
5186
6059
  }
5187
6060
  function resolveSqliteApplyPath(dbArg, projectRoot, config) {
5188
6061
  if (typeof dbArg === "string") {
5189
- return (0, import_node_path23.resolve)(projectRoot, dbArg);
6062
+ return (0, import_node_path26.resolve)(projectRoot, dbArg);
5190
6063
  }
5191
6064
  const sync = config?.dev?.sync;
5192
6065
  if (typeof sync === "object" && sync !== null) {
5193
6066
  if (sync.store === "sqlite") {
5194
- return (0, import_node_path23.join)(projectRoot, "kora-sync.db");
6067
+ return (0, import_node_path26.join)(projectRoot, "kora-sync.db");
5195
6068
  }
5196
6069
  if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "sqlite") {
5197
6070
  if (typeof sync.store.filename === "string" && sync.store.filename.length > 0) {
5198
- return (0, import_node_path23.resolve)(projectRoot, sync.store.filename);
6071
+ return (0, import_node_path26.resolve)(projectRoot, sync.store.filename);
5199
6072
  }
5200
- return (0, import_node_path23.join)(projectRoot, "kora-sync.db");
6073
+ return (0, import_node_path26.join)(projectRoot, "kora-sync.db");
5201
6074
  }
5202
6075
  }
5203
6076
  return void 0;
@@ -5214,19 +6087,156 @@ function resolvePostgresConnectionString(config) {
5214
6087
  return void 0;
5215
6088
  }
5216
6089
 
6090
+ // src/commands/status/status-command.ts
6091
+ var import_citty10 = require("citty");
6092
+ var DEFAULT_SYNC_PORT3 = 3001;
6093
+ var STATUS_ENDPOINT2 = "/__kora/status";
6094
+ async function fetchServerStatus(url, token) {
6095
+ const statusUrl = `${url.replace(/\/$/, "")}${STATUS_ENDPOINT2}`;
6096
+ const response = await fetch(statusUrl, {
6097
+ headers: token ? { Authorization: `Bearer ${token}` } : void 0
6098
+ });
6099
+ if (!response.ok) {
6100
+ throw new Error(
6101
+ `Failed to fetch status from ${statusUrl}: ${response.status} ${response.statusText}`
6102
+ );
6103
+ }
6104
+ return await response.json();
6105
+ }
6106
+ function formatUptime(ms) {
6107
+ const seconds = Math.floor(ms / 1e3);
6108
+ const minutes = Math.floor(seconds / 60);
6109
+ const hours = Math.floor(minutes / 60);
6110
+ const days = Math.floor(hours / 24);
6111
+ const parts = [];
6112
+ if (days > 0) parts.push(`${days}d`);
6113
+ if (hours % 24 > 0) parts.push(`${hours % 24}h`);
6114
+ if (minutes % 60 > 0) parts.push(`${minutes % 60}m`);
6115
+ parts.push(`${seconds % 60}s`);
6116
+ return parts.join(" ");
6117
+ }
6118
+ var statusCommand = (0, import_citty10.defineCommand)({
6119
+ meta: {
6120
+ name: "status",
6121
+ description: "Show Kora sync server status"
6122
+ },
6123
+ args: {
6124
+ url: {
6125
+ type: "string",
6126
+ description: "Sync server URL (default: http://localhost:3001)",
6127
+ default: `http://localhost:${DEFAULT_SYNC_PORT3}`
6128
+ },
6129
+ watch: {
6130
+ type: "boolean",
6131
+ description: "Live-updating status (like htop)",
6132
+ default: false,
6133
+ alias: "w"
6134
+ },
6135
+ token: {
6136
+ type: "string",
6137
+ description: "Admin token (defaults to KORA_ADMIN_TOKEN)"
6138
+ }
6139
+ },
6140
+ async run({ args }) {
6141
+ const logger = createLogger();
6142
+ const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT3}`;
6143
+ const watch2 = args.watch === true;
6144
+ const token = typeof args.token === "string" ? args.token : process.env.KORA_ADMIN_TOKEN ?? void 0;
6145
+ try {
6146
+ if (watch2) {
6147
+ console.clear();
6148
+ logger.banner();
6149
+ logger.info(`Connecting to ${url}...`);
6150
+ const interval = setInterval(async () => {
6151
+ try {
6152
+ const status = await fetchServerStatus(url, token);
6153
+ printStatus(status, url, logger);
6154
+ } catch {
6155
+ }
6156
+ }, 2e3);
6157
+ try {
6158
+ const status = await fetchServerStatus(url, token);
6159
+ printStatus(status, url, logger);
6160
+ } catch {
6161
+ }
6162
+ await new Promise(() => {
6163
+ process.on("SIGINT", () => {
6164
+ clearInterval(interval);
6165
+ process.exit(0);
6166
+ });
6167
+ process.on("SIGTERM", () => {
6168
+ clearInterval(interval);
6169
+ process.exit(0);
6170
+ });
6171
+ });
6172
+ } else {
6173
+ const status = await fetchServerStatus(url, token);
6174
+ printStatus(status, url, logger);
6175
+ }
6176
+ } catch (error) {
6177
+ logger.error(`Failed to connect to ${url}`);
6178
+ if (error instanceof Error) {
6179
+ logger.error(error.message);
6180
+ }
6181
+ logger.blank();
6182
+ logger.step("Make sure the Kora sync server is running.");
6183
+ logger.step("Start it with: kora dev");
6184
+ process.exit(1);
6185
+ }
6186
+ }
6187
+ });
6188
+ function printStatus(status, url, logger) {
6189
+ console.clear();
6190
+ logger.banner();
6191
+ logger.info(`Kora Sync Server \u2014 ${url}`);
6192
+ logger.blank();
6193
+ if (!status.running) {
6194
+ logger.error("Server is not running");
6195
+ return;
6196
+ }
6197
+ logger.step(`Status: Running (uptime: ${formatUptime(status.uptime)}) \u2014 v${status.version}`);
6198
+ logger.step(`Schema: v${status.schemaVersion}`);
6199
+ logger.blank();
6200
+ logger.step(`Connections: ${status.connectedClients} connected`);
6201
+ logger.step(` Peak: ${status.peakConnections}`);
6202
+ logger.step(` Total: ${status.connectionsTotal}`);
6203
+ if (status.connectedNodeIds.length > 0) {
6204
+ logger.step(" Active nodes:");
6205
+ for (const nodeId of status.connectedNodeIds) {
6206
+ logger.step(` \u2022 ${nodeId}`);
6207
+ }
6208
+ }
6209
+ logger.blank();
6210
+ logger.step("Operations:");
6211
+ logger.step(` Received: ${status.operationsReceived.toLocaleString()}`);
6212
+ logger.step(` Sent: ${status.operationsSent.toLocaleString()}`);
6213
+ logger.step(` Total: ${status.totalOperations.toLocaleString()}`);
6214
+ logger.blank();
6215
+ if (status.errorCount > 0) {
6216
+ logger.warn(`Errors: ${status.errorCount}`);
6217
+ } else {
6218
+ logger.step("Errors: 0");
6219
+ }
6220
+ }
6221
+
5217
6222
  // src/bin.ts
5218
- var main = (0, import_citty6.defineCommand)({
6223
+ var main = (0, import_citty11.defineCommand)({
5219
6224
  meta: {
5220
6225
  name: "kora",
5221
6226
  description: "Kora.js \u2014 Offline-first application framework"
5222
6227
  },
5223
6228
  subCommands: {
6229
+ backup: backupCommand,
6230
+ compact: compactCommand,
5224
6231
  create: createCommand,
5225
6232
  dev: devCommand,
6233
+ doctor: doctorCommand,
5226
6234
  deploy: deployCommand,
5227
6235
  generate: generateCommand,
5228
- migrate: migrateCommand
6236
+ logs: logsCommand,
6237
+ migrate: migrateCommand,
6238
+ status: statusCommand
5229
6239
  }
5230
6240
  });
5231
- (0, import_citty6.runMain)(main);
6241
+ (0, import_citty11.runMain)(main);
5232
6242
  //# sourceMappingURL=bin.cjs.map