@korajs/cli 0.4.0 → 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 (103) hide show
  1. package/dist/bin.cjs +1609 -643
  2. package/dist/bin.cjs.map +1 -1
  3. package/dist/bin.js +1086 -139
  4. package/dist/bin.js.map +1 -1
  5. package/dist/{chunk-CMSX76KM.js → chunk-3WNFM3QB.js} +103 -96
  6. package/dist/chunk-3WNFM3QB.js.map +1 -0
  7. package/dist/{chunk-MVP5PDT4.js → chunk-PSRM56B7.js} +2 -2
  8. package/dist/{chunk-YGVO4POI.js → chunk-SOTZIWIF.js} +27 -15
  9. package/dist/chunk-SOTZIWIF.js.map +1 -0
  10. package/dist/create.cjs +54 -47
  11. package/dist/create.cjs.map +1 -1
  12. package/dist/create.js +2 -2
  13. package/dist/index.cjs +78 -59
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +5 -3
  16. package/dist/index.d.ts +5 -3
  17. package/dist/index.js +2 -2
  18. package/package.json +9 -3
  19. package/templates/react-basic/README.md.hbs +10 -1
  20. package/templates/react-basic/src/App.tsx +112 -126
  21. package/templates/react-basic/src/index.css +165 -159
  22. package/templates/react-basic/src/kora-worker.ts +0 -1
  23. package/templates/react-basic/src/main.tsx +12 -12
  24. package/templates/react-basic/src/modules/todos/todo.mutations.ts +25 -0
  25. package/templates/react-basic/src/modules/todos/todo.queries.ts +5 -0
  26. package/templates/react-basic/src/modules/todos/todo.schema.ts +10 -0
  27. package/templates/react-basic/src/modules/todos/useTodos.ts +31 -0
  28. package/templates/react-basic/src/schema.ts +6 -12
  29. package/templates/react-basic/src/vite-env.d.ts +17 -0
  30. package/templates/react-basic/{tsconfig.json → tsconfig.json.hbs} +1 -2
  31. package/templates/react-basic/vite.config.ts +57 -50
  32. package/templates/react-sync/.env.example +19 -0
  33. package/templates/react-sync/README.md.hbs +43 -1
  34. package/templates/react-sync/package.json.hbs +3 -0
  35. package/templates/react-sync/server.ts +90 -26
  36. package/templates/react-sync/src/App.tsx +132 -130
  37. package/templates/react-sync/src/auth.ts +25 -0
  38. package/templates/react-sync/src/index.css +212 -165
  39. package/templates/react-sync/src/kora-worker.ts +0 -1
  40. package/templates/react-sync/src/main.tsx +34 -18
  41. package/templates/react-sync/src/modules/todos/todo.mutations.ts +25 -0
  42. package/templates/react-sync/src/modules/todos/todo.queries.ts +5 -0
  43. package/templates/react-sync/src/modules/todos/todo.schema.ts +10 -0
  44. package/templates/react-sync/src/modules/todos/useTodos.ts +31 -0
  45. package/templates/react-sync/src/schema.ts +6 -12
  46. package/templates/react-sync/src/vite-env.d.ts +17 -0
  47. package/templates/react-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
  48. package/templates/react-sync/vite.config.ts +67 -60
  49. package/templates/react-tailwind/README.md.hbs +10 -1
  50. package/templates/react-tailwind/src/App.tsx +186 -197
  51. package/templates/react-tailwind/src/index.css +3 -3
  52. package/templates/react-tailwind/src/kora-worker.ts +0 -1
  53. package/templates/react-tailwind/src/main.tsx +19 -12
  54. package/templates/react-tailwind/src/modules/todos/todo.mutations.ts +25 -0
  55. package/templates/react-tailwind/src/modules/todos/todo.queries.ts +5 -0
  56. package/templates/react-tailwind/src/modules/todos/todo.schema.ts +10 -0
  57. package/templates/react-tailwind/src/modules/todos/useTodos.ts +31 -0
  58. package/templates/react-tailwind/src/schema.ts +6 -12
  59. package/templates/react-tailwind/src/vite-env.d.ts +17 -0
  60. package/templates/react-tailwind/{tsconfig.json → tsconfig.json.hbs} +1 -2
  61. package/templates/react-tailwind/vite.config.ts +57 -50
  62. package/templates/react-tailwind-sync/.env.example +19 -0
  63. package/templates/react-tailwind-sync/README.md.hbs +43 -1
  64. package/templates/react-tailwind-sync/package.json.hbs +3 -0
  65. package/templates/react-tailwind-sync/server.ts +90 -26
  66. package/templates/react-tailwind-sync/src/App.tsx +246 -222
  67. package/templates/react-tailwind-sync/src/auth.ts +25 -0
  68. package/templates/react-tailwind-sync/src/index.css +3 -3
  69. package/templates/react-tailwind-sync/src/kora-worker.ts +0 -1
  70. package/templates/react-tailwind-sync/src/main.tsx +48 -18
  71. package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
  72. package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
  73. package/templates/react-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
  74. package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +31 -0
  75. package/templates/react-tailwind-sync/src/schema.ts +6 -12
  76. package/templates/react-tailwind-sync/src/vite-env.d.ts +17 -0
  77. package/templates/react-tailwind-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
  78. package/templates/react-tailwind-sync/vite.config.ts +67 -60
  79. package/templates/tauri-react/.env.example +20 -0
  80. package/templates/tauri-react/README.md.hbs +36 -1
  81. package/templates/tauri-react/dev.ts +11 -11
  82. package/templates/tauri-react/package.json.hbs +2 -0
  83. package/templates/tauri-react/server.ts +86 -12
  84. package/templates/tauri-react/src/App.tsx +769 -544
  85. package/templates/tauri-react/src/AppShell.tsx +125 -83
  86. package/templates/tauri-react/src/SetupScreen.tsx +257 -208
  87. package/templates/tauri-react/src/auth.ts +45 -0
  88. package/templates/tauri-react/src/main.tsx +3 -3
  89. package/templates/tauri-react/src/modules/todos/todo.mutations.ts +25 -0
  90. package/templates/tauri-react/src/modules/todos/todo.queries.ts +5 -0
  91. package/templates/tauri-react/src/modules/todos/todo.schema.ts +10 -0
  92. package/templates/tauri-react/src/modules/todos/useTodos.ts +31 -0
  93. package/templates/tauri-react/src/schema.ts +6 -12
  94. package/templates/tauri-react/src/sync-config.ts +47 -47
  95. package/templates/tauri-react/src/updater.ts +15 -15
  96. package/templates/tauri-react/src/vite-env.d.ts +17 -0
  97. package/templates/tauri-react/src-tauri/tauri.conf.json +1 -1
  98. package/templates/tauri-react/tsconfig.json.hbs +14 -0
  99. package/templates/tauri-react/vite.config.ts +9 -9
  100. package/dist/chunk-CMSX76KM.js.map +0 -1
  101. package/dist/chunk-YGVO4POI.js.map +0 -1
  102. package/templates/tauri-react/tsconfig.json +0 -15
  103. /package/dist/{chunk-MVP5PDT4.js.map → chunk-PSRM56B7.js.map} +0 -0
package/dist/bin.cjs CHANGED
@@ -23,67 +23,600 @@ 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
+
28
+ // src/commands/backup/backup-command.ts
29
+ var import_promises = require("fs/promises");
30
+ var import_node_path = require("path");
31
+ var import_citty = require("citty");
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
+
244
+ // src/errors.ts
245
+ var import_core = require("@korajs/core");
246
+ var ProjectExistsError = class extends import_core.KoraError {
247
+ constructor(directory) {
248
+ super(
249
+ `Directory "${directory}" already exists. Choose a different name or remove the existing directory.`,
250
+ "PROJECT_EXISTS",
251
+ { directory }
252
+ );
253
+ this.directory = directory;
254
+ this.name = "ProjectExistsError";
255
+ }
256
+ directory;
257
+ };
258
+ var SchemaNotFoundError = class extends import_core.KoraError {
259
+ constructor(searchedPaths) {
260
+ super(
261
+ `Could not find a schema file. Searched: ${searchedPaths.join(", ")}. Create a schema file using defineSchema() from @korajs/core.`,
262
+ "SCHEMA_NOT_FOUND",
263
+ { searchedPaths }
264
+ );
265
+ this.searchedPaths = searchedPaths;
266
+ this.name = "SchemaNotFoundError";
267
+ }
268
+ searchedPaths;
269
+ };
270
+ var InvalidProjectError = class extends import_core.KoraError {
271
+ constructor(directory) {
272
+ super(
273
+ `"${directory}" is not a valid Kora project. No package.json with a kora dependency found. Run this command from inside a Kora project.`,
274
+ "INVALID_PROJECT",
275
+ { directory }
276
+ );
277
+ this.directory = directory;
278
+ this.name = "InvalidProjectError";
279
+ }
280
+ directory;
281
+ };
282
+ var DevServerError = class extends import_core.KoraError {
283
+ constructor(binary, searchPath) {
284
+ super(
285
+ `Could not find required binary "${binary}" at ${searchPath}. Install project dependencies and try again.`,
286
+ "DEV_SERVER_ERROR",
287
+ { binary, searchPath }
288
+ );
289
+ this.binary = binary;
290
+ this.searchPath = searchPath;
291
+ this.name = "DevServerError";
292
+ }
293
+ binary;
294
+ searchPath;
295
+ };
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
+ });
27
613
 
28
614
  // src/commands/create/create-command.ts
29
- var import_node_child_process2 = require("child_process");
615
+ var import_node_child_process4 = require("child_process");
30
616
  var import_node_fs2 = require("fs");
31
- var import_node_path5 = require("path");
32
- var import_node_url2 = require("url");
33
- var import_citty = require("citty");
34
-
35
- // src/errors.ts
36
- var import_core = require("@korajs/core");
37
- var ProjectExistsError = class extends import_core.KoraError {
38
- constructor(directory) {
39
- super(
40
- `Directory "${directory}" already exists. Choose a different name or remove the existing directory.`,
41
- "PROJECT_EXISTS",
42
- { directory }
43
- );
44
- this.directory = directory;
45
- this.name = "ProjectExistsError";
46
- }
47
- directory;
48
- };
49
- var SchemaNotFoundError = class extends import_core.KoraError {
50
- constructor(searchedPaths) {
51
- super(
52
- `Could not find a schema file. Searched: ${searchedPaths.join(", ")}. Create a schema file using defineSchema() from @korajs/core.`,
53
- "SCHEMA_NOT_FOUND",
54
- { searchedPaths }
55
- );
56
- this.searchedPaths = searchedPaths;
57
- this.name = "SchemaNotFoundError";
58
- }
59
- searchedPaths;
60
- };
61
- var InvalidProjectError = class extends import_core.KoraError {
62
- constructor(directory) {
63
- super(
64
- `"${directory}" is not a valid Kora project. No package.json with a kora dependency found. Run this command from inside a Kora project.`,
65
- "INVALID_PROJECT",
66
- { directory }
67
- );
68
- this.directory = directory;
69
- this.name = "InvalidProjectError";
70
- }
71
- directory;
72
- };
73
- var DevServerError = class extends import_core.KoraError {
74
- constructor(binary, searchPath) {
75
- super(
76
- `Could not find required binary "${binary}" at ${searchPath}. Install project dependencies and try again.`,
77
- "DEV_SERVER_ERROR",
78
- { binary, searchPath }
79
- );
80
- this.binary = binary;
81
- this.searchPath = searchPath;
82
- this.name = "DevServerError";
83
- }
84
- binary;
85
- searchPath;
86
- };
617
+ var import_node_path9 = require("path");
618
+ var import_node_url4 = require("url");
619
+ var import_citty3 = require("citty");
87
620
 
88
621
  // src/prompts/preferences.ts
89
622
  var import_conf = __toESM(require("conf"), 1);
@@ -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,17 +710,17 @@ 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
  }
193
726
  ;
@@ -300,132 +833,8 @@ var TEMPLATES = [
300
833
  "tauri-react"
301
834
  ];
302
835
 
303
- // src/utils/fs-helpers.ts
304
- var import_promises = require("fs/promises");
305
- var import_node_path = require("path");
306
- async function directoryExists(path) {
307
- try {
308
- await (0, import_promises.access)(path);
309
- return true;
310
- } catch {
311
- return false;
312
- }
313
- }
314
- async function findProjectRoot(startDir) {
315
- let current = (0, import_node_path.resolve)(startDir ?? process.cwd());
316
- for (; ; ) {
317
- const pkgPath = (0, import_node_path.join)(current, "package.json");
318
- try {
319
- const content = await (0, import_promises.readFile)(pkgPath, "utf-8");
320
- const pkg = JSON.parse(content);
321
- if (isKoraProject(pkg)) {
322
- return current;
323
- }
324
- } catch {
325
- }
326
- const parent = (0, import_node_path.dirname)(current);
327
- if (parent === current) break;
328
- current = parent;
329
- }
330
- return null;
331
- }
332
- async function findSchemaFile(projectRoot) {
333
- const candidates = [
334
- (0, import_node_path.join)(projectRoot, "src", "schema.ts"),
335
- (0, import_node_path.join)(projectRoot, "schema.ts"),
336
- (0, import_node_path.join)(projectRoot, "src", "schema.js"),
337
- (0, import_node_path.join)(projectRoot, "schema.js")
338
- ];
339
- for (const candidate of candidates) {
340
- try {
341
- await (0, import_promises.access)(candidate);
342
- return candidate;
343
- } catch {
344
- }
345
- }
346
- return null;
347
- }
348
- async function hasTsxInstalled(projectRoot) {
349
- try {
350
- await (0, import_promises.access)((0, import_node_path.join)(projectRoot, "node_modules", "tsx", "package.json"));
351
- return true;
352
- } catch {
353
- return false;
354
- }
355
- }
356
- async function resolveProjectBinaryEntryPoint(projectRoot, packageName, binaryName) {
357
- const pkgJsonPath = (0, import_node_path.join)(projectRoot, "node_modules", packageName, "package.json");
358
- try {
359
- const content = await (0, import_promises.readFile)(pkgJsonPath, "utf-8");
360
- const pkg = JSON.parse(content);
361
- let binPath;
362
- if (typeof pkg.bin === "string") {
363
- binPath = pkg.bin;
364
- } else if (typeof pkg.bin === "object" && pkg.bin !== null) {
365
- binPath = pkg.bin[binaryName];
366
- }
367
- if (!binPath) return null;
368
- const fullPath = (0, import_node_path.join)(projectRoot, "node_modules", packageName, binPath);
369
- await (0, import_promises.access)(fullPath);
370
- return fullPath;
371
- } catch {
372
- return null;
373
- }
374
- }
375
- function isKoraProject(pkg) {
376
- if (typeof pkg !== "object" || pkg === null) return false;
377
- const record = pkg;
378
- return hasKoraDep(record.dependencies) || hasKoraDep(record.devDependencies);
379
- }
380
- function hasKoraDep(deps) {
381
- if (typeof deps !== "object" || deps === null) return false;
382
- return Object.keys(deps).some((key) => key === "kora" || key.startsWith("@korajs/"));
383
- }
384
-
385
- // src/utils/logger.ts
386
- var RESET = "\x1B[0m";
387
- var BOLD = "\x1B[1m";
388
- var DIM = "\x1B[2m";
389
- var GREEN = "\x1B[32m";
390
- var YELLOW = "\x1B[33m";
391
- var RED = "\x1B[31m";
392
- var CYAN = "\x1B[36m";
393
- function createLogger(options) {
394
- const colorDisabled = options?.noColor === true || process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
395
- function color(code, text) {
396
- return colorDisabled ? text : `${code}${text}${RESET}`;
397
- }
398
- return {
399
- info(message) {
400
- console.log(color(CYAN, message));
401
- },
402
- success(message) {
403
- console.log(color(GREEN, ` \u2713 ${message}`));
404
- },
405
- warn(message) {
406
- console.warn(color(YELLOW, ` \u26A0 ${message}`));
407
- },
408
- error(message) {
409
- console.error(color(RED, ` \u2717 ${message}`));
410
- },
411
- step(message) {
412
- console.log(color(DIM, ` ${message}`));
413
- },
414
- blank() {
415
- console.log();
416
- },
417
- banner() {
418
- console.log();
419
- console.log(
420
- color(BOLD + CYAN, " Kora.js") + color(DIM, " \u2014 Offline-first application framework")
421
- );
422
- console.log();
423
- }
424
- };
425
- }
426
-
427
836
  // src/utils/package-manager.ts
428
- var import_node_child_process = require("child_process");
837
+ var import_node_child_process3 = require("child_process");
429
838
  function detectPackageManager() {
430
839
  const userAgent = process.env.npm_config_user_agent;
431
840
  if (!userAgent) return "npm";
@@ -443,8 +852,8 @@ function getRunDevCommand(pm) {
443
852
  }
444
853
 
445
854
  // src/commands/create/environment-detection.ts
446
- var import_promises2 = require("fs/promises");
447
- var import_node_path2 = require("path");
855
+ var import_promises4 = require("fs/promises");
856
+ var import_node_path6 = require("path");
448
857
  function detectEditorFromEnvironment(env = process.env) {
449
858
  const termProgram = String(env.TERM_PROGRAM ?? "").toLowerCase();
450
859
  const editorValue = `${String(env.EDITOR ?? "")} ${String(env.VISUAL ?? "")}`.toLowerCase();
@@ -470,18 +879,18 @@ async function detectGitContext(startDir) {
470
879
  };
471
880
  }
472
881
  async function detectMonorepoContext(startDir) {
473
- let current = (0, import_node_path2.resolve)(startDir);
882
+ let current = (0, import_node_path6.resolve)(startDir);
474
883
  for (; ; ) {
475
- 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"))) {
476
885
  return { isMonorepo: true, root: current, kind: "pnpm-workspace" };
477
886
  }
478
- if (await fileExists((0, import_node_path2.join)(current, "turbo.json"))) {
887
+ if (await fileExists((0, import_node_path6.join)(current, "turbo.json"))) {
479
888
  return { isMonorepo: true, root: current, kind: "turborepo" };
480
889
  }
481
- const packageJsonPath = (0, import_node_path2.join)(current, "package.json");
890
+ const packageJsonPath = (0, import_node_path6.join)(current, "package.json");
482
891
  if (await fileExists(packageJsonPath)) {
483
892
  try {
484
- const packageJsonRaw = await (0, import_promises2.readFile)(packageJsonPath, "utf-8");
893
+ const packageJsonRaw = await (0, import_promises4.readFile)(packageJsonPath, "utf-8");
485
894
  const parsed = JSON.parse(packageJsonRaw);
486
895
  if (Array.isArray(parsed.workspaces) || isNpmWorkspaceObject(parsed.workspaces)) {
487
896
  return { isMonorepo: true, root: current, kind: "npm-workspaces" };
@@ -489,7 +898,7 @@ async function detectMonorepoContext(startDir) {
489
898
  } catch {
490
899
  }
491
900
  }
492
- const parent = (0, import_node_path2.dirname)(current);
901
+ const parent = (0, import_node_path6.dirname)(current);
493
902
  if (parent === current) {
494
903
  return { isMonorepo: false, root: null, kind: "none" };
495
904
  }
@@ -501,9 +910,9 @@ async function applyEditorWorkspacePreset(params) {
501
910
  if (editor !== "vscode" && editor !== "cursor" && editor !== "windsurf") {
502
911
  return { applied: false, filePath: null };
503
912
  }
504
- const vscodeDir = (0, import_node_path2.join)(targetDir, ".vscode");
505
- const extensionsPath = (0, import_node_path2.join)(vscodeDir, "extensions.json");
506
- 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 });
507
916
  const recommendations = ["korajs.kora-devtools"];
508
917
  const existing = await readJsonObject(extensionsPath);
509
918
  const existingRecommendations = Array.isArray(existing?.recommendations) ? existing.recommendations.filter((item) => typeof item === "string") : [];
@@ -511,19 +920,19 @@ async function applyEditorWorkspacePreset(params) {
511
920
  const next = {
512
921
  recommendations: mergedRecommendations
513
922
  };
514
- await (0, import_promises2.writeFile)(extensionsPath, `${JSON.stringify(next, null, 2)}
923
+ await (0, import_promises4.writeFile)(extensionsPath, `${JSON.stringify(next, null, 2)}
515
924
  `, "utf-8");
516
925
  return { applied: true, filePath: extensionsPath };
517
926
  }
518
927
  function resolveMonorepoTargetDirectory(monorepoRoot, projectName) {
519
- return (0, import_node_path2.join)(monorepoRoot, "packages", projectName);
928
+ return (0, import_node_path6.join)(monorepoRoot, "packages", projectName);
520
929
  }
521
930
  function dedupeStrings(values) {
522
931
  return Array.from(new Set(values));
523
932
  }
524
933
  async function readJsonObject(path) {
525
934
  try {
526
- const content = await (0, import_promises2.readFile)(path, "utf-8");
935
+ const content = await (0, import_promises4.readFile)(path, "utf-8");
527
936
  const parsed = JSON.parse(content);
528
937
  if (typeof parsed === "object" && parsed !== null) {
529
938
  return parsed;
@@ -540,22 +949,22 @@ function isNpmWorkspaceObject(value) {
540
949
  }
541
950
  async function fileExists(path) {
542
951
  try {
543
- await (0, import_promises2.access)(path);
952
+ await (0, import_promises4.access)(path);
544
953
  return true;
545
954
  } catch {
546
955
  return false;
547
956
  }
548
957
  }
549
958
  async function findNearestAncestorWithEntry(startDir, entryName) {
550
- let current = (0, import_node_path2.resolve)(startDir);
959
+ let current = (0, import_node_path6.resolve)(startDir);
551
960
  for (; ; ) {
552
- const candidate = (0, import_node_path2.join)(current, entryName);
961
+ const candidate = (0, import_node_path6.join)(current, entryName);
553
962
  try {
554
- await (0, import_promises2.stat)(candidate);
963
+ await (0, import_promises4.stat)(candidate);
555
964
  return current;
556
965
  } catch {
557
966
  }
558
- const parent = (0, import_node_path2.dirname)(current);
967
+ const parent = (0, import_node_path6.dirname)(current);
559
968
  if (parent === current) {
560
969
  return null;
561
970
  }
@@ -626,10 +1035,49 @@ async function resolveCreatePreferencesFlow(params) {
626
1035
  if (isTauri) {
627
1036
  effective.framework = "react";
628
1037
  effective.tailwind = false;
629
- effective.sync = true;
630
- effective.db = "sqlite";
631
- effective.dbProvider = "none";
632
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
+ }
633
1081
  } else {
634
1082
  if (flags.framework !== void 0) {
635
1083
  if (!isFrameworkValue(flags.framework)) {
@@ -778,8 +1226,8 @@ function validateProjectName(name) {
778
1226
  }
779
1227
 
780
1228
  // src/commands/create/sync-provider-preset.ts
781
- var import_promises3 = require("fs/promises");
782
- var import_node_path3 = require("path");
1229
+ var import_promises5 = require("fs/promises");
1230
+ var import_node_path7 = require("path");
783
1231
  async function applySyncProviderPreset(options) {
784
1232
  if (!isSyncTemplate(options.template)) {
785
1233
  return;
@@ -789,64 +1237,32 @@ async function applySyncProviderPreset(options) {
789
1237
  }
790
1238
  const providerName = getProviderDisplayName(options.dbProvider);
791
1239
  const providerConnectionString = getProviderConnectionStringExample(options.dbProvider);
792
- const serverPath = (0, import_node_path3.join)(options.targetDir, "server.ts");
793
- const envPath = (0, import_node_path3.join)(options.targetDir, ".env.example");
794
- const readmePath = (0, import_node_path3.join)(options.targetDir, "README.md");
795
- const serverTemplate = [
796
- "import { createPostgresServerStore, createProductionServer } from '@korajs/server'",
797
- "",
798
- `// PostgreSQL provider preset: ${providerName}`,
799
- "// Ensure DATABASE_URL is set in your environment.",
800
- "",
801
- "async function start(): Promise<void> {",
802
- " const connectionString = process.env.DATABASE_URL || ''",
803
- " if (connectionString.length === 0) {",
804
- " throw new Error('DATABASE_URL is required for PostgreSQL sync server store.')",
805
- " }",
806
- "",
807
- " const store = await createPostgresServerStore({ connectionString })",
808
- " const server = createProductionServer({",
809
- " store,",
810
- " port: Number(process.env.PORT) || 3001,",
811
- " staticDir: './dist',",
812
- " syncPath: '/kora-sync',",
813
- " })",
814
- "",
815
- " const url = await server.start()",
816
- " console.log(`Kora app running at ${url}`)",
817
- "}",
818
- "",
819
- "void start()",
820
- ""
821
- ].join("\n");
822
- const envTemplate = [
823
- "# Kora Sync Server",
824
- "# WebSocket URL for the sync server (used by the client)",
825
- "VITE_SYNC_URL=ws://localhost:3001",
826
- "",
827
- "# Sync server port",
828
- "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 = [
829
1247
  "",
830
- `# PostgreSQL connection string (${providerName})`,
831
- `# Example: ${providerConnectionString}`,
832
- "DATABASE_URL=",
833
- ""
1248
+ `# PostgreSQL provider preset: ${providerName}`,
1249
+ `# Example: ${providerConnectionString}`
834
1250
  ].join("\n");
835
- const existingReadme = await (0, import_promises3.readFile)(readmePath, "utf-8");
836
- const trimmedReadme = existingReadme.trimEnd();
837
1251
  const readmeSuffix = [
838
1252
  "",
839
1253
  "## PostgreSQL Provider Preset",
840
1254
  "",
841
1255
  `Selected DB provider: ${options.dbProvider}`,
842
1256
  "",
843
- "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.",
844
1260
  ""
845
1261
  ].join("\n");
846
1262
  const readmeTemplate = `${trimmedReadme}${readmeSuffix}`;
847
- await (0, import_promises3.writeFile)(serverPath, serverTemplate, "utf-8");
848
- await (0, import_promises3.writeFile)(envPath, envTemplate, "utf-8");
849
- 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");
850
1266
  }
851
1267
  function isSyncTemplate(template) {
852
1268
  return template === "react-sync" || template === "react-tailwind-sync" || template === "tauri-react";
@@ -890,9 +1306,9 @@ function getProviderConnectionStringExample(provider) {
890
1306
 
891
1307
  // src/templates/composer.ts
892
1308
  var import_node_fs = require("fs");
893
- var import_promises4 = require("fs/promises");
894
- var import_node_path4 = require("path");
895
- 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");
896
1312
  var import_meta = {};
897
1313
  function substituteVariables(template, context) {
898
1314
  return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
@@ -901,16 +1317,16 @@ function substituteVariables(template, context) {
901
1317
  });
902
1318
  }
903
1319
  function getTemplatePath(templateName) {
904
- 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));
905
1321
  for (let i = 0; i < 7; i++) {
906
- const candidate = (0, import_node_path4.resolve)(dir, "templates", templateName);
1322
+ const candidate = (0, import_node_path8.resolve)(dir, "templates", templateName);
907
1323
  if ((0, import_node_fs.existsSync)(candidate)) {
908
1324
  return candidate;
909
1325
  }
910
- dir = (0, import_node_path4.dirname)(dir);
1326
+ dir = (0, import_node_path8.dirname)(dir);
911
1327
  }
912
- const currentDir = (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
913
- 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);
914
1330
  }
915
1331
  function createCompatibilityLayerPlan(templateName) {
916
1332
  const baseLayer = { category: "base", name: "base", sourceTemplate: "react-basic" };
@@ -995,22 +1411,22 @@ async function composeTemplateLayers(plan, targetDir, context) {
995
1411
  }
996
1412
  }
997
1413
  async function copyDirectory(src, dest, vars) {
998
- await (0, import_promises4.mkdir)(dest, { recursive: true });
999
- 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);
1000
1416
  for (const entry of entries) {
1001
- const srcPath = (0, import_node_path4.join)(src, entry);
1002
- 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);
1003
1419
  if (srcStat.isDirectory()) {
1004
- await copyDirectory(srcPath, (0, import_node_path4.join)(dest, entry), vars);
1420
+ await copyDirectory(srcPath, (0, import_node_path8.join)(dest, entry), vars);
1005
1421
  continue;
1006
1422
  }
1007
1423
  if (entry.endsWith(".hbs")) {
1008
- const content = await (0, import_promises4.readFile)(srcPath, "utf-8");
1424
+ const content = await (0, import_promises6.readFile)(srcPath, "utf-8");
1009
1425
  const outputName = entry.slice(0, -4);
1010
- 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");
1011
1427
  continue;
1012
1428
  }
1013
- 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));
1014
1430
  }
1015
1431
  }
1016
1432
 
@@ -1022,7 +1438,7 @@ async function scaffoldTemplate(templateName, targetDir, context) {
1022
1438
 
1023
1439
  // src/commands/create/create-command.ts
1024
1440
  var import_meta2 = {};
1025
- var createCommand = (0, import_citty.defineCommand)({
1441
+ var createCommand = (0, import_citty3.defineCommand)({
1026
1442
  meta: {
1027
1443
  name: "create",
1028
1444
  description: "Create a new Kora application"
@@ -1161,7 +1577,7 @@ var createCommand = (0, import_citty.defineCommand)({
1161
1577
  const editorDetection = detectEditorFromEnvironment();
1162
1578
  const gitContext = await detectGitContext(process.cwd());
1163
1579
  const monorepoContext = await detectMonorepoContext(process.cwd());
1164
- let targetDir = (0, import_node_path5.resolve)(process.cwd(), projectName);
1580
+ let targetDir = (0, import_node_path9.resolve)(process.cwd(), projectName);
1165
1581
  if (!useDefaults && monorepoContext.isMonorepo && monorepoContext.root !== null) {
1166
1582
  const useWorkspaceTarget = await prompts.confirm(
1167
1583
  `Detected ${formatMonorepoKind(monorepoContext.kind)} at ${monorepoContext.root}. Create app under packages/${projectName}?`,
@@ -1231,7 +1647,7 @@ var createCommand = (0, import_citty.defineCommand)({
1231
1647
  if (!args["skip-install"]) {
1232
1648
  logger.step("Installing dependencies...");
1233
1649
  try {
1234
- (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" });
1235
1651
  logger.success("Dependencies installed");
1236
1652
  } catch {
1237
1653
  logger.warn("Failed to install dependencies. Run install manually.");
@@ -1331,9 +1747,9 @@ function relativeToTarget(targetDir, filePath) {
1331
1747
  }
1332
1748
  function resolveKoraVersion() {
1333
1749
  try {
1334
- 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));
1335
1751
  for (let i = 0; i < 5; i++) {
1336
- const pkgPath = (0, import_node_path5.resolve)(dir, "package.json");
1752
+ const pkgPath = (0, import_node_path9.resolve)(dir, "package.json");
1337
1753
  if ((0, import_node_fs2.existsSync)(pkgPath)) {
1338
1754
  const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
1339
1755
  if (pkg.name === "@korajs/cli") {
@@ -1342,7 +1758,7 @@ function resolveKoraVersion() {
1342
1758
  return `^${parts[0]}.${parts[1]}.0`;
1343
1759
  }
1344
1760
  }
1345
- dir = (0, import_node_path5.dirname)(dir);
1761
+ dir = (0, import_node_path9.dirname)(dir);
1346
1762
  }
1347
1763
  return "latest";
1348
1764
  } catch {
@@ -1351,8 +1767,9 @@ function resolveKoraVersion() {
1351
1767
  }
1352
1768
 
1353
1769
  // src/commands/deploy/deploy-command.ts
1354
- var import_node_path16 = require("path");
1355
- 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");
1356
1773
 
1357
1774
  // src/commands/deploy/adapters/adapter.ts
1358
1775
  var DEPLOY_PLATFORMS = [
@@ -1369,14 +1786,14 @@ function isDeployPlatform(value) {
1369
1786
  }
1370
1787
 
1371
1788
  // src/commands/deploy/adapters/aws-ecs-adapter.ts
1372
- var import_node_child_process4 = require("child_process");
1373
- var import_node_path8 = require("path");
1789
+ var import_node_child_process6 = require("child_process");
1790
+ var import_node_path12 = require("path");
1374
1791
 
1375
1792
  // src/commands/deploy/builder/client-builder.ts
1376
- var import_node_child_process3 = require("child_process");
1793
+ var import_node_child_process5 = require("child_process");
1377
1794
  var import_node_fs3 = require("fs");
1378
- var import_promises5 = require("fs/promises");
1379
- var import_node_path6 = require("path");
1795
+ var import_promises7 = require("fs/promises");
1796
+ var import_node_path10 = require("path");
1380
1797
  async function buildClient(options) {
1381
1798
  const viteEntryPoint = await resolveProjectBinaryEntryPoint(options.projectRoot, "vite", "vite");
1382
1799
  if (!viteEntryPoint) {
@@ -1397,15 +1814,15 @@ async function buildClient(options) {
1397
1814
  return { outDir: options.outDir };
1398
1815
  }
1399
1816
  async function patchSqliteWasmAssets(projectRoot, outDir) {
1400
- const assetsDir = (0, import_node_path6.join)(outDir, "assets");
1817
+ const assetsDir = (0, import_node_path10.join)(outDir, "assets");
1401
1818
  if (!(0, import_node_fs3.existsSync)(assetsDir)) return;
1402
- const files = await (0, import_promises5.readdir)(assetsDir);
1819
+ const files = await (0, import_promises7.readdir)(assetsDir);
1403
1820
  const hashedWasm = files.find((f) => /^sqlite3-.+\.wasm$/.test(f));
1404
1821
  if (hashedWasm && !files.includes("sqlite3.wasm")) {
1405
- 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"));
1406
1823
  }
1407
1824
  if (!files.includes("sqlite3-opfs-async-proxy.js")) {
1408
- const proxyFile = (0, import_node_path6.resolve)(
1825
+ const proxyFile = (0, import_node_path10.resolve)(
1409
1826
  projectRoot,
1410
1827
  "node_modules",
1411
1828
  "@sqlite.org",
@@ -1415,13 +1832,13 @@ async function patchSqliteWasmAssets(projectRoot, outDir) {
1415
1832
  "sqlite3-opfs-async-proxy.js"
1416
1833
  );
1417
1834
  if ((0, import_node_fs3.existsSync)(proxyFile)) {
1418
- 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"));
1419
1836
  }
1420
1837
  }
1421
1838
  }
1422
1839
  async function runProcess(command, args, cwd) {
1423
- await new Promise((resolve9, reject) => {
1424
- 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, {
1425
1842
  cwd,
1426
1843
  stdio: "inherit",
1427
1844
  env: process.env
@@ -1431,7 +1848,7 @@ async function runProcess(command, args, cwd) {
1431
1848
  });
1432
1849
  child.on("exit", (code) => {
1433
1850
  if (code === 0) {
1434
- resolve9();
1851
+ resolve11();
1435
1852
  return;
1436
1853
  }
1437
1854
  reject(new Error(`Client build failed with exit code ${String(code ?? "unknown")}.`));
@@ -1440,9 +1857,9 @@ async function runProcess(command, args, cwd) {
1440
1857
  }
1441
1858
 
1442
1859
  // src/commands/deploy/builder/server-bundler.ts
1443
- var import_promises6 = require("fs/promises");
1444
- var import_promises7 = require("fs/promises");
1445
- 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");
1446
1863
  var import_esbuild = require("esbuild");
1447
1864
  var DEFAULT_ENTRY_CANDIDATES = [
1448
1865
  "server.ts",
@@ -1458,8 +1875,8 @@ async function bundleServer(options) {
1458
1875
  `Could not find a server entry file in ${options.projectRoot}. Looked for: ${candidates.join(", ")}`
1459
1876
  );
1460
1877
  }
1461
- await (0, import_promises6.mkdir)(options.deployDirectory, { recursive: true });
1462
- 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");
1463
1880
  await (0, import_esbuild.build)({
1464
1881
  entryPoints: [entryFilePath],
1465
1882
  outfile: outputFilePath,
@@ -1481,9 +1898,9 @@ async function bundleServer(options) {
1481
1898
  }
1482
1899
  async function resolveServerEntry(projectRoot, candidates) {
1483
1900
  for (const candidate of candidates) {
1484
- const fullPath = (0, import_node_path7.join)(projectRoot, candidate);
1901
+ const fullPath = (0, import_node_path11.join)(projectRoot, candidate);
1485
1902
  try {
1486
- await (0, import_promises7.access)(fullPath);
1903
+ await (0, import_promises9.access)(fullPath);
1487
1904
  return fullPath;
1488
1905
  } catch {
1489
1906
  }
@@ -1585,19 +2002,19 @@ var AwsEcsAdapter = class {
1585
2002
  appName: config.appName,
1586
2003
  region: config.region
1587
2004
  };
1588
- const deployDirectory = (0, import_node_path8.join)(config.projectRoot, ".kora", "deploy");
2005
+ const deployDirectory = (0, import_node_path12.join)(config.projectRoot, ".kora", "deploy");
1589
2006
  await bundleServer({
1590
2007
  projectRoot: config.projectRoot,
1591
2008
  deployDirectory
1592
2009
  });
1593
- const client = await buildClient({
2010
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
1594
2011
  projectRoot: config.projectRoot,
1595
- outDir: (0, import_node_path8.join)(deployDirectory, "dist"),
2012
+ outDir: (0, import_node_path12.join)(deployDirectory, "dist"),
1596
2013
  mode: "production"
1597
2014
  });
1598
2015
  return {
1599
- clientDirectory: client.outDir,
1600
- 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"),
1601
2018
  deployDirectory
1602
2019
  };
1603
2020
  }
@@ -1829,8 +2246,8 @@ var AwsEcsAdapter = class {
1829
2246
  };
1830
2247
  var NodeAwsCommandRunner = class {
1831
2248
  async run(command, args, cwd) {
1832
- return new Promise((resolve9) => {
1833
- 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, {
1834
2251
  cwd,
1835
2252
  env: process.env,
1836
2253
  stdio: ["ignore", "pipe", "pipe"]
@@ -1844,11 +2261,11 @@ var NodeAwsCommandRunner = class {
1844
2261
  stderr += chunk.toString("utf-8");
1845
2262
  });
1846
2263
  child.on("error", (error) => {
1847
- resolve9({ exitCode: 1, stdout, stderr: `${stderr}
2264
+ resolve11({ exitCode: 1, stdout, stderr: `${stderr}
1848
2265
  ${error.message}` });
1849
2266
  });
1850
2267
  child.on("exit", (code) => {
1851
- resolve9({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
2268
+ resolve11({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
1852
2269
  });
1853
2270
  });
1854
2271
  }
@@ -1862,8 +2279,8 @@ function inferLogLevel(line) {
1862
2279
  }
1863
2280
 
1864
2281
  // src/commands/deploy/adapters/aws-lightsail-adapter.ts
1865
- var import_node_child_process5 = require("child_process");
1866
- var import_node_path9 = require("path");
2282
+ var import_node_child_process7 = require("child_process");
2283
+ var import_node_path13 = require("path");
1867
2284
  var AwsLightsailAdapter = class {
1868
2285
  name = "aws-lightsail";
1869
2286
  logger = createLogger();
@@ -1936,19 +2353,19 @@ var AwsLightsailAdapter = class {
1936
2353
  appName: config.appName,
1937
2354
  region: config.region
1938
2355
  };
1939
- const deployDirectory = (0, import_node_path9.join)(config.projectRoot, ".kora", "deploy");
2356
+ const deployDirectory = (0, import_node_path13.join)(config.projectRoot, ".kora", "deploy");
1940
2357
  await bundleServer({
1941
2358
  projectRoot: config.projectRoot,
1942
2359
  deployDirectory
1943
2360
  });
1944
- const client = await buildClient({
2361
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
1945
2362
  projectRoot: config.projectRoot,
1946
- outDir: (0, import_node_path9.join)(deployDirectory, "dist"),
2363
+ outDir: (0, import_node_path13.join)(deployDirectory, "dist"),
1947
2364
  mode: "production"
1948
2365
  });
1949
2366
  return {
1950
- clientDirectory: client.outDir,
1951
- 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"),
1952
2369
  deployDirectory
1953
2370
  };
1954
2371
  }
@@ -2176,8 +2593,8 @@ var AwsLightsailAdapter = class {
2176
2593
  };
2177
2594
  var NodeAwsLightsailCommandRunner = class {
2178
2595
  async run(command, args, cwd) {
2179
- return new Promise((resolve9) => {
2180
- 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, {
2181
2598
  cwd,
2182
2599
  env: process.env,
2183
2600
  stdio: ["ignore", "pipe", "pipe"]
@@ -2191,11 +2608,11 @@ var NodeAwsLightsailCommandRunner = class {
2191
2608
  stderr += chunk.toString("utf-8");
2192
2609
  });
2193
2610
  child.on("error", (error) => {
2194
- resolve9({ exitCode: 1, stdout, stderr: `${stderr}
2611
+ resolve11({ exitCode: 1, stdout, stderr: `${stderr}
2195
2612
  ${error.message}` });
2196
2613
  });
2197
2614
  child.on("exit", (code) => {
2198
- resolve9({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
2615
+ resolve11({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
2199
2616
  });
2200
2617
  });
2201
2618
  }
@@ -2247,12 +2664,12 @@ function inferLogLevel2(line) {
2247
2664
  }
2248
2665
 
2249
2666
  // src/commands/deploy/adapters/fly-adapter.ts
2250
- var import_node_child_process6 = require("child_process");
2251
- var import_node_path11 = require("path");
2667
+ var import_node_child_process8 = require("child_process");
2668
+ var import_node_path15 = require("path");
2252
2669
 
2253
2670
  // src/commands/deploy/artifacts/fly-toml-generator.ts
2254
- var import_promises8 = require("fs/promises");
2255
- var import_node_path10 = require("path");
2671
+ var import_promises10 = require("fs/promises");
2672
+ var import_node_path14 = require("path");
2256
2673
  var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
2257
2674
  function generateFlyToml(options) {
2258
2675
  const internalPort = options.internalPort ?? 3e3;
@@ -2277,14 +2694,14 @@ function generateFlyToml(options) {
2277
2694
  ' interval = "30s"',
2278
2695
  ' method = "GET"',
2279
2696
  ' timeout = "5s"',
2280
- ' path = "/"',
2697
+ ' path = "/health"',
2281
2698
  ""
2282
2699
  ].join("\n");
2283
2700
  }
2284
2701
  async function writeFlyTomlArtifact(deployDirectory, options) {
2285
- await (0, import_promises8.mkdir)(deployDirectory, { recursive: true });
2286
- const filePath = (0, import_node_path10.join)(deployDirectory, "fly.toml");
2287
- 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");
2288
2705
  return filePath;
2289
2706
  }
2290
2707
 
@@ -2378,7 +2795,7 @@ var FlyAdapter = class {
2378
2795
  appName: config.appName,
2379
2796
  region: config.region
2380
2797
  };
2381
- const deployDirectory = (0, import_node_path11.join)(config.projectRoot, ".kora", "deploy");
2798
+ const deployDirectory = (0, import_node_path15.join)(config.projectRoot, ".kora", "deploy");
2382
2799
  await writeFlyTomlArtifact(deployDirectory, {
2383
2800
  appName: config.appName,
2384
2801
  region: config.region ?? "iad"
@@ -2387,14 +2804,14 @@ var FlyAdapter = class {
2387
2804
  projectRoot: config.projectRoot,
2388
2805
  deployDirectory
2389
2806
  });
2390
- const client = await buildClient({
2807
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
2391
2808
  projectRoot: config.projectRoot,
2392
- outDir: (0, import_node_path11.join)(deployDirectory, "dist"),
2809
+ outDir: (0, import_node_path15.join)(deployDirectory, "dist"),
2393
2810
  mode: "production"
2394
2811
  });
2395
2812
  return {
2396
- clientDirectory: client.outDir,
2397
- 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"),
2398
2815
  deployDirectory
2399
2816
  };
2400
2817
  }
@@ -2520,8 +2937,8 @@ var FlyAdapter = class {
2520
2937
  };
2521
2938
  var NodeFlyCommandRunner = class {
2522
2939
  async run(command, args, cwd) {
2523
- return await new Promise((resolve9) => {
2524
- 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, {
2525
2942
  cwd,
2526
2943
  env: process.env,
2527
2944
  stdio: ["ignore", "pipe", "pipe"]
@@ -2535,7 +2952,7 @@ var NodeFlyCommandRunner = class {
2535
2952
  stderr += chunk.toString("utf-8");
2536
2953
  });
2537
2954
  child.on("error", (error) => {
2538
- resolve9({
2955
+ resolve11({
2539
2956
  exitCode: 1,
2540
2957
  stdout,
2541
2958
  stderr: `${stderr}
@@ -2543,7 +2960,7 @@ ${error.message}`
2543
2960
  });
2544
2961
  });
2545
2962
  child.on("exit", (code) => {
2546
- resolve9({
2963
+ resolve11({
2547
2964
  exitCode: code ?? 1,
2548
2965
  stdout: stdout.trim(),
2549
2966
  stderr: stderr.trim()
@@ -2615,12 +3032,12 @@ ${stdout}`.toLowerCase();
2615
3032
  }
2616
3033
 
2617
3034
  // src/commands/deploy/adapters/railway-adapter.ts
2618
- var import_node_child_process7 = require("child_process");
2619
- var import_node_path13 = require("path");
3035
+ var import_node_child_process9 = require("child_process");
3036
+ var import_node_path17 = require("path");
2620
3037
 
2621
3038
  // src/commands/deploy/artifacts/railway-json-generator.ts
2622
- var import_promises9 = require("fs/promises");
2623
- var import_node_path12 = require("path");
3039
+ var import_promises11 = require("fs/promises");
3040
+ var import_node_path16 = require("path");
2624
3041
  var GENERATED_HEADER2 = "Generated by kora deploy - do not edit manually.";
2625
3042
  function generateRailwayJson(options) {
2626
3043
  const file = {
@@ -2645,9 +3062,9 @@ function generateRailwayJson(options) {
2645
3062
  `;
2646
3063
  }
2647
3064
  async function writeRailwayJsonArtifact(deployDirectory, options) {
2648
- await (0, import_promises9.mkdir)(deployDirectory, { recursive: true });
2649
- const filePath = (0, import_node_path12.join)(deployDirectory, "railway.json");
2650
- 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");
2651
3068
  return filePath;
2652
3069
  }
2653
3070
 
@@ -2736,7 +3153,7 @@ var RailwayAdapter = class {
2736
3153
  appName: config.appName,
2737
3154
  region: config.region
2738
3155
  };
2739
- const deployDirectory = (0, import_node_path13.join)(config.projectRoot, ".kora", "deploy");
3156
+ const deployDirectory = (0, import_node_path17.join)(config.projectRoot, ".kora", "deploy");
2740
3157
  await writeRailwayJsonArtifact(deployDirectory, {
2741
3158
  appName: config.appName,
2742
3159
  environment: config.environment
@@ -2745,14 +3162,14 @@ var RailwayAdapter = class {
2745
3162
  projectRoot: config.projectRoot,
2746
3163
  deployDirectory
2747
3164
  });
2748
- const client = await buildClient({
3165
+ const client = config.deployTarget === "sync-server" ? null : await buildClient({
2749
3166
  projectRoot: config.projectRoot,
2750
- outDir: (0, import_node_path13.join)(deployDirectory, "dist"),
3167
+ outDir: (0, import_node_path17.join)(deployDirectory, "dist"),
2751
3168
  mode: "production"
2752
3169
  });
2753
3170
  return {
2754
- clientDirectory: client.outDir,
2755
- 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"),
2756
3173
  deployDirectory
2757
3174
  };
2758
3175
  }
@@ -2856,8 +3273,8 @@ var RailwayAdapter = class {
2856
3273
  };
2857
3274
  var NodeRailwayCommandRunner = class {
2858
3275
  async run(command, args, cwd) {
2859
- return await new Promise((resolve9) => {
2860
- 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, {
2861
3278
  cwd,
2862
3279
  env: process.env,
2863
3280
  stdio: ["ignore", "pipe", "pipe"]
@@ -2871,7 +3288,7 @@ var NodeRailwayCommandRunner = class {
2871
3288
  stderr += chunk.toString("utf-8");
2872
3289
  });
2873
3290
  child.on("error", (error) => {
2874
- resolve9({
3291
+ resolve11({
2875
3292
  exitCode: 1,
2876
3293
  stdout,
2877
3294
  stderr: `${stderr}
@@ -2879,7 +3296,7 @@ ${error.message}`
2879
3296
  });
2880
3297
  });
2881
3298
  child.on("exit", (code) => {
2882
- resolve9({
3299
+ resolve11({
2883
3300
  exitCode: code ?? 1,
2884
3301
  stdout: stdout.trim(),
2885
3302
  stderr: stderr.trim()
@@ -3037,13 +3454,13 @@ function createDeployAdapter(platform) {
3037
3454
  }
3038
3455
 
3039
3456
  // src/commands/deploy/artifacts/dockerfile-generator.ts
3040
- var import_promises10 = require("fs/promises");
3041
- var import_node_path14 = require("path");
3457
+ var import_promises12 = require("fs/promises");
3458
+ var import_node_path18 = require("path");
3042
3459
  var GENERATED_HEADER3 = "# Generated by kora deploy \u2014 do not edit manually";
3043
3460
  function generateDockerfile(options = {}) {
3044
3461
  const nodeVersion = options.nodeVersion ?? "20-alpine";
3045
3462
  const port = options.port ?? 3e3;
3046
- const clientDirectory = options.clientDirectory ?? "dist";
3463
+ const clientDirectory = options.clientDirectory === void 0 ? "dist" : options.clientDirectory;
3047
3464
  const serverBundleFile = options.serverBundleFile ?? "server-bundled.js";
3048
3465
  const hasNativeDeps = options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0;
3049
3466
  const lines = [
@@ -3063,7 +3480,9 @@ function generateDockerfile(options = {}) {
3063
3480
  lines.push("RUN npm install --omit=dev");
3064
3481
  lines.push("");
3065
3482
  }
3066
- lines.push(`COPY ${clientDirectory} ./dist`);
3483
+ if (clientDirectory !== null) {
3484
+ lines.push(`COPY ${clientDirectory} ./dist`);
3485
+ }
3067
3486
  lines.push(`COPY ${serverBundleFile} ./server-bundled.js`);
3068
3487
  lines.push("");
3069
3488
  lines.push(`EXPOSE ${String(port)}`);
@@ -3102,37 +3521,37 @@ function generateDockerIgnore() {
3102
3521
  ].join("\n");
3103
3522
  }
3104
3523
  async function writeDockerfileArtifact(deployDirectory, options = {}) {
3105
- await (0, import_promises10.mkdir)(deployDirectory, { recursive: true });
3106
- const dockerfilePath = (0, import_node_path14.join)(deployDirectory, "Dockerfile");
3107
- 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");
3108
3527
  if (options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0) {
3109
- const pkgJsonPath = (0, import_node_path14.join)(deployDirectory, "package.json");
3110
- 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");
3111
3530
  }
3112
3531
  return dockerfilePath;
3113
3532
  }
3114
3533
  async function writeDockerIgnoreArtifact(deployDirectory) {
3115
- await (0, import_promises10.mkdir)(deployDirectory, { recursive: true });
3116
- const dockerIgnorePath = (0, import_node_path14.join)(deployDirectory, ".dockerignore");
3117
- 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");
3118
3537
  return dockerIgnorePath;
3119
3538
  }
3120
3539
 
3121
3540
  // src/commands/deploy/state/deploy-state.ts
3122
- var import_promises11 = require("fs/promises");
3123
- var import_node_path15 = require("path");
3124
- 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");
3125
3544
  var DEPLOY_STATE_FILENAME = "deploy.json";
3126
3545
  function resolveDeployDirectory(projectRoot) {
3127
- return (0, import_node_path15.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
3546
+ return (0, import_node_path19.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
3128
3547
  }
3129
3548
  function resolveDeployStatePath(projectRoot) {
3130
- return (0, import_node_path15.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
3549
+ return (0, import_node_path19.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
3131
3550
  }
3132
3551
  async function readDeployState(projectRoot) {
3133
3552
  const statePath = resolveDeployStatePath(projectRoot);
3134
3553
  try {
3135
- const source = await (0, import_promises11.readFile)(statePath, "utf-8");
3554
+ const source = await (0, import_promises13.readFile)(statePath, "utf-8");
3136
3555
  const parsed = JSON.parse(source);
3137
3556
  return parseDeployState(parsed);
3138
3557
  } catch (error) {
@@ -3179,13 +3598,13 @@ async function updateDeployState(projectRoot, patch, now = /* @__PURE__ */ new D
3179
3598
  return nextState;
3180
3599
  }
3181
3600
  async function resetDeployState(projectRoot) {
3182
- await (0, import_promises11.rm)(resolveDeployDirectory(projectRoot), { recursive: true, force: true });
3601
+ await (0, import_promises13.rm)(resolveDeployDirectory(projectRoot), { recursive: true, force: true });
3183
3602
  }
3184
3603
  async function persistDeployState(projectRoot, state) {
3185
3604
  const deployDir = resolveDeployDirectory(projectRoot);
3186
3605
  const statePath = resolveDeployStatePath(projectRoot);
3187
- await (0, import_promises11.mkdir)(deployDir, { recursive: true });
3188
- 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)}
3189
3608
  `, "utf-8");
3190
3609
  }
3191
3610
  function parseDeployState(value) {
@@ -3235,7 +3654,7 @@ function readOptionalString(value, field) {
3235
3654
  }
3236
3655
 
3237
3656
  // src/commands/deploy/deploy-command.ts
3238
- var deployCommand = (0, import_citty2.defineCommand)({
3657
+ var deployCommand = (0, import_citty4.defineCommand)({
3239
3658
  meta: {
3240
3659
  name: "deploy",
3241
3660
  description: "Deploy a Kora project to your selected platform"
@@ -3270,7 +3689,7 @@ var deployCommand = (0, import_citty2.defineCommand)({
3270
3689
  }
3271
3690
  },
3272
3691
  subCommands: {
3273
- status: (0, import_citty2.defineCommand)({
3692
+ status: (0, import_citty4.defineCommand)({
3274
3693
  meta: {
3275
3694
  name: "status",
3276
3695
  description: "Show current deployment status"
@@ -3301,7 +3720,7 @@ var deployCommand = (0, import_citty2.defineCommand)({
3301
3720
  logger.step(`Sync URL: ${state.syncUrl ?? "n/a"}`);
3302
3721
  }
3303
3722
  }),
3304
- rollback: (0, import_citty2.defineCommand)({
3723
+ rollback: (0, import_citty4.defineCommand)({
3305
3724
  meta: {
3306
3725
  name: "rollback",
3307
3726
  description: "Rollback the current deployment"
@@ -3332,7 +3751,7 @@ var deployCommand = (0, import_citty2.defineCommand)({
3332
3751
  logger.success(`Rolled back ${state.platform} deployment to ${deploymentId}.`);
3333
3752
  }
3334
3753
  }),
3335
- logs: (0, import_citty2.defineCommand)({
3754
+ logs: (0, import_citty4.defineCommand)({
3336
3755
  meta: {
3337
3756
  name: "logs",
3338
3757
  description: "Read deployment logs"
@@ -3388,12 +3807,14 @@ var deployCommand = (0, import_citty2.defineCommand)({
3388
3807
  const region = resolveRegion(args.region, existingState?.region, confirmMode);
3389
3808
  const deployDirectory = resolveDeployDirectory(projectRoot);
3390
3809
  const environment = args.prod === true ? "production" : "preview";
3810
+ const deployTarget = detectDeployTarget(projectRoot);
3391
3811
  const config = {
3392
3812
  projectRoot,
3393
3813
  appName,
3394
3814
  region,
3395
3815
  environment,
3396
- confirm: confirmMode
3816
+ confirm: confirmMode,
3817
+ deployTarget
3397
3818
  };
3398
3819
  const adapter = createDeployAdapter(platform);
3399
3820
  configureAdapterContext(adapter, {
@@ -3405,10 +3826,14 @@ var deployCommand = (0, import_citty2.defineCommand)({
3405
3826
  logger.info(
3406
3827
  `Deploying to ${platform} (${appName}${region ? `, ${region}` : ""}, ${environment})`
3407
3828
  );
3829
+ if (deployTarget === "sync-server") {
3830
+ logger.step("Detected Tauri project. Deploying the sync server only.");
3831
+ }
3408
3832
  if (confirmMode) {
3409
3833
  logger.step("Running in --confirm mode (non-interactive, fail-fast).");
3410
3834
  }
3411
3835
  await writeDockerfileArtifact(deployDirectory, {
3836
+ clientDirectory: deployTarget === "sync-server" ? null : void 0,
3412
3837
  nativeDependencies: {
3413
3838
  "better-sqlite3": "^11.0.0",
3414
3839
  "drizzle-orm": "^0.45.2",
@@ -3513,7 +3938,7 @@ function resolveAppName(argValue, storedValue, projectRoot, confirm) {
3513
3938
  "Missing app name in --confirm mode. Provide --app or run an interactive deploy first."
3514
3939
  );
3515
3940
  }
3516
- return sanitizeAppName((0, import_node_path16.basename)(projectRoot));
3941
+ return sanitizeAppName((0, import_node_path20.basename)(projectRoot));
3517
3942
  }
3518
3943
  function resolveRegion(argValue, storedValue, confirm) {
3519
3944
  if (typeof argValue === "string" && argValue.length > 0) return argValue;
@@ -3547,109 +3972,21 @@ function configureAdapterContext(adapter, context) {
3547
3972
  adapter.setContext(context);
3548
3973
  }
3549
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
+ }
3550
3978
  function hasContextSetter(adapter) {
3551
3979
  return typeof adapter.setContext === "function";
3552
3980
  }
3553
3981
 
3554
3982
  // src/commands/dev/dev-command.ts
3555
- var import_promises13 = require("fs/promises");
3556
- var import_node_path19 = require("path");
3557
- var import_node_path20 = require("path");
3558
- var import_citty3 = require("citty");
3559
-
3560
- // src/commands/dev/kora-config.ts
3561
- var import_node_child_process8 = require("child_process");
3562
- var import_promises12 = require("fs/promises");
3563
- var import_node_path17 = require("path");
3564
- var import_node_url3 = require("url");
3565
- var CONFIG_CANDIDATES = [
3566
- "kora.config.ts",
3567
- "kora.config.mts",
3568
- "kora.config.cts",
3569
- "kora.config.js",
3570
- "kora.config.mjs",
3571
- "kora.config.cjs"
3572
- ];
3573
- async function loadKoraConfig(projectRoot) {
3574
- const configPath = await findKoraConfigFile(projectRoot);
3575
- if (!configPath) return null;
3576
- const ext = (0, import_node_path17.extname)(configPath);
3577
- if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
3578
- const loaded2 = await loadTypeScriptConfig(configPath, projectRoot);
3579
- return toConfigObject(loaded2);
3580
- }
3581
- const loaded = await import((0, import_node_url3.pathToFileURL)(configPath).href);
3582
- return toConfigObject(loaded);
3583
- }
3584
- async function findKoraConfigFile(projectRoot) {
3585
- for (const file of CONFIG_CANDIDATES) {
3586
- const candidate = (0, import_node_path17.join)(projectRoot, file);
3587
- try {
3588
- await (0, import_promises12.access)(candidate);
3589
- return candidate;
3590
- } catch {
3591
- }
3592
- }
3593
- return null;
3594
- }
3595
- async function loadTypeScriptConfig(configPath, projectRoot) {
3596
- if (!await hasTsxInstalled(projectRoot)) {
3597
- throw new Error(
3598
- `Found TypeScript config at ${configPath}, but "tsx" is not installed in this project. Install tsx or use kora.config.js.`
3599
- );
3600
- }
3601
- 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) })";
3602
- const output = await runCommand(
3603
- process.execPath,
3604
- ["--import", "tsx", "--eval", script, configPath],
3605
- projectRoot
3606
- );
3607
- try {
3608
- return JSON.parse(output);
3609
- } catch {
3610
- throw new Error(`Failed to parse ${configPath} output as JSON.`);
3611
- }
3612
- }
3613
- async function runCommand(command, args, cwd) {
3614
- return await new Promise((resolve9, reject) => {
3615
- const child = (0, import_node_child_process8.spawn)(command, args, {
3616
- cwd,
3617
- stdio: ["ignore", "pipe", "pipe"],
3618
- env: process.env
3619
- });
3620
- let stdout = "";
3621
- let stderr = "";
3622
- child.stdout?.on("data", (chunk) => {
3623
- stdout += chunk.toString("utf-8");
3624
- });
3625
- child.stderr?.on("data", (chunk) => {
3626
- stderr += chunk.toString("utf-8");
3627
- });
3628
- child.on("error", (error) => {
3629
- reject(error);
3630
- });
3631
- child.on("exit", (code) => {
3632
- if (code === 0) {
3633
- resolve9(stdout.trim());
3634
- return;
3635
- }
3636
- reject(new Error(`Failed to load kora config (exit ${code ?? "unknown"}): ${stderr.trim()}`));
3637
- });
3638
- });
3639
- }
3640
- function toConfigObject(mod) {
3641
- if (typeof mod !== "object" || mod === null) {
3642
- throw new Error("kora config must export an object.");
3643
- }
3644
- const value = mod.default ?? mod;
3645
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
3646
- throw new Error("kora config must export an object.");
3647
- }
3648
- return value;
3649
- }
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");
3650
3987
 
3651
3988
  // src/commands/dev/process-manager.ts
3652
- var import_node_child_process9 = require("child_process");
3989
+ var import_node_child_process10 = require("child_process");
3653
3990
  var ProcessManager = class {
3654
3991
  processes = /* @__PURE__ */ new Map();
3655
3992
  spawn(config) {
@@ -3658,12 +3995,12 @@ var ProcessManager = class {
3658
3995
  env: { ...process.env, ...config.env },
3659
3996
  stdio: ["ignore", "pipe", "pipe"]
3660
3997
  };
3661
- 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);
3662
3999
  let resolveExit;
3663
4000
  const runningProcess = {
3664
4001
  child,
3665
- exitPromise: new Promise((resolve9) => {
3666
- resolveExit = resolve9;
4002
+ exitPromise: new Promise((resolve11) => {
4003
+ resolveExit = resolve11;
3667
4004
  }),
3668
4005
  stdoutBuffer: "",
3669
4006
  stderrBuffer: ""
@@ -3730,15 +4067,15 @@ var ProcessManager = class {
3730
4067
  }
3731
4068
  };
3732
4069
  function delay(ms) {
3733
- return new Promise((resolve9) => {
3734
- setTimeout(resolve9, ms);
4070
+ return new Promise((resolve11) => {
4071
+ setTimeout(resolve11, ms);
3735
4072
  });
3736
4073
  }
3737
4074
 
3738
4075
  // src/commands/dev/schema-watcher.ts
3739
- var import_node_child_process10 = require("child_process");
3740
- var import_node_fs4 = require("fs");
3741
- 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");
3742
4079
  var SchemaWatcher = class {
3743
4080
  constructor(config) {
3744
4081
  this.config = config;
@@ -3750,7 +4087,7 @@ var SchemaWatcher = class {
3750
4087
  debounceTimer = null;
3751
4088
  start() {
3752
4089
  if (this.watcher) return;
3753
- this.watcher = (0, import_node_fs4.watch)(this.config.schemaPath, () => {
4090
+ this.watcher = (0, import_node_fs5.watch)(this.config.schemaPath, () => {
3754
4091
  this.scheduleRegeneration();
3755
4092
  });
3756
4093
  this.watcher.on("error", (error) => {
@@ -3766,7 +4103,7 @@ var SchemaWatcher = class {
3766
4103
  this.watcher = null;
3767
4104
  }
3768
4105
  async regenerate() {
3769
- const koraBinJs = (0, import_node_path18.join)(
4106
+ const koraBinJs = (0, import_node_path21.join)(
3770
4107
  this.config.projectRoot,
3771
4108
  "node_modules",
3772
4109
  "@korajs",
@@ -3793,8 +4130,8 @@ var SchemaWatcher = class {
3793
4130
  }
3794
4131
  };
3795
4132
  async function spawnCommand(command, args, cwd) {
3796
- await new Promise((resolve9, reject) => {
3797
- 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, {
3798
4135
  cwd,
3799
4136
  stdio: ["ignore", "pipe", "pipe"],
3800
4137
  env: process.env
@@ -3810,7 +4147,7 @@ async function spawnCommand(command, args, cwd) {
3810
4147
  });
3811
4148
  child.on("exit", (code) => {
3812
4149
  if (code === 0) {
3813
- resolve9();
4150
+ resolve11();
3814
4151
  return;
3815
4152
  }
3816
4153
  reject(new Error(`Type generation exited with code ${code ?? "unknown"}.`));
@@ -3832,7 +4169,7 @@ function toError(error) {
3832
4169
  }
3833
4170
 
3834
4171
  // src/commands/dev/dev-command.ts
3835
- var devCommand = (0, import_citty3.defineCommand)({
4172
+ var devCommand = (0, import_citty5.defineCommand)({
3836
4173
  meta: {
3837
4174
  name: "dev",
3838
4175
  description: "Start the Kora development environment"
@@ -3872,7 +4209,7 @@ var devCommand = (0, import_citty3.defineCommand)({
3872
4209
  const watchDebounceMs = typeof config?.dev?.watch === "object" && typeof config.dev.watch.debounceMs === "number" ? config.dev.watch.debounceMs : 300;
3873
4210
  const viteEntryPoint = await resolveProjectBinaryEntryPoint(projectRoot, "vite", "vite");
3874
4211
  if (!viteEntryPoint) {
3875
- 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"));
3876
4213
  }
3877
4214
  const syncServerFile = await findSyncServerFile(projectRoot);
3878
4215
  let managedSyncStore = normalizeManagedSyncStore(config, projectRoot);
@@ -3888,7 +4225,7 @@ var devCommand = (0, import_citty3.defineCommand)({
3888
4225
  }
3889
4226
  if (shouldStartSync && syncServerFile === null && managedSyncStore) {
3890
4227
  const hasServerPackage = await fileExists2(
3891
- (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")
3892
4229
  );
3893
4230
  if (!hasServerPackage) {
3894
4231
  logger.warn(
@@ -3905,7 +4242,7 @@ var devCommand = (0, import_citty3.defineCommand)({
3905
4242
  }
3906
4243
  let configuredSchemaPath = null;
3907
4244
  if (typeof config?.schema === "string") {
3908
- const candidate = (0, import_node_path20.resolve)(projectRoot, config.schema);
4245
+ const candidate = (0, import_node_path23.resolve)(projectRoot, config.schema);
3909
4246
  if (await fileExists2(candidate)) {
3910
4247
  configuredSchemaPath = candidate;
3911
4248
  } else {
@@ -3920,8 +4257,8 @@ var devCommand = (0, import_citty3.defineCommand)({
3920
4257
  let schemaWatcher = null;
3921
4258
  let shuttingDown = false;
3922
4259
  let resolveFinished;
3923
- const finished = new Promise((resolve9) => {
3924
- resolveFinished = resolve9;
4260
+ const finished = new Promise((resolve11) => {
4261
+ resolveFinished = resolve11;
3925
4262
  });
3926
4263
  const onManagedProcessExit = () => {
3927
4264
  if (!processManager.hasRunning() && !shuttingDown) {
@@ -3964,11 +4301,15 @@ var devCommand = (0, import_citty3.defineCommand)({
3964
4301
  logger.step(" Schema watcher disabled (schema.ts not found)");
3965
4302
  }
3966
4303
  logger.blank();
4304
+ logger.step(" Kora DevTools overlay: Ctrl+Shift+K (Cmd+Shift+K on macOS) when devtools: true");
3967
4305
  processManager.spawn({
3968
4306
  label: "vite",
3969
4307
  command: process.execPath,
3970
4308
  args: [viteEntryPoint, "--port", String(vitePort)],
3971
4309
  cwd: projectRoot,
4310
+ env: {
4311
+ KORA_DEV: "1"
4312
+ },
3972
4313
  onExit: onManagedProcessExit
3973
4314
  });
3974
4315
  if (shouldStartSync && hasTsx && syncServerFile) {
@@ -4019,10 +4360,10 @@ var devCommand = (0, import_citty3.defineCommand)({
4019
4360
  }
4020
4361
  });
4021
4362
  async function findSyncServerFile(projectRoot) {
4022
- 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")];
4023
4364
  for (const candidate of candidates) {
4024
4365
  try {
4025
- await (0, import_promises13.access)(candidate);
4366
+ await (0, import_promises14.access)(candidate);
4026
4367
  return candidate;
4027
4368
  } catch {
4028
4369
  }
@@ -4031,7 +4372,7 @@ async function findSyncServerFile(projectRoot) {
4031
4372
  }
4032
4373
  async function fileExists2(path) {
4033
4374
  try {
4034
- await (0, import_promises13.access)(path);
4375
+ await (0, import_promises14.access)(path);
4035
4376
  return true;
4036
4377
  } catch {
4037
4378
  return false;
@@ -4043,7 +4384,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
4043
4384
  const store = sync.store;
4044
4385
  if (store === void 0) return { type: "memory" };
4045
4386
  if (store === "memory") return { type: "memory" };
4046
- 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") };
4047
4388
  if (store === "postgres") {
4048
4389
  const connectionString = process.env.DATABASE_URL;
4049
4390
  if (!connectionString) return null;
@@ -4052,7 +4393,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
4052
4393
  if (typeof store === "object" && store !== null) {
4053
4394
  if (store.type === "memory") return { type: "memory" };
4054
4395
  if (store.type === "sqlite") {
4055
- 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");
4056
4397
  return { type: "sqlite", filename };
4057
4398
  }
4058
4399
  if (store.type === "postgres" && typeof store.connectionString === "string") {
@@ -4090,35 +4431,403 @@ if (storeConfig.type === 'memory') {
4090
4431
  if (typeof storeConfig.connectionString !== 'string' || storeConfig.connectionString.length === 0) {
4091
4432
  throw new Error('Managed postgres sync requires a connectionString');
4092
4433
  }
4093
- store = await createPostgresServerStore({
4094
- connectionString: storeConfig.connectionString,
4095
- });
4096
- } else {
4097
- 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
+ }
4098
4714
  }
4099
- const server = createKoraServer({ store, port: Number(config.port ?? 3001) });
4100
- const shutdown = async () => {
4101
- try {
4102
- await server.stop();
4103
- } 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
+ }
4104
4771
  }
4105
- process.exit(0);
4106
- };
4107
- process.on('SIGINT', () => {
4108
- void shutdown();
4109
- });
4110
- process.on('SIGTERM', () => {
4111
- void shutdown();
4112
4772
  });
4113
- await server.start();
4114
- process.stdout.write('Managed sync server running on ws://localhost:' + String(config.port ?? 3001) + '\\n');
4115
- await new Promise(() => {});
4116
- `;
4117
4773
 
4118
4774
  // src/commands/generate/generate-command.ts
4119
- var import_promises14 = require("fs/promises");
4120
- var import_node_path21 = require("path");
4121
- 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
+ }
4122
4831
 
4123
4832
  // src/commands/generate/type-generator.ts
4124
4833
  function generateTypes(schema) {
@@ -4132,7 +4841,7 @@ function generateTypes(schema) {
4132
4841
  return lines.join("\n");
4133
4842
  }
4134
4843
  for (const [name, collection] of Object.entries(schema.collections)) {
4135
- const pascal = toPascalCase(name);
4844
+ const pascal = toPascalCase2(name);
4136
4845
  const fields = collection.fields;
4137
4846
  lines.push(`export interface ${pascal}Record {`);
4138
4847
  lines.push(" readonly id: string");
@@ -4209,7 +4918,7 @@ function itemKindToTypeScript(kind) {
4209
4918
  return "unknown";
4210
4919
  }
4211
4920
  }
4212
- function toPascalCase(name) {
4921
+ function toPascalCase2(name) {
4213
4922
  return name.split(/[_-]/).map((part) => {
4214
4923
  if (part.length === 0) return "";
4215
4924
  const first = part[0];
@@ -4218,13 +4927,13 @@ function toPascalCase(name) {
4218
4927
  }
4219
4928
 
4220
4929
  // src/commands/generate/generate-command.ts
4221
- var generateCommand = (0, import_citty4.defineCommand)({
4930
+ var generateCommand = (0, import_citty7.defineCommand)({
4222
4931
  meta: {
4223
4932
  name: "generate",
4224
4933
  description: "Generate code from your Kora schema"
4225
4934
  },
4226
4935
  subCommands: {
4227
- types: (0, import_citty4.defineCommand)({
4936
+ types: (0, import_citty7.defineCommand)({
4228
4937
  meta: {
4229
4938
  name: "types",
4230
4939
  description: "Generate TypeScript types from your schema"
@@ -4248,7 +4957,7 @@ var generateCommand = (0, import_citty4.defineCommand)({
4248
4957
  }
4249
4958
  let schemaPath;
4250
4959
  if (args.schema && typeof args.schema === "string") {
4251
- schemaPath = (0, import_node_path21.resolve)(args.schema);
4960
+ schemaPath = (0, import_node_path25.resolve)(args.schema);
4252
4961
  } else {
4253
4962
  const found = await findSchemaFile(projectRoot);
4254
4963
  if (!found) {
@@ -4263,7 +4972,7 @@ var generateCommand = (0, import_citty4.defineCommand)({
4263
4972
  }
4264
4973
  logger.step(`Reading schema from ${schemaPath}...`);
4265
4974
  const schemaModule = await import(schemaPath);
4266
- const schema = extractSchema(schemaModule);
4975
+ const schema = extractSchema2(schemaModule);
4267
4976
  if (!schema) {
4268
4977
  logger.error("Schema file must export a SchemaDefinition as the default export.");
4269
4978
  process.exitCode = 1;
@@ -4271,31 +4980,193 @@ var generateCommand = (0, import_citty4.defineCommand)({
4271
4980
  }
4272
4981
  const output = generateTypes(schema);
4273
4982
  const outputFile = typeof args.output === "string" ? args.output : "kora/generated/types.ts";
4274
- const outputPath = (0, import_node_path21.resolve)(projectRoot, outputFile);
4275
- await (0, import_promises14.mkdir)((0, import_node_path21.dirname)(outputPath), { recursive: true });
4276
- 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");
4277
4986
  logger.success(`Generated types at ${outputPath}`);
4278
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
+ }
4279
5043
  })
4280
5044
  }
4281
5045
  });
4282
- function extractSchema(mod) {
5046
+ function extractSchema2(mod) {
4283
5047
  if (typeof mod !== "object" || mod === null) return null;
4284
5048
  const record = mod;
4285
5049
  const candidate = record.default ?? record;
4286
- if (isSchemaDefinition(candidate)) return candidate;
5050
+ if (isSchemaDefinition2(candidate)) return candidate;
4287
5051
  return null;
4288
5052
  }
4289
- function isSchemaDefinition(value) {
5053
+ function isSchemaDefinition2(value) {
4290
5054
  if (typeof value !== "object" || value === null) return false;
4291
5055
  const obj = value;
4292
5056
  return typeof obj.version === "number" && typeof obj.collections === "object" && obj.collections !== null;
4293
5057
  }
4294
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
+
4295
5166
  // src/commands/migrate/migrate-command.ts
4296
- var import_promises15 = require("fs/promises");
4297
- var import_node_path23 = require("path");
4298
- 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");
4299
5170
 
4300
5171
  // src/commands/migrate/migration-generator.ts
4301
5172
  var import_core2 = require("@korajs/core");
@@ -4861,83 +5732,34 @@ async function loadPostgresModule() {
4861
5732
  }
4862
5733
  }
4863
5734
 
4864
- // src/commands/migrate/schema-loader.ts
4865
- var import_node_child_process11 = require("child_process");
4866
- var import_node_path22 = require("path");
4867
- var import_node_url4 = require("url");
4868
- async function loadSchemaDefinition(schemaPath, projectRoot) {
4869
- const ext = (0, import_node_path22.extname)(schemaPath);
4870
- 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()}`);
4871
- return extractSchema2(moduleValue);
4872
- }
4873
- function extractSchema2(value) {
4874
- if (typeof value !== "object" || value === null) {
4875
- throw new Error("Schema module must export an object.");
4876
- }
4877
- const moduleRecord = value;
4878
- const candidate = moduleRecord.default ?? moduleRecord;
4879
- if (!isSchemaDefinition2(candidate)) {
4880
- throw new Error("Schema module must export a valid SchemaDefinition as default export.");
4881
- }
4882
- return candidate;
4883
- }
4884
- function isSchemaDefinition2(value) {
4885
- if (typeof value !== "object" || value === null) return false;
4886
- const object = value;
4887
- return typeof object.version === "number" && typeof object.collections === "object" && object.collections !== null && typeof object.relations === "object" && object.relations !== null;
4888
- }
4889
- async function loadTypeScriptModule(schemaPath, projectRoot) {
4890
- if (!await hasTsxInstalled(projectRoot)) {
4891
- throw new Error(
4892
- `Schema file is TypeScript (${schemaPath}) but local "tsx" was not found. Install tsx in the project.`
4893
- );
4894
- }
4895
- 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) })";
4896
- const output = await runCommand2(
4897
- process.execPath,
4898
- ["--import", "tsx", "--eval", script, schemaPath],
4899
- projectRoot
4900
- );
4901
- try {
4902
- return JSON.parse(output);
4903
- } catch {
4904
- throw new Error(`Failed to parse schema module output for ${schemaPath}`);
4905
- }
4906
- }
4907
- async function runCommand2(command, args, cwd) {
4908
- return await new Promise((resolve9, reject) => {
4909
- const child = (0, import_node_child_process11.spawn)(command, args, {
4910
- cwd,
4911
- stdio: ["ignore", "pipe", "pipe"],
4912
- env: process.env
4913
- });
4914
- let stdout = "";
4915
- let stderr = "";
4916
- child.stdout?.on("data", (chunk) => {
4917
- stdout += chunk.toString("utf-8");
4918
- });
4919
- child.stderr?.on("data", (chunk) => {
4920
- stderr += chunk.toString("utf-8");
4921
- });
4922
- child.on("error", (error) => {
4923
- reject(error);
4924
- });
4925
- child.on("exit", (code) => {
4926
- if (code === 0) {
4927
- resolve9(stdout.trim());
4928
- return;
4929
- }
4930
- reject(
4931
- new Error(`Failed to load TypeScript schema (exit ${code ?? "unknown"}): ${stderr.trim()}`)
4932
- );
4933
- });
4934
- });
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");
4935
5757
  }
4936
5758
 
4937
5759
  // src/commands/migrate/migrate-command.ts
4938
5760
  var SNAPSHOT_PATH = "kora/schema.snapshot.json";
4939
5761
  var MIGRATIONS_DIR = "kora/migrations";
4940
- var migrateCommand = (0, import_citty5.defineCommand)({
5762
+ var migrateCommand = (0, import_citty9.defineCommand)({
4941
5763
  meta: {
4942
5764
  name: "migrate",
4943
5765
  description: "Detect schema changes and generate/apply migrations"
@@ -4979,12 +5801,12 @@ var migrateCommand = (0, import_citty5.defineCommand)({
4979
5801
  throw new InvalidProjectError(process.cwd());
4980
5802
  }
4981
5803
  const config = await loadKoraConfig(projectRoot);
4982
- 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);
4983
5805
  if (!resolvedSchemaPath) {
4984
5806
  throw new SchemaNotFoundError(["src/schema.ts", "schema.ts", "src/schema.js", "schema.js"]);
4985
5807
  }
4986
5808
  const currentSchema = await loadSchemaDefinition(resolvedSchemaPath, projectRoot);
4987
- const snapshotFile = (0, import_node_path23.join)(projectRoot, SNAPSHOT_PATH);
5809
+ const snapshotFile = (0, import_node_path26.join)(projectRoot, SNAPSHOT_PATH);
4988
5810
  const previousSchema = await readSchemaSnapshot(snapshotFile);
4989
5811
  if (!previousSchema) {
4990
5812
  if (args["dry-run"] === true) {
@@ -4997,7 +5819,7 @@ var migrateCommand = (0, import_citty5.defineCommand)({
4997
5819
  return;
4998
5820
  }
4999
5821
  const diff = diffSchemas(previousSchema, currentSchema);
5000
- 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);
5001
5823
  if (!diff.hasChanges) {
5002
5824
  logger.success("No schema changes detected.");
5003
5825
  if (args.apply === true) {
@@ -5049,7 +5871,7 @@ var migrateCommand = (0, import_citty5.defineCommand)({
5049
5871
  logger.warn("Dry run enabled: no files written, no migrations applied.");
5050
5872
  return;
5051
5873
  }
5052
- await (0, import_promises15.mkdir)(outputDir, { recursive: true });
5874
+ await (0, import_promises17.mkdir)(outputDir, { recursive: true });
5053
5875
  const migrationPath = await writeMigrationFile(
5054
5876
  outputDir,
5055
5877
  diff.fromVersion,
@@ -5085,7 +5907,7 @@ var migrateCommand = (0, import_citty5.defineCommand)({
5085
5907
  });
5086
5908
  async function readSchemaSnapshot(path) {
5087
5909
  try {
5088
- const content = await (0, import_promises15.readFile)(path, "utf-8");
5910
+ const content = await (0, import_promises17.readFile)(path, "utf-8");
5089
5911
  return JSON.parse(content);
5090
5912
  } catch {
5091
5913
  return null;
@@ -5106,15 +5928,15 @@ function isInteractiveTerminal2() {
5106
5928
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
5107
5929
  }
5108
5930
  async function writeSchemaSnapshot(path, schema) {
5109
- await (0, import_promises15.mkdir)((0, import_node_path23.dirname)(path), { recursive: true });
5110
- 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)}
5111
5933
  `, "utf-8");
5112
5934
  }
5113
5935
  async function writeMigrationFile(outputDir, fromVersion, toVersion, generated) {
5114
- const existing = await (0, import_promises15.readdir)(outputDir).catch(() => []);
5936
+ const existing = await (0, import_promises17.readdir)(outputDir).catch(() => []);
5115
5937
  const sequence = existing.filter((file) => /^\d{3}-/.test(file)).length + 1;
5116
5938
  const filename = `${String(sequence).padStart(3, "0")}-v${fromVersion}-to-v${toVersion}.ts`;
5117
- const path = (0, import_node_path23.join)(outputDir, filename);
5939
+ const path = (0, import_node_path26.join)(outputDir, filename);
5118
5940
  const migrationId = filename.replace(/\.ts$/, "");
5119
5941
  const fileContent = [
5120
5942
  `export const up = ${JSON.stringify(generated.up, null, 2)} as const`,
@@ -5126,8 +5948,10 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
5126
5948
  `export const containsBreakingChanges = ${generated.containsBreakingChanges}`,
5127
5949
  ""
5128
5950
  ].join("\n");
5129
- await (0, import_promises15.writeFile)(path, fileContent, "utf-8");
5130
- 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`), {
5131
5955
  id: migrationId,
5132
5956
  fromVersion,
5133
5957
  toVersion,
@@ -5139,22 +5963,22 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
5139
5963
  return path;
5140
5964
  }
5141
5965
  async function writeMigrationManifest(path, manifest) {
5142
- await (0, import_promises15.writeFile)(path, `${JSON.stringify(manifest, null, 2)}
5966
+ await (0, import_promises17.writeFile)(path, `${JSON.stringify(manifest, null, 2)}
5143
5967
  `, "utf-8");
5144
5968
  }
5145
5969
  async function listMigrationManifests(outputDir) {
5146
- const files = await (0, import_promises15.readdir)(outputDir).catch(() => []);
5970
+ const files = await (0, import_promises17.readdir)(outputDir).catch(() => []);
5147
5971
  const migrationFiles = files.filter((file) => /^\d{3}-.*\.ts$/.test(file)).sort((left, right) => left.localeCompare(right));
5148
5972
  const manifests = [];
5149
5973
  for (const file of migrationFiles) {
5150
5974
  const id = file.replace(/\.ts$/, "");
5151
- const manifestPath = (0, import_node_path23.join)(outputDir, `${id}.json`);
5975
+ const manifestPath = (0, import_node_path26.join)(outputDir, `${id}.json`);
5152
5976
  const jsonManifest = await readMigrationManifest(manifestPath);
5153
5977
  if (jsonManifest) {
5154
5978
  manifests.push({ ...jsonManifest, id });
5155
5979
  continue;
5156
5980
  }
5157
- const migrationPath = (0, import_node_path23.join)(outputDir, file);
5981
+ const migrationPath = (0, import_node_path26.join)(outputDir, file);
5158
5982
  const sourceManifest = await readMigrationManifestFromSource(migrationPath, id);
5159
5983
  manifests.push(sourceManifest);
5160
5984
  }
@@ -5162,7 +5986,7 @@ async function listMigrationManifests(outputDir) {
5162
5986
  }
5163
5987
  async function readMigrationManifest(path) {
5164
5988
  try {
5165
- const content = await (0, import_promises15.readFile)(path, "utf-8");
5989
+ const content = await (0, import_promises17.readFile)(path, "utf-8");
5166
5990
  return JSON.parse(content);
5167
5991
  } catch (error) {
5168
5992
  const code = error.code;
@@ -5173,7 +5997,7 @@ async function readMigrationManifest(path) {
5173
5997
  }
5174
5998
  }
5175
5999
  async function readMigrationManifestFromSource(path, id) {
5176
- const content = await (0, import_promises15.readFile)(path, "utf-8");
6000
+ const content = await (0, import_promises17.readFile)(path, "utf-8");
5177
6001
  const versions = parseVersionsFromMigrationId(id);
5178
6002
  return {
5179
6003
  id,
@@ -5190,9 +6014,14 @@ function parseVersionsFromMigrationId(id) {
5190
6014
  if (!match) {
5191
6015
  throw new Error(`Migration id "${id}" does not include a vX-to-vY version suffix.`);
5192
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
+ }
5193
6022
  return {
5194
- fromVersion: Number.parseInt(match[1], 10),
5195
- toVersion: Number.parseInt(match[2], 10)
6023
+ fromVersion: Number.parseInt(fromVersionText, 10),
6024
+ toVersion: Number.parseInt(toVersionText, 10)
5196
6025
  };
5197
6026
  }
5198
6027
  function parseStringArrayExport(source, exportName) {
@@ -5230,18 +6059,18 @@ function parseLiteralExport(source, exportName) {
5230
6059
  }
5231
6060
  function resolveSqliteApplyPath(dbArg, projectRoot, config) {
5232
6061
  if (typeof dbArg === "string") {
5233
- return (0, import_node_path23.resolve)(projectRoot, dbArg);
6062
+ return (0, import_node_path26.resolve)(projectRoot, dbArg);
5234
6063
  }
5235
6064
  const sync = config?.dev?.sync;
5236
6065
  if (typeof sync === "object" && sync !== null) {
5237
6066
  if (sync.store === "sqlite") {
5238
- return (0, import_node_path23.join)(projectRoot, "kora-sync.db");
6067
+ return (0, import_node_path26.join)(projectRoot, "kora-sync.db");
5239
6068
  }
5240
6069
  if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "sqlite") {
5241
6070
  if (typeof sync.store.filename === "string" && sync.store.filename.length > 0) {
5242
- return (0, import_node_path23.resolve)(projectRoot, sync.store.filename);
6071
+ return (0, import_node_path26.resolve)(projectRoot, sync.store.filename);
5243
6072
  }
5244
- return (0, import_node_path23.join)(projectRoot, "kora-sync.db");
6073
+ return (0, import_node_path26.join)(projectRoot, "kora-sync.db");
5245
6074
  }
5246
6075
  }
5247
6076
  return void 0;
@@ -5258,19 +6087,156 @@ function resolvePostgresConnectionString(config) {
5258
6087
  return void 0;
5259
6088
  }
5260
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
+
5261
6222
  // src/bin.ts
5262
- var main = (0, import_citty6.defineCommand)({
6223
+ var main = (0, import_citty11.defineCommand)({
5263
6224
  meta: {
5264
6225
  name: "kora",
5265
6226
  description: "Kora.js \u2014 Offline-first application framework"
5266
6227
  },
5267
6228
  subCommands: {
6229
+ backup: backupCommand,
6230
+ compact: compactCommand,
5268
6231
  create: createCommand,
5269
6232
  dev: devCommand,
6233
+ doctor: doctorCommand,
5270
6234
  deploy: deployCommand,
5271
6235
  generate: generateCommand,
5272
- migrate: migrateCommand
6236
+ logs: logsCommand,
6237
+ migrate: migrateCommand,
6238
+ status: statusCommand
5273
6239
  }
5274
6240
  });
5275
- (0, import_citty6.runMain)(main);
6241
+ (0, import_citty11.runMain)(main);
5276
6242
  //# sourceMappingURL=bin.cjs.map