@korajs/cli 0.3.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +1839 -829
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1107 -146
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-HNVCNPER.js → chunk-3WNFM3QB.js} +107 -97
- package/dist/chunk-3WNFM3QB.js.map +1 -0
- package/dist/{chunk-ULTO5NFA.js → chunk-PSRM56B7.js} +2 -2
- package/dist/chunk-PSRM56B7.js.map +1 -0
- package/dist/{chunk-MIMLCXSX.js → chunk-SOTZIWIF.js} +267 -228
- package/dist/chunk-SOTZIWIF.js.map +1 -0
- package/dist/create.cjs +58 -48
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +2 -2
- package/dist/index.cjs +322 -273
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -29
- package/dist/index.d.ts +31 -29
- package/dist/index.js +2 -2
- package/package.json +9 -3
- package/templates/react-basic/README.md.hbs +10 -1
- package/templates/react-basic/src/App.tsx +112 -126
- package/templates/react-basic/src/index.css +165 -159
- package/templates/react-basic/src/kora-worker.ts +0 -1
- package/templates/react-basic/src/main.tsx +12 -12
- package/templates/react-basic/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/react-basic/src/modules/todos/todo.queries.ts +5 -0
- package/templates/react-basic/src/modules/todos/todo.schema.ts +10 -0
- package/templates/react-basic/src/modules/todos/useTodos.ts +31 -0
- package/templates/react-basic/src/schema.ts +6 -12
- package/templates/react-basic/src/vite-env.d.ts +17 -0
- package/templates/react-basic/{tsconfig.json → tsconfig.json.hbs} +1 -2
- package/templates/react-basic/vite.config.ts +57 -50
- package/templates/react-sync/.env.example +19 -0
- package/templates/react-sync/README.md.hbs +43 -1
- package/templates/react-sync/package.json.hbs +3 -0
- package/templates/react-sync/server.ts +90 -26
- package/templates/react-sync/src/App.tsx +132 -130
- package/templates/react-sync/src/auth.ts +25 -0
- package/templates/react-sync/src/index.css +212 -165
- package/templates/react-sync/src/kora-worker.ts +0 -1
- package/templates/react-sync/src/main.tsx +34 -18
- package/templates/react-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/react-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/react-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/react-sync/src/modules/todos/useTodos.ts +31 -0
- package/templates/react-sync/src/schema.ts +6 -12
- package/templates/react-sync/src/vite-env.d.ts +17 -0
- package/templates/react-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
- package/templates/react-sync/vite.config.ts +67 -60
- package/templates/react-tailwind/README.md.hbs +10 -1
- package/templates/react-tailwind/src/App.tsx +186 -197
- package/templates/react-tailwind/src/index.css +3 -3
- package/templates/react-tailwind/src/kora-worker.ts +0 -1
- package/templates/react-tailwind/src/main.tsx +19 -12
- package/templates/react-tailwind/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/react-tailwind/src/modules/todos/todo.queries.ts +5 -0
- package/templates/react-tailwind/src/modules/todos/todo.schema.ts +10 -0
- package/templates/react-tailwind/src/modules/todos/useTodos.ts +31 -0
- package/templates/react-tailwind/src/schema.ts +6 -12
- package/templates/react-tailwind/src/vite-env.d.ts +17 -0
- package/templates/react-tailwind/{tsconfig.json → tsconfig.json.hbs} +1 -2
- package/templates/react-tailwind/vite.config.ts +57 -50
- package/templates/react-tailwind-sync/.env.example +19 -0
- package/templates/react-tailwind-sync/README.md.hbs +43 -1
- package/templates/react-tailwind-sync/package.json.hbs +3 -0
- package/templates/react-tailwind-sync/server.ts +90 -26
- package/templates/react-tailwind-sync/src/App.tsx +246 -222
- package/templates/react-tailwind-sync/src/auth.ts +25 -0
- package/templates/react-tailwind-sync/src/index.css +3 -3
- package/templates/react-tailwind-sync/src/kora-worker.ts +0 -1
- package/templates/react-tailwind-sync/src/main.tsx +48 -18
- package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +5 -0
- package/templates/react-tailwind-sync/src/modules/todos/todo.schema.ts +10 -0
- package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +31 -0
- package/templates/react-tailwind-sync/src/schema.ts +6 -12
- package/templates/react-tailwind-sync/src/vite-env.d.ts +17 -0
- package/templates/react-tailwind-sync/{tsconfig.json → tsconfig.json.hbs} +1 -2
- package/templates/react-tailwind-sync/vite.config.ts +67 -60
- package/templates/tauri-react/.env.example +20 -0
- package/templates/tauri-react/README.md.hbs +36 -1
- package/templates/tauri-react/dev.ts +11 -11
- package/templates/tauri-react/package.json.hbs +2 -0
- package/templates/tauri-react/server.ts +86 -12
- package/templates/tauri-react/src/App.tsx +769 -544
- package/templates/tauri-react/src/AppShell.tsx +125 -83
- package/templates/tauri-react/src/SetupScreen.tsx +257 -208
- package/templates/tauri-react/src/auth.ts +45 -0
- package/templates/tauri-react/src/main.tsx +3 -3
- package/templates/tauri-react/src/modules/todos/todo.mutations.ts +25 -0
- package/templates/tauri-react/src/modules/todos/todo.queries.ts +5 -0
- package/templates/tauri-react/src/modules/todos/todo.schema.ts +10 -0
- package/templates/tauri-react/src/modules/todos/useTodos.ts +31 -0
- package/templates/tauri-react/src/schema.ts +6 -12
- package/templates/tauri-react/src/sync-config.ts +47 -47
- package/templates/tauri-react/src/updater.ts +15 -15
- package/templates/tauri-react/src/vite-env.d.ts +17 -0
- package/templates/tauri-react/src-tauri/tauri.conf.json +2 -2
- package/templates/tauri-react/tsconfig.json.hbs +14 -0
- package/templates/tauri-react/vite.config.ts +9 -9
- package/dist/chunk-HNVCNPER.js.map +0 -1
- package/dist/chunk-MIMLCXSX.js.map +0 -1
- package/dist/chunk-ULTO5NFA.js.map +0 -1
- package/templates/tauri-react/tsconfig.json +0 -15
package/dist/bin.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createCommand
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PSRM56B7.js";
|
|
5
5
|
import {
|
|
6
6
|
deployCommand,
|
|
7
7
|
generateTypes
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SOTZIWIF.js";
|
|
9
9
|
import {
|
|
10
10
|
DevServerError,
|
|
11
11
|
InvalidProjectError,
|
|
@@ -16,21 +16,187 @@ import {
|
|
|
16
16
|
hasTsxInstalled,
|
|
17
17
|
promptConfirm,
|
|
18
18
|
resolveProjectBinaryEntryPoint
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-3WNFM3QB.js";
|
|
20
20
|
|
|
21
21
|
// src/bin.ts
|
|
22
|
-
import { defineCommand as
|
|
22
|
+
import { defineCommand as defineCommand9, runMain } from "citty";
|
|
23
23
|
|
|
24
|
-
// src/commands/
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
import { resolve } from "path";
|
|
24
|
+
// src/commands/backup/backup-command.ts
|
|
25
|
+
import { readFile, writeFile } from "fs/promises";
|
|
26
|
+
import "path";
|
|
28
27
|
import { defineCommand } from "citty";
|
|
28
|
+
var DEFAULT_SYNC_PORT = 3001;
|
|
29
|
+
var backupCommand = defineCommand({
|
|
30
|
+
meta: {
|
|
31
|
+
name: "backup",
|
|
32
|
+
description: "Backup and restore Kora sync server data"
|
|
33
|
+
},
|
|
34
|
+
subCommands: {
|
|
35
|
+
create: defineCommand({
|
|
36
|
+
meta: {
|
|
37
|
+
name: "create",
|
|
38
|
+
description: "Create a backup of the sync server"
|
|
39
|
+
},
|
|
40
|
+
args: {
|
|
41
|
+
url: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "Sync server URL (default: http://localhost:3001)",
|
|
44
|
+
default: `http://localhost:${DEFAULT_SYNC_PORT}`
|
|
45
|
+
},
|
|
46
|
+
out: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Output file path (default: kora-backup-<timestamp>.kora)"
|
|
49
|
+
},
|
|
50
|
+
token: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description: "Backup token (defaults to KORA_BACKUP_TOKEN or KORA_ADMIN_TOKEN)"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
async run({ args }) {
|
|
56
|
+
const logger = createLogger();
|
|
57
|
+
const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT}`;
|
|
58
|
+
const outFile = typeof args.out === "string" ? args.out : `kora-backup-${Date.now()}.kora`;
|
|
59
|
+
const token = typeof args.token === "string" ? args.token : process.env.KORA_BACKUP_TOKEN ?? process.env.KORA_ADMIN_TOKEN;
|
|
60
|
+
logger.banner();
|
|
61
|
+
logger.info(`Exporting backup from ${url}...`);
|
|
62
|
+
try {
|
|
63
|
+
const backupUrl = `${url.replace(/\/$/, "")}/__kora/backup/export`;
|
|
64
|
+
const response = await fetch(backupUrl, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: token ? { Authorization: `Bearer ${token}` } : void 0
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Backup failed: ${error.message ?? response.statusText}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const buffer = await response.arrayBuffer();
|
|
75
|
+
await writeFile(outFile, new Uint8Array(buffer));
|
|
76
|
+
const size = (buffer.byteLength / 1024).toFixed(1);
|
|
77
|
+
logger.success(`Backup saved to ${outFile} (${size} KB)`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.error("Backup failed");
|
|
80
|
+
if (error instanceof Error) logger.error(error.message);
|
|
81
|
+
logger.blank();
|
|
82
|
+
logger.step("Make sure the Kora sync server is running.");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}),
|
|
87
|
+
restore: defineCommand({
|
|
88
|
+
meta: {
|
|
89
|
+
name: "restore",
|
|
90
|
+
description: "Restore a backup to the sync server"
|
|
91
|
+
},
|
|
92
|
+
args: {
|
|
93
|
+
file: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: "Backup file path",
|
|
96
|
+
required: true
|
|
97
|
+
},
|
|
98
|
+
url: {
|
|
99
|
+
type: "string",
|
|
100
|
+
description: "Sync server URL (default: http://localhost:3001)",
|
|
101
|
+
default: `http://localhost:${DEFAULT_SYNC_PORT}`
|
|
102
|
+
},
|
|
103
|
+
merge: {
|
|
104
|
+
type: "boolean",
|
|
105
|
+
description: "Merge with existing data instead of replacing",
|
|
106
|
+
default: false
|
|
107
|
+
},
|
|
108
|
+
token: {
|
|
109
|
+
type: "string",
|
|
110
|
+
description: "Backup token (defaults to KORA_BACKUP_TOKEN or KORA_ADMIN_TOKEN)"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
async run({ args }) {
|
|
114
|
+
const logger = createLogger();
|
|
115
|
+
const filePath = typeof args.file === "string" ? args.file : "";
|
|
116
|
+
const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT}`;
|
|
117
|
+
const merge = args.merge === true;
|
|
118
|
+
const token = typeof args.token === "string" ? args.token : process.env.KORA_BACKUP_TOKEN ?? process.env.KORA_ADMIN_TOKEN;
|
|
119
|
+
logger.banner();
|
|
120
|
+
logger.info(`Restoring backup from ${filePath} to ${url}...`);
|
|
121
|
+
try {
|
|
122
|
+
const data = await readFile(filePath);
|
|
123
|
+
const restoreUrl = `${url.replace(/\/$/, "")}/__kora/backup/import?merge=${merge}`;
|
|
124
|
+
const response = await fetch(restoreUrl, {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: token ? { Authorization: `Bearer ${token}` } : void 0,
|
|
127
|
+
body: data
|
|
128
|
+
});
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Restore failed: ${error.message ?? response.statusText}`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
const result = await response.json();
|
|
136
|
+
if (result.success) {
|
|
137
|
+
logger.success(
|
|
138
|
+
`Restored ${result.operationsRestored} operations${result.duration ? ` in ${result.duration}ms` : ""}`
|
|
139
|
+
);
|
|
140
|
+
} else {
|
|
141
|
+
logger.error("Restore completed with errors");
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
logger.error("Restore failed");
|
|
145
|
+
if (error instanceof Error) logger.error(error.message);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}),
|
|
150
|
+
info: defineCommand({
|
|
151
|
+
meta: {
|
|
152
|
+
name: "info",
|
|
153
|
+
description: "Show backup file information"
|
|
154
|
+
},
|
|
155
|
+
args: {
|
|
156
|
+
file: {
|
|
157
|
+
type: "string",
|
|
158
|
+
description: "Backup file path",
|
|
159
|
+
required: true
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
async run({ args }) {
|
|
163
|
+
const logger = createLogger();
|
|
164
|
+
const filePath = typeof args.file === "string" ? args.file : "";
|
|
165
|
+
try {
|
|
166
|
+
const data = await readFile(filePath);
|
|
167
|
+
const { readBackupManifest } = await import("@korajs/store");
|
|
168
|
+
const manifest = readBackupManifest(
|
|
169
|
+
new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
170
|
+
);
|
|
171
|
+
logger.banner();
|
|
172
|
+
logger.info(`Backup: ${filePath}`);
|
|
173
|
+
logger.blank();
|
|
174
|
+
logger.step(`Created: ${new Date(manifest.createdAt).toISOString()}`);
|
|
175
|
+
logger.step(`Node ID: ${manifest.nodeId}`);
|
|
176
|
+
logger.step(`Schema: v${manifest.schemaVersion}`);
|
|
177
|
+
logger.step(`Operations: ${manifest.operationCount.toLocaleString()}`);
|
|
178
|
+
logger.step(`Collections: ${manifest.collections.join(", ") || "(none)"}`);
|
|
179
|
+
logger.step(`Checksum: ${manifest.checksum.slice(0, 16)}...`);
|
|
180
|
+
logger.blank();
|
|
181
|
+
logger.step(`Format version: ${manifest.version}`);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
logger.error("Failed to read backup file");
|
|
184
|
+
if (error instanceof Error) logger.error(error.message);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// src/commands/compact/compact-command.ts
|
|
193
|
+
import { resolve } from "path";
|
|
194
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
29
195
|
|
|
30
196
|
// src/commands/dev/kora-config.ts
|
|
31
197
|
import { spawn } from "child_process";
|
|
32
198
|
import { access } from "fs/promises";
|
|
33
|
-
import { extname, join } from "path";
|
|
199
|
+
import { extname, join as join2 } from "path";
|
|
34
200
|
import { pathToFileURL } from "url";
|
|
35
201
|
var CONFIG_CANDIDATES = [
|
|
36
202
|
"kora.config.ts",
|
|
@@ -53,7 +219,7 @@ async function loadKoraConfig(projectRoot) {
|
|
|
53
219
|
}
|
|
54
220
|
async function findKoraConfigFile(projectRoot) {
|
|
55
221
|
for (const file of CONFIG_CANDIDATES) {
|
|
56
|
-
const candidate =
|
|
222
|
+
const candidate = join2(projectRoot, file);
|
|
57
223
|
try {
|
|
58
224
|
await access(candidate);
|
|
59
225
|
return candidate;
|
|
@@ -81,7 +247,7 @@ async function loadTypeScriptConfig(configPath, projectRoot) {
|
|
|
81
247
|
}
|
|
82
248
|
}
|
|
83
249
|
async function runCommand(command, args, cwd) {
|
|
84
|
-
return await new Promise((
|
|
250
|
+
return await new Promise((resolve6, reject) => {
|
|
85
251
|
const child = spawn(command, args, {
|
|
86
252
|
cwd,
|
|
87
253
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -100,7 +266,7 @@ async function runCommand(command, args, cwd) {
|
|
|
100
266
|
});
|
|
101
267
|
child.on("exit", (code) => {
|
|
102
268
|
if (code === 0) {
|
|
103
|
-
|
|
269
|
+
resolve6(stdout.trim());
|
|
104
270
|
return;
|
|
105
271
|
}
|
|
106
272
|
reject(new Error(`Failed to load kora config (exit ${code ?? "unknown"}): ${stderr.trim()}`));
|
|
@@ -118,6 +284,156 @@ function toConfigObject(mod) {
|
|
|
118
284
|
return value;
|
|
119
285
|
}
|
|
120
286
|
|
|
287
|
+
// src/commands/migrate/schema-loader.ts
|
|
288
|
+
import { spawn as spawn2 } from "child_process";
|
|
289
|
+
import { extname as extname2 } from "path";
|
|
290
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
291
|
+
async function loadSchemaDefinition(schemaPath, projectRoot) {
|
|
292
|
+
const ext = extname2(schemaPath);
|
|
293
|
+
const moduleValue = ext === ".ts" || ext === ".mts" || ext === ".cts" ? await loadTypeScriptModule(schemaPath, projectRoot) : await import(`${pathToFileURL2(schemaPath).href}?t=${Date.now()}-${Math.random()}`);
|
|
294
|
+
return extractSchema(moduleValue);
|
|
295
|
+
}
|
|
296
|
+
function extractSchema(value) {
|
|
297
|
+
if (typeof value !== "object" || value === null) {
|
|
298
|
+
throw new Error("Schema module must export an object.");
|
|
299
|
+
}
|
|
300
|
+
const moduleRecord = value;
|
|
301
|
+
const candidate = moduleRecord.default ?? moduleRecord;
|
|
302
|
+
if (!isSchemaDefinition(candidate)) {
|
|
303
|
+
throw new Error("Schema module must export a valid SchemaDefinition as default export.");
|
|
304
|
+
}
|
|
305
|
+
return candidate;
|
|
306
|
+
}
|
|
307
|
+
function isSchemaDefinition(value) {
|
|
308
|
+
if (typeof value !== "object" || value === null) return false;
|
|
309
|
+
const object = value;
|
|
310
|
+
return typeof object.version === "number" && typeof object.collections === "object" && object.collections !== null && typeof object.relations === "object" && object.relations !== null;
|
|
311
|
+
}
|
|
312
|
+
async function loadTypeScriptModule(schemaPath, projectRoot) {
|
|
313
|
+
if (!await hasTsxInstalled(projectRoot)) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
`Schema file is TypeScript (${schemaPath}) but local "tsx" was not found. Install tsx in the project.`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
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) })";
|
|
319
|
+
const output = await runCommand2(
|
|
320
|
+
process.execPath,
|
|
321
|
+
["--import", "tsx", "--eval", script, schemaPath],
|
|
322
|
+
projectRoot
|
|
323
|
+
);
|
|
324
|
+
try {
|
|
325
|
+
return JSON.parse(output);
|
|
326
|
+
} catch {
|
|
327
|
+
throw new Error(`Failed to parse schema module output for ${schemaPath}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async function runCommand2(command, args, cwd) {
|
|
331
|
+
return await new Promise((resolve6, reject) => {
|
|
332
|
+
const child = spawn2(command, args, {
|
|
333
|
+
cwd,
|
|
334
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
335
|
+
env: process.env
|
|
336
|
+
});
|
|
337
|
+
let stdout = "";
|
|
338
|
+
let stderr = "";
|
|
339
|
+
child.stdout?.on("data", (chunk) => {
|
|
340
|
+
stdout += chunk.toString("utf-8");
|
|
341
|
+
});
|
|
342
|
+
child.stderr?.on("data", (chunk) => {
|
|
343
|
+
stderr += chunk.toString("utf-8");
|
|
344
|
+
});
|
|
345
|
+
child.on("error", (error) => {
|
|
346
|
+
reject(error);
|
|
347
|
+
});
|
|
348
|
+
child.on("exit", (code) => {
|
|
349
|
+
if (code === 0) {
|
|
350
|
+
resolve6(stdout.trim());
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
reject(
|
|
354
|
+
new Error(`Failed to load TypeScript schema (exit ${code ?? "unknown"}): ${stderr.trim()}`)
|
|
355
|
+
);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/commands/compact/compact-command.ts
|
|
361
|
+
var compactCommand = defineCommand2({
|
|
362
|
+
meta: {
|
|
363
|
+
name: "compact",
|
|
364
|
+
description: "Compact the local Kora operation log after server ack"
|
|
365
|
+
},
|
|
366
|
+
args: {
|
|
367
|
+
db: {
|
|
368
|
+
type: "string",
|
|
369
|
+
description: "Path to the local SQLite database (required)"
|
|
370
|
+
},
|
|
371
|
+
schema: {
|
|
372
|
+
type: "string",
|
|
373
|
+
description: "Path to schema file"
|
|
374
|
+
},
|
|
375
|
+
strategy: {
|
|
376
|
+
type: "string",
|
|
377
|
+
description: "Compaction strategy: after-ack | after-days | never",
|
|
378
|
+
default: "after-ack"
|
|
379
|
+
},
|
|
380
|
+
days: {
|
|
381
|
+
type: "string",
|
|
382
|
+
description: "Age threshold when strategy is after-days",
|
|
383
|
+
default: "30"
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
async run({ args }) {
|
|
387
|
+
const logger = createLogger();
|
|
388
|
+
const projectRoot = await findProjectRoot();
|
|
389
|
+
if (!projectRoot) {
|
|
390
|
+
throw new InvalidProjectError(process.cwd());
|
|
391
|
+
}
|
|
392
|
+
const dbPath = typeof args.db === "string" ? resolve(projectRoot, args.db) : void 0;
|
|
393
|
+
if (!dbPath) {
|
|
394
|
+
throw new Error("Missing --db <path> to the local SQLite database file.");
|
|
395
|
+
}
|
|
396
|
+
const config = await loadKoraConfig(projectRoot);
|
|
397
|
+
const schemaPath = typeof args.schema === "string" ? resolve(projectRoot, args.schema) : typeof config?.schema === "string" ? resolve(projectRoot, config.schema) : void 0;
|
|
398
|
+
if (!schemaPath) {
|
|
399
|
+
throw new Error("Could not resolve schema path. Pass --schema <path>.");
|
|
400
|
+
}
|
|
401
|
+
const schema = await loadSchemaDefinition(schemaPath, projectRoot);
|
|
402
|
+
const { BetterSqlite3Adapter } = await import("@korajs/store/better-sqlite3");
|
|
403
|
+
const { Store } = await import("@korajs/store");
|
|
404
|
+
const strategyName = typeof args.strategy === "string" ? args.strategy : "after-ack";
|
|
405
|
+
const days = Number.parseInt(typeof args.days === "string" ? args.days : "30", 10);
|
|
406
|
+
const store = new Store({
|
|
407
|
+
schema,
|
|
408
|
+
adapter: new BetterSqlite3Adapter(dbPath)
|
|
409
|
+
});
|
|
410
|
+
await store.open();
|
|
411
|
+
let result;
|
|
412
|
+
if (strategyName === "never") {
|
|
413
|
+
result = await store.compact({ mode: "never" });
|
|
414
|
+
} else if (strategyName === "after-days") {
|
|
415
|
+
result = await store.compact({ mode: "after-days", days });
|
|
416
|
+
} else if (strategyName === "after-ack") {
|
|
417
|
+
result = await store.compact({ mode: "after-ack" });
|
|
418
|
+
} else {
|
|
419
|
+
await store.close();
|
|
420
|
+
throw new Error(`Unknown strategy "${strategyName}". Use after-ack, after-days, or never.`);
|
|
421
|
+
}
|
|
422
|
+
await store.close();
|
|
423
|
+
logger.banner();
|
|
424
|
+
logger.success(`Compacted ${result.deletedCount} operation log entries.`);
|
|
425
|
+
for (const [nodeId, seq] of result.watermark) {
|
|
426
|
+
logger.step(` ${nodeId}: seq <= ${seq}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// src/commands/dev/dev-command.ts
|
|
432
|
+
import { access as access2 } from "fs/promises";
|
|
433
|
+
import { join as join4 } from "path";
|
|
434
|
+
import { resolve as resolve2 } from "path";
|
|
435
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
436
|
+
|
|
121
437
|
// src/commands/dev/process-manager.ts
|
|
122
438
|
import { spawn as spawnChild } from "child_process";
|
|
123
439
|
var ProcessManager = class {
|
|
@@ -132,8 +448,8 @@ var ProcessManager = class {
|
|
|
132
448
|
let resolveExit;
|
|
133
449
|
const runningProcess = {
|
|
134
450
|
child,
|
|
135
|
-
exitPromise: new Promise((
|
|
136
|
-
resolveExit =
|
|
451
|
+
exitPromise: new Promise((resolve6) => {
|
|
452
|
+
resolveExit = resolve6;
|
|
137
453
|
}),
|
|
138
454
|
stdoutBuffer: "",
|
|
139
455
|
stderrBuffer: ""
|
|
@@ -200,15 +516,15 @@ var ProcessManager = class {
|
|
|
200
516
|
}
|
|
201
517
|
};
|
|
202
518
|
function delay(ms) {
|
|
203
|
-
return new Promise((
|
|
204
|
-
setTimeout(
|
|
519
|
+
return new Promise((resolve6) => {
|
|
520
|
+
setTimeout(resolve6, ms);
|
|
205
521
|
});
|
|
206
522
|
}
|
|
207
523
|
|
|
208
524
|
// src/commands/dev/schema-watcher.ts
|
|
209
|
-
import { spawn as
|
|
525
|
+
import { spawn as spawn3 } from "child_process";
|
|
210
526
|
import { watch } from "fs";
|
|
211
|
-
import { join as
|
|
527
|
+
import { join as join3 } from "path";
|
|
212
528
|
var SchemaWatcher = class {
|
|
213
529
|
constructor(config) {
|
|
214
530
|
this.config = config;
|
|
@@ -236,7 +552,14 @@ var SchemaWatcher = class {
|
|
|
236
552
|
this.watcher = null;
|
|
237
553
|
}
|
|
238
554
|
async regenerate() {
|
|
239
|
-
const koraBinJs =
|
|
555
|
+
const koraBinJs = join3(
|
|
556
|
+
this.config.projectRoot,
|
|
557
|
+
"node_modules",
|
|
558
|
+
"@korajs",
|
|
559
|
+
"cli",
|
|
560
|
+
"dist",
|
|
561
|
+
"bin.js"
|
|
562
|
+
);
|
|
240
563
|
const hasTsx = await hasTsxInstalled(this.config.projectRoot);
|
|
241
564
|
const command = process.execPath;
|
|
242
565
|
const args = hasTsx ? ["--import", "tsx", koraBinJs, "generate", "types", "--schema", this.config.schemaPath] : [koraBinJs, "generate", "types", "--schema", this.config.schemaPath];
|
|
@@ -256,8 +579,8 @@ var SchemaWatcher = class {
|
|
|
256
579
|
}
|
|
257
580
|
};
|
|
258
581
|
async function spawnCommand(command, args, cwd) {
|
|
259
|
-
await new Promise((
|
|
260
|
-
const child =
|
|
582
|
+
await new Promise((resolve6, reject) => {
|
|
583
|
+
const child = spawn3(command, args, {
|
|
261
584
|
cwd,
|
|
262
585
|
stdio: ["ignore", "pipe", "pipe"],
|
|
263
586
|
env: process.env
|
|
@@ -273,7 +596,7 @@ async function spawnCommand(command, args, cwd) {
|
|
|
273
596
|
});
|
|
274
597
|
child.on("exit", (code) => {
|
|
275
598
|
if (code === 0) {
|
|
276
|
-
|
|
599
|
+
resolve6();
|
|
277
600
|
return;
|
|
278
601
|
}
|
|
279
602
|
reject(new Error(`Type generation exited with code ${code ?? "unknown"}.`));
|
|
@@ -295,7 +618,7 @@ function toError(error) {
|
|
|
295
618
|
}
|
|
296
619
|
|
|
297
620
|
// src/commands/dev/dev-command.ts
|
|
298
|
-
var devCommand =
|
|
621
|
+
var devCommand = defineCommand3({
|
|
299
622
|
meta: {
|
|
300
623
|
name: "dev",
|
|
301
624
|
description: "Start the Kora development environment"
|
|
@@ -335,7 +658,7 @@ var devCommand = defineCommand({
|
|
|
335
658
|
const watchDebounceMs = typeof config?.dev?.watch === "object" && typeof config.dev.watch.debounceMs === "number" ? config.dev.watch.debounceMs : 300;
|
|
336
659
|
const viteEntryPoint = await resolveProjectBinaryEntryPoint(projectRoot, "vite", "vite");
|
|
337
660
|
if (!viteEntryPoint) {
|
|
338
|
-
throw new DevServerError("vite",
|
|
661
|
+
throw new DevServerError("vite", join4(projectRoot, "node_modules", ".bin", "vite"));
|
|
339
662
|
}
|
|
340
663
|
const syncServerFile = await findSyncServerFile(projectRoot);
|
|
341
664
|
let managedSyncStore = normalizeManagedSyncStore(config, projectRoot);
|
|
@@ -351,7 +674,7 @@ var devCommand = defineCommand({
|
|
|
351
674
|
}
|
|
352
675
|
if (shouldStartSync && syncServerFile === null && managedSyncStore) {
|
|
353
676
|
const hasServerPackage = await fileExists(
|
|
354
|
-
|
|
677
|
+
join4(projectRoot, "node_modules", "@korajs", "server", "package.json")
|
|
355
678
|
);
|
|
356
679
|
if (!hasServerPackage) {
|
|
357
680
|
logger.warn(
|
|
@@ -368,11 +691,13 @@ var devCommand = defineCommand({
|
|
|
368
691
|
}
|
|
369
692
|
let configuredSchemaPath = null;
|
|
370
693
|
if (typeof config?.schema === "string") {
|
|
371
|
-
const candidate =
|
|
694
|
+
const candidate = resolve2(projectRoot, config.schema);
|
|
372
695
|
if (await fileExists(candidate)) {
|
|
373
696
|
configuredSchemaPath = candidate;
|
|
374
697
|
} else {
|
|
375
|
-
logger.warn(
|
|
698
|
+
logger.warn(
|
|
699
|
+
`Configured schema file not found: ${candidate}. Falling back to auto-detection.`
|
|
700
|
+
);
|
|
376
701
|
}
|
|
377
702
|
}
|
|
378
703
|
const schemaPath = configuredSchemaPath ?? await findSchemaFile(projectRoot);
|
|
@@ -381,8 +706,8 @@ var devCommand = defineCommand({
|
|
|
381
706
|
let schemaWatcher = null;
|
|
382
707
|
let shuttingDown = false;
|
|
383
708
|
let resolveFinished;
|
|
384
|
-
const finished = new Promise((
|
|
385
|
-
resolveFinished =
|
|
709
|
+
const finished = new Promise((resolve6) => {
|
|
710
|
+
resolveFinished = resolve6;
|
|
386
711
|
});
|
|
387
712
|
const onManagedProcessExit = () => {
|
|
388
713
|
if (!processManager.hasRunning() && !shuttingDown) {
|
|
@@ -425,11 +750,15 @@ var devCommand = defineCommand({
|
|
|
425
750
|
logger.step(" Schema watcher disabled (schema.ts not found)");
|
|
426
751
|
}
|
|
427
752
|
logger.blank();
|
|
753
|
+
logger.step(" Kora DevTools overlay: Ctrl+Shift+K (Cmd+Shift+K on macOS) when devtools: true");
|
|
428
754
|
processManager.spawn({
|
|
429
755
|
label: "vite",
|
|
430
756
|
command: process.execPath,
|
|
431
757
|
args: [viteEntryPoint, "--port", String(vitePort)],
|
|
432
758
|
cwd: projectRoot,
|
|
759
|
+
env: {
|
|
760
|
+
KORA_DEV: "1"
|
|
761
|
+
},
|
|
433
762
|
onExit: onManagedProcessExit
|
|
434
763
|
});
|
|
435
764
|
if (shouldStartSync && hasTsx && syncServerFile) {
|
|
@@ -480,7 +809,7 @@ var devCommand = defineCommand({
|
|
|
480
809
|
}
|
|
481
810
|
});
|
|
482
811
|
async function findSyncServerFile(projectRoot) {
|
|
483
|
-
const candidates = [
|
|
812
|
+
const candidates = [join4(projectRoot, "server.ts"), join4(projectRoot, "server.js")];
|
|
484
813
|
for (const candidate of candidates) {
|
|
485
814
|
try {
|
|
486
815
|
await access2(candidate);
|
|
@@ -504,7 +833,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
|
|
|
504
833
|
const store = sync.store;
|
|
505
834
|
if (store === void 0) return { type: "memory" };
|
|
506
835
|
if (store === "memory") return { type: "memory" };
|
|
507
|
-
if (store === "sqlite") return { type: "sqlite", filename:
|
|
836
|
+
if (store === "sqlite") return { type: "sqlite", filename: join4(projectRoot, "kora-sync.db") };
|
|
508
837
|
if (store === "postgres") {
|
|
509
838
|
const connectionString = process.env.DATABASE_URL;
|
|
510
839
|
if (!connectionString) return null;
|
|
@@ -513,7 +842,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
|
|
|
513
842
|
if (typeof store === "object" && store !== null) {
|
|
514
843
|
if (store.type === "memory") return { type: "memory" };
|
|
515
844
|
if (store.type === "sqlite") {
|
|
516
|
-
const filename = typeof store.filename === "string" && store.filename.length > 0 ?
|
|
845
|
+
const filename = typeof store.filename === "string" && store.filename.length > 0 ? resolve2(projectRoot, store.filename) : join4(projectRoot, "kora-sync.db");
|
|
517
846
|
return { type: "sqlite", filename };
|
|
518
847
|
}
|
|
519
848
|
if (store.type === "postgres" && typeof store.connectionString === "string") {
|
|
@@ -526,7 +855,8 @@ function isPostgresEnvRequested(config) {
|
|
|
526
855
|
const sync = config?.dev?.sync;
|
|
527
856
|
if (typeof sync !== "object" || sync === null) return false;
|
|
528
857
|
if (sync.store === "postgres") return true;
|
|
529
|
-
if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "postgres")
|
|
858
|
+
if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "postgres")
|
|
859
|
+
return true;
|
|
530
860
|
return false;
|
|
531
861
|
}
|
|
532
862
|
var MANAGED_SYNC_BOOTSTRAP_SCRIPT = `
|
|
@@ -575,17 +905,387 @@ process.stdout.write('Managed sync server running on ws://localhost:' + String(c
|
|
|
575
905
|
await new Promise(() => {});
|
|
576
906
|
`;
|
|
577
907
|
|
|
908
|
+
// src/commands/doctor/doctor-command.ts
|
|
909
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
910
|
+
|
|
911
|
+
// src/commands/doctor/doctor-checks.ts
|
|
912
|
+
import { access as access3, readFile as readFile2 } from "fs/promises";
|
|
913
|
+
import { join as join5, resolve as resolve3 } from "path";
|
|
914
|
+
var STATUS_ENDPOINT = "/__kora/status";
|
|
915
|
+
async function runDoctorChecks(options) {
|
|
916
|
+
const { projectRoot } = options;
|
|
917
|
+
const results = [];
|
|
918
|
+
results.push({
|
|
919
|
+
id: "project-root",
|
|
920
|
+
title: "Project root",
|
|
921
|
+
status: "pass",
|
|
922
|
+
message: projectRoot
|
|
923
|
+
});
|
|
924
|
+
const config = await loadKoraConfig(projectRoot);
|
|
925
|
+
if (!config) {
|
|
926
|
+
results.push({
|
|
927
|
+
id: "kora-config",
|
|
928
|
+
title: "kora.config",
|
|
929
|
+
status: "warn",
|
|
930
|
+
message: "No kora.config.* file found.",
|
|
931
|
+
fix: "Add kora.config.ts with schema path and dev settings (see create-kora-app templates)."
|
|
932
|
+
});
|
|
933
|
+
} else {
|
|
934
|
+
results.push({
|
|
935
|
+
id: "kora-config",
|
|
936
|
+
title: "kora.config",
|
|
937
|
+
status: "pass",
|
|
938
|
+
message: "Configuration file loaded."
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
const schemaPath = await resolveSchemaPath(projectRoot, config?.schema);
|
|
942
|
+
if (!schemaPath) {
|
|
943
|
+
results.push({
|
|
944
|
+
id: "schema",
|
|
945
|
+
title: "Schema",
|
|
946
|
+
status: "fail",
|
|
947
|
+
message: "Could not find schema.ts in src/ or project root.",
|
|
948
|
+
fix: "Add src/schema.ts or set schema in kora.config.ts."
|
|
949
|
+
});
|
|
950
|
+
} else {
|
|
951
|
+
try {
|
|
952
|
+
const schema = await loadSchemaDefinition(schemaPath, projectRoot);
|
|
953
|
+
results.push({
|
|
954
|
+
id: "schema",
|
|
955
|
+
title: "Schema",
|
|
956
|
+
status: "pass",
|
|
957
|
+
message: `Loaded schema v${schema.version} (${Object.keys(schema.collections).length} collection(s)).`
|
|
958
|
+
});
|
|
959
|
+
const workerCheck = await checkWorkerFile(projectRoot);
|
|
960
|
+
results.push(workerCheck);
|
|
961
|
+
const depsCheck = await checkDependencies(projectRoot);
|
|
962
|
+
results.push(depsCheck);
|
|
963
|
+
results.push(opfsBrowserNote());
|
|
964
|
+
if (!options.skipNetwork) {
|
|
965
|
+
const syncUrl = options.syncHttpUrl ?? await resolveSyncHttpUrl(projectRoot, config);
|
|
966
|
+
const syncChecks = await checkSyncServer(syncUrl, schema);
|
|
967
|
+
results.push(...syncChecks);
|
|
968
|
+
} else {
|
|
969
|
+
results.push({
|
|
970
|
+
id: "sync-server",
|
|
971
|
+
title: "Sync server",
|
|
972
|
+
status: "skip",
|
|
973
|
+
message: "Network checks skipped."
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
} catch (error) {
|
|
977
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
978
|
+
results.push({
|
|
979
|
+
id: "schema",
|
|
980
|
+
title: "Schema",
|
|
981
|
+
status: "fail",
|
|
982
|
+
message: `Failed to load schema: ${message}`,
|
|
983
|
+
fix: "Ensure the schema exports defineSchema() as default and TypeScript can be evaluated (install tsx)."
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return results;
|
|
988
|
+
}
|
|
989
|
+
async function resolveSchemaPath(projectRoot, configSchema) {
|
|
990
|
+
if (typeof configSchema === "string") {
|
|
991
|
+
const resolved = resolve3(projectRoot, configSchema);
|
|
992
|
+
try {
|
|
993
|
+
await access3(resolved);
|
|
994
|
+
return resolved;
|
|
995
|
+
} catch {
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return findSchemaFile(projectRoot);
|
|
999
|
+
}
|
|
1000
|
+
async function checkWorkerFile(projectRoot) {
|
|
1001
|
+
const candidates = [
|
|
1002
|
+
join5(projectRoot, "src", "kora-worker.ts"),
|
|
1003
|
+
join5(projectRoot, "src", "kora-worker.js")
|
|
1004
|
+
];
|
|
1005
|
+
for (const path of candidates) {
|
|
1006
|
+
try {
|
|
1007
|
+
await access3(path);
|
|
1008
|
+
return {
|
|
1009
|
+
id: "worker",
|
|
1010
|
+
title: "SQLite WASM worker",
|
|
1011
|
+
status: "pass",
|
|
1012
|
+
message: `Found ${path.replace(projectRoot, ".")}.`
|
|
1013
|
+
};
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return {
|
|
1018
|
+
id: "worker",
|
|
1019
|
+
title: "SQLite WASM worker",
|
|
1020
|
+
status: "warn",
|
|
1021
|
+
message: "No src/kora-worker.ts found.",
|
|
1022
|
+
fix: "Add kora-worker.ts and pass store.workerUrl from Vite (?worker&url) in createApp()."
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
async function checkDependencies(projectRoot) {
|
|
1026
|
+
try {
|
|
1027
|
+
const raw = await readFile2(join5(projectRoot, "package.json"), "utf-8");
|
|
1028
|
+
const pkg = JSON.parse(raw);
|
|
1029
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1030
|
+
const hasKora = Boolean(deps.korajs || deps.kora);
|
|
1031
|
+
const hasStore = Boolean(deps["@korajs/store"]);
|
|
1032
|
+
if (hasKora || hasStore) {
|
|
1033
|
+
return {
|
|
1034
|
+
id: "dependencies",
|
|
1035
|
+
title: "Dependencies",
|
|
1036
|
+
status: "pass",
|
|
1037
|
+
message: hasKora ? "korajs is listed in package.json." : "@korajs/store is listed in package.json."
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
return {
|
|
1041
|
+
id: "dependencies",
|
|
1042
|
+
title: "Dependencies",
|
|
1043
|
+
status: "fail",
|
|
1044
|
+
message: "No korajs or @korajs/store dependency found.",
|
|
1045
|
+
fix: "Run pnpm add korajs @korajs/react (or your package manager equivalent)."
|
|
1046
|
+
};
|
|
1047
|
+
} catch {
|
|
1048
|
+
return {
|
|
1049
|
+
id: "dependencies",
|
|
1050
|
+
title: "Dependencies",
|
|
1051
|
+
status: "fail",
|
|
1052
|
+
message: "Could not read package.json.",
|
|
1053
|
+
fix: "Run this command from a Kora app directory with a valid package.json."
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function opfsBrowserNote() {
|
|
1058
|
+
return {
|
|
1059
|
+
id: "opfs",
|
|
1060
|
+
title: "OPFS persistence",
|
|
1061
|
+
status: "skip",
|
|
1062
|
+
message: "OPFS availability is checked in the browser at runtime. Open the app and confirm no IndexedDB fallback warning in the console.",
|
|
1063
|
+
fix: "Use a Chromium-based browser with storage access; avoid private mode if persistence fails."
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
async function resolveSyncHttpUrl(projectRoot, config) {
|
|
1067
|
+
const envUrl = await readEnvSyncUrl(projectRoot);
|
|
1068
|
+
if (envUrl) {
|
|
1069
|
+
return toSyncStatusHttpBase(envUrl);
|
|
1070
|
+
}
|
|
1071
|
+
const syncPort = typeof config?.dev?.sync === "object" && typeof config.dev.sync.port === "number" ? config.dev.sync.port : 3001;
|
|
1072
|
+
return `http://localhost:${syncPort}`;
|
|
1073
|
+
}
|
|
1074
|
+
async function readEnvSyncUrl(projectRoot) {
|
|
1075
|
+
for (const file of [".env", ".env.local"]) {
|
|
1076
|
+
const path = join5(projectRoot, file);
|
|
1077
|
+
try {
|
|
1078
|
+
const content = await readFile2(path, "utf-8");
|
|
1079
|
+
for (const line of content.split("\n")) {
|
|
1080
|
+
const trimmed = line.trim();
|
|
1081
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1082
|
+
const match = trimmed.match(/^VITE_SYNC_URL=(.+)$/);
|
|
1083
|
+
if (match?.[1]) {
|
|
1084
|
+
return match[1].trim().replace(/^["']|["']$/g, "");
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
} catch {
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
function toSyncStatusHttpBase(url) {
|
|
1093
|
+
try {
|
|
1094
|
+
const parsed = new URL(url);
|
|
1095
|
+
if (parsed.protocol === "ws:") {
|
|
1096
|
+
parsed.protocol = "http:";
|
|
1097
|
+
} else if (parsed.protocol === "wss:") {
|
|
1098
|
+
parsed.protocol = "https:";
|
|
1099
|
+
}
|
|
1100
|
+
parsed.pathname = "";
|
|
1101
|
+
parsed.search = "";
|
|
1102
|
+
parsed.hash = "";
|
|
1103
|
+
return parsed.toString().replace(/\/$/, "");
|
|
1104
|
+
} catch {
|
|
1105
|
+
return "http://localhost:3001";
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
async function checkSyncServer(httpBase, schema) {
|
|
1109
|
+
const statusUrl = `${httpBase.replace(/\/$/, "")}${STATUS_ENDPOINT}`;
|
|
1110
|
+
try {
|
|
1111
|
+
const response = await fetch(statusUrl, {
|
|
1112
|
+
signal: AbortSignal.timeout(5e3)
|
|
1113
|
+
});
|
|
1114
|
+
if (!response.ok) {
|
|
1115
|
+
return [
|
|
1116
|
+
{
|
|
1117
|
+
id: "sync-server",
|
|
1118
|
+
title: "Sync server",
|
|
1119
|
+
status: "fail",
|
|
1120
|
+
message: `GET ${statusUrl} returned ${response.status}.`,
|
|
1121
|
+
fix: "Start the sync server with `kora dev` or `pnpm run dev` in a sync template."
|
|
1122
|
+
}
|
|
1123
|
+
];
|
|
1124
|
+
}
|
|
1125
|
+
const body = await response.json();
|
|
1126
|
+
const results = [
|
|
1127
|
+
{
|
|
1128
|
+
id: "sync-server",
|
|
1129
|
+
title: "Sync server",
|
|
1130
|
+
status: "pass",
|
|
1131
|
+
message: `Reachable at ${statusUrl} (server v${body.version ?? "unknown"}).`
|
|
1132
|
+
}
|
|
1133
|
+
];
|
|
1134
|
+
if (typeof body.schemaVersion === "number" && body.schemaVersion !== schema.version) {
|
|
1135
|
+
results.push({
|
|
1136
|
+
id: "schema-version",
|
|
1137
|
+
title: "Schema version",
|
|
1138
|
+
status: "warn",
|
|
1139
|
+
message: `Client schema v${schema.version} differs from server schema v${body.schemaVersion}.`,
|
|
1140
|
+
fix: "Run migrations on the server and client, or align schema.version before syncing."
|
|
1141
|
+
});
|
|
1142
|
+
} else {
|
|
1143
|
+
results.push({
|
|
1144
|
+
id: "schema-version",
|
|
1145
|
+
title: "Schema version",
|
|
1146
|
+
status: "pass",
|
|
1147
|
+
message: `Client and server both use schema v${schema.version}.`
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
return results;
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1153
|
+
return [
|
|
1154
|
+
{
|
|
1155
|
+
id: "sync-server",
|
|
1156
|
+
title: "Sync server",
|
|
1157
|
+
status: "warn",
|
|
1158
|
+
message: `Could not reach ${statusUrl}: ${detail}`,
|
|
1159
|
+
fix: "Start the sync server, or pass --skip-network for local-only checks."
|
|
1160
|
+
}
|
|
1161
|
+
];
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function doctorHasFailures(results) {
|
|
1165
|
+
return results.some((r) => r.status === "fail");
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/commands/doctor/doctor-command.ts
|
|
1169
|
+
var doctorCommand = defineCommand4({
|
|
1170
|
+
meta: {
|
|
1171
|
+
name: "doctor",
|
|
1172
|
+
description: "Check Kora project setup (schema, worker, sync server, versions)"
|
|
1173
|
+
},
|
|
1174
|
+
args: {
|
|
1175
|
+
url: {
|
|
1176
|
+
type: "string",
|
|
1177
|
+
description: "Sync server HTTP base URL for status probe (default: from .env or localhost:3001)"
|
|
1178
|
+
},
|
|
1179
|
+
"skip-network": {
|
|
1180
|
+
type: "boolean",
|
|
1181
|
+
description: "Skip sync server and schema version network checks",
|
|
1182
|
+
default: false
|
|
1183
|
+
}
|
|
1184
|
+
},
|
|
1185
|
+
async run({ args }) {
|
|
1186
|
+
const logger = createLogger();
|
|
1187
|
+
const projectRoot = await findProjectRoot();
|
|
1188
|
+
if (!projectRoot) {
|
|
1189
|
+
throw new InvalidProjectError(process.cwd());
|
|
1190
|
+
}
|
|
1191
|
+
const syncHttpUrl = typeof args.url === "string" ? args.url : void 0;
|
|
1192
|
+
const skipNetwork = args["skip-network"] === true;
|
|
1193
|
+
logger.banner();
|
|
1194
|
+
logger.info(`Checking project at ${projectRoot}`);
|
|
1195
|
+
logger.blank();
|
|
1196
|
+
const results = await runDoctorChecks({
|
|
1197
|
+
projectRoot,
|
|
1198
|
+
syncHttpUrl,
|
|
1199
|
+
skipNetwork
|
|
1200
|
+
});
|
|
1201
|
+
for (const check of results) {
|
|
1202
|
+
const prefix = check.status === "pass" ? "success" : check.status === "warn" ? "warn" : check.status === "fail" ? "error" : "step";
|
|
1203
|
+
logger[prefix](`${check.title}: ${check.message}`);
|
|
1204
|
+
if (check.fix && check.status !== "pass" && check.status !== "skip") {
|
|
1205
|
+
logger.step(` Fix: ${check.fix}`);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
logger.blank();
|
|
1209
|
+
if (doctorHasFailures(results)) {
|
|
1210
|
+
logger.error("Doctor found blocking issues.");
|
|
1211
|
+
process.exitCode = 1;
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
const warnings = results.filter((r) => r.status === "warn").length;
|
|
1215
|
+
if (warnings > 0) {
|
|
1216
|
+
logger.warn(`Doctor finished with ${warnings} warning(s).`);
|
|
1217
|
+
} else {
|
|
1218
|
+
logger.success("All checks passed.");
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
|
|
578
1223
|
// src/commands/generate/generate-command.ts
|
|
579
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
580
|
-
import { dirname, resolve as
|
|
581
|
-
import { defineCommand as
|
|
582
|
-
|
|
1224
|
+
import { mkdir, writeFile as writeFile2 } from "fs/promises";
|
|
1225
|
+
import { dirname, resolve as resolve4 } from "path";
|
|
1226
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
1227
|
+
|
|
1228
|
+
// src/commands/generate/hook-generator.ts
|
|
1229
|
+
function generateCollectionHooks(schema) {
|
|
1230
|
+
const files = /* @__PURE__ */ new Map();
|
|
1231
|
+
for (const collectionName of Object.keys(schema.collections)) {
|
|
1232
|
+
const pascal = toPascalCase(collectionName);
|
|
1233
|
+
const hookName = `use${pascal}`;
|
|
1234
|
+
const content = [
|
|
1235
|
+
"// Auto-generated by `kora generate hooks` \u2014 safe to edit",
|
|
1236
|
+
`// Schema v${String(schema.version)}`,
|
|
1237
|
+
"",
|
|
1238
|
+
"import { useCollection, useMutation, useQuery } from '@korajs/react'",
|
|
1239
|
+
`import type { ${pascal}InsertInput, ${pascal}Record, ${pascal}UpdateInput } from '../types'`,
|
|
1240
|
+
"",
|
|
1241
|
+
"/**",
|
|
1242
|
+
` * Collection hook for \`${collectionName}\`.`,
|
|
1243
|
+
" * Add useQuery(...) calls with your QueryBuilder factories.",
|
|
1244
|
+
" */",
|
|
1245
|
+
`export function ${hookName}() {`,
|
|
1246
|
+
` const ${collectionName} = useCollection('${collectionName}')`,
|
|
1247
|
+
"",
|
|
1248
|
+
` const insert = useMutation((data: ${pascal}InsertInput) => ${collectionName}.insert(data))`,
|
|
1249
|
+
` const update = useMutation((id: string, data: ${pascal}UpdateInput) =>`,
|
|
1250
|
+
` ${collectionName}.update(id, data),`,
|
|
1251
|
+
" )",
|
|
1252
|
+
` const remove = useMutation((id: string) => ${collectionName}.delete(id))`,
|
|
1253
|
+
"",
|
|
1254
|
+
" return {",
|
|
1255
|
+
` ${collectionName},`,
|
|
1256
|
+
" insert,",
|
|
1257
|
+
" update,",
|
|
1258
|
+
" remove,",
|
|
1259
|
+
" }",
|
|
1260
|
+
"}",
|
|
1261
|
+
""
|
|
1262
|
+
].join("\n");
|
|
1263
|
+
files.set(`${collectionName}.ts`, content);
|
|
1264
|
+
}
|
|
1265
|
+
const indexLines = [
|
|
1266
|
+
"// Auto-generated by `kora generate hooks`",
|
|
1267
|
+
"",
|
|
1268
|
+
...Object.keys(schema.collections).map((name) => {
|
|
1269
|
+
const pascal = toPascalCase(name);
|
|
1270
|
+
return `export { use${pascal} } from './${name}'`;
|
|
1271
|
+
}),
|
|
1272
|
+
""
|
|
1273
|
+
];
|
|
1274
|
+
files.set("index.ts", indexLines.join("\n"));
|
|
1275
|
+
return files;
|
|
1276
|
+
}
|
|
1277
|
+
function toPascalCase(value) {
|
|
1278
|
+
return value.split(/[-_]/).filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// src/commands/generate/generate-command.ts
|
|
1282
|
+
var generateCommand = defineCommand5({
|
|
583
1283
|
meta: {
|
|
584
1284
|
name: "generate",
|
|
585
1285
|
description: "Generate code from your Kora schema"
|
|
586
1286
|
},
|
|
587
1287
|
subCommands: {
|
|
588
|
-
types:
|
|
1288
|
+
types: defineCommand5({
|
|
589
1289
|
meta: {
|
|
590
1290
|
name: "types",
|
|
591
1291
|
description: "Generate TypeScript types from your schema"
|
|
@@ -609,7 +1309,7 @@ var generateCommand = defineCommand2({
|
|
|
609
1309
|
}
|
|
610
1310
|
let schemaPath;
|
|
611
1311
|
if (args.schema && typeof args.schema === "string") {
|
|
612
|
-
schemaPath =
|
|
1312
|
+
schemaPath = resolve4(args.schema);
|
|
613
1313
|
} else {
|
|
614
1314
|
const found = await findSchemaFile(projectRoot);
|
|
615
1315
|
if (!found) {
|
|
@@ -624,7 +1324,7 @@ var generateCommand = defineCommand2({
|
|
|
624
1324
|
}
|
|
625
1325
|
logger.step(`Reading schema from ${schemaPath}...`);
|
|
626
1326
|
const schemaModule = await import(schemaPath);
|
|
627
|
-
const schema =
|
|
1327
|
+
const schema = extractSchema2(schemaModule);
|
|
628
1328
|
if (!schema) {
|
|
629
1329
|
logger.error("Schema file must export a SchemaDefinition as the default export.");
|
|
630
1330
|
process.exitCode = 1;
|
|
@@ -632,31 +1332,193 @@ var generateCommand = defineCommand2({
|
|
|
632
1332
|
}
|
|
633
1333
|
const output = generateTypes(schema);
|
|
634
1334
|
const outputFile = typeof args.output === "string" ? args.output : "kora/generated/types.ts";
|
|
635
|
-
const outputPath =
|
|
1335
|
+
const outputPath = resolve4(projectRoot, outputFile);
|
|
636
1336
|
await mkdir(dirname(outputPath), { recursive: true });
|
|
637
|
-
await
|
|
1337
|
+
await writeFile2(outputPath, output, "utf-8");
|
|
638
1338
|
logger.success(`Generated types at ${outputPath}`);
|
|
639
1339
|
}
|
|
1340
|
+
}),
|
|
1341
|
+
hooks: defineCommand5({
|
|
1342
|
+
meta: {
|
|
1343
|
+
name: "hooks",
|
|
1344
|
+
description: "Generate per-collection React hook stubs"
|
|
1345
|
+
},
|
|
1346
|
+
args: {
|
|
1347
|
+
schema: {
|
|
1348
|
+
type: "string",
|
|
1349
|
+
description: "Path to schema file"
|
|
1350
|
+
},
|
|
1351
|
+
output: {
|
|
1352
|
+
type: "string",
|
|
1353
|
+
description: "Output directory for hooks",
|
|
1354
|
+
default: "kora/generated/hooks"
|
|
1355
|
+
},
|
|
1356
|
+
types: {
|
|
1357
|
+
type: "string",
|
|
1358
|
+
description: "Relative import path to generated types from hook files",
|
|
1359
|
+
default: "../types"
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
async run({ args }) {
|
|
1363
|
+
const logger = createLogger();
|
|
1364
|
+
const projectRoot = await findProjectRoot();
|
|
1365
|
+
if (!projectRoot) {
|
|
1366
|
+
throw new InvalidProjectError(process.cwd());
|
|
1367
|
+
}
|
|
1368
|
+
let schemaPath;
|
|
1369
|
+
if (args.schema && typeof args.schema === "string") {
|
|
1370
|
+
schemaPath = resolve4(projectRoot, args.schema);
|
|
1371
|
+
} else {
|
|
1372
|
+
const found = await findSchemaFile(projectRoot);
|
|
1373
|
+
if (!found) {
|
|
1374
|
+
throw new SchemaNotFoundError([
|
|
1375
|
+
"src/schema.ts",
|
|
1376
|
+
"schema.ts",
|
|
1377
|
+
"src/schema.js",
|
|
1378
|
+
"schema.js"
|
|
1379
|
+
]);
|
|
1380
|
+
}
|
|
1381
|
+
schemaPath = found;
|
|
1382
|
+
}
|
|
1383
|
+
const schema = await loadSchemaDefinition(schemaPath, projectRoot);
|
|
1384
|
+
const outputDir = typeof args.output === "string" ? resolve4(projectRoot, args.output) : resolve4(projectRoot, "kora/generated/hooks");
|
|
1385
|
+
const files = generateCollectionHooks(schema);
|
|
1386
|
+
for (const [name, content] of files) {
|
|
1387
|
+
const adjusted = typeof args.types === "string" && args.types !== "../types" ? content.replace("from '../types'", `from '${args.types}'`) : content;
|
|
1388
|
+
const filePath = resolve4(outputDir, name);
|
|
1389
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
1390
|
+
await writeFile2(filePath, adjusted, "utf-8");
|
|
1391
|
+
}
|
|
1392
|
+
logger.success(`Generated ${String(files.size)} hook file(s) at ${outputDir}`);
|
|
1393
|
+
logger.step("Run `kora generate types` first if ../types is missing.");
|
|
1394
|
+
}
|
|
640
1395
|
})
|
|
641
1396
|
}
|
|
642
1397
|
});
|
|
643
|
-
function
|
|
1398
|
+
function extractSchema2(mod) {
|
|
644
1399
|
if (typeof mod !== "object" || mod === null) return null;
|
|
645
1400
|
const record = mod;
|
|
646
1401
|
const candidate = record.default ?? record;
|
|
647
|
-
if (
|
|
1402
|
+
if (isSchemaDefinition2(candidate)) return candidate;
|
|
648
1403
|
return null;
|
|
649
1404
|
}
|
|
650
|
-
function
|
|
1405
|
+
function isSchemaDefinition2(value) {
|
|
651
1406
|
if (typeof value !== "object" || value === null) return false;
|
|
652
1407
|
const obj = value;
|
|
653
1408
|
return typeof obj.version === "number" && typeof obj.collections === "object" && obj.collections !== null;
|
|
654
1409
|
}
|
|
655
1410
|
|
|
1411
|
+
// src/commands/logs/logs-command.ts
|
|
1412
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
1413
|
+
var DEFAULT_SYNC_PORT2 = 3001;
|
|
1414
|
+
var EVENTS_ENDPOINT = "/__kora/events";
|
|
1415
|
+
var logsCommand = defineCommand6({
|
|
1416
|
+
meta: {
|
|
1417
|
+
name: "logs",
|
|
1418
|
+
description: "Stream real-time events from a Kora sync server"
|
|
1419
|
+
},
|
|
1420
|
+
args: {
|
|
1421
|
+
url: {
|
|
1422
|
+
type: "string",
|
|
1423
|
+
description: "Sync server URL (default: http://localhost:3001)",
|
|
1424
|
+
default: `http://localhost:${DEFAULT_SYNC_PORT2}`
|
|
1425
|
+
},
|
|
1426
|
+
follow: {
|
|
1427
|
+
type: "boolean",
|
|
1428
|
+
description: "Follow log output (like tail -f)",
|
|
1429
|
+
default: true,
|
|
1430
|
+
alias: "f"
|
|
1431
|
+
},
|
|
1432
|
+
level: {
|
|
1433
|
+
type: "string",
|
|
1434
|
+
description: "Filter by log level (info, warn, error)"
|
|
1435
|
+
},
|
|
1436
|
+
token: {
|
|
1437
|
+
type: "string",
|
|
1438
|
+
description: "Admin token (defaults to KORA_ADMIN_TOKEN)"
|
|
1439
|
+
}
|
|
1440
|
+
},
|
|
1441
|
+
async run({ args }) {
|
|
1442
|
+
const logger = createLogger();
|
|
1443
|
+
const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT2}`;
|
|
1444
|
+
const follow = args.follow !== false;
|
|
1445
|
+
const levelFilter = typeof args.level === "string" ? args.level : null;
|
|
1446
|
+
const token = typeof args.token === "string" ? args.token : process.env.KORA_ADMIN_TOKEN ?? void 0;
|
|
1447
|
+
const eventsUrl = `${url.replace(/\/$/, "")}${EVENTS_ENDPOINT}`;
|
|
1448
|
+
logger.banner();
|
|
1449
|
+
logger.info(`Connecting to ${eventsUrl}...`);
|
|
1450
|
+
logger.blank();
|
|
1451
|
+
try {
|
|
1452
|
+
const response = await fetch(eventsUrl, {
|
|
1453
|
+
headers: token ? { Authorization: `Bearer ${token}` } : void 0
|
|
1454
|
+
});
|
|
1455
|
+
if (!response.ok) {
|
|
1456
|
+
throw new Error(
|
|
1457
|
+
`Failed to connect to ${eventsUrl}: ${response.status} ${response.statusText}`
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
const reader = response.body?.getReader();
|
|
1461
|
+
if (!reader) {
|
|
1462
|
+
throw new Error("Response body is not readable");
|
|
1463
|
+
}
|
|
1464
|
+
const decoder = new TextDecoder();
|
|
1465
|
+
let buffer = "";
|
|
1466
|
+
const processLine = (line) => {
|
|
1467
|
+
if (line.startsWith("event: ")) {
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (line.startsWith("data: ")) {
|
|
1471
|
+
const data = line.slice(6);
|
|
1472
|
+
try {
|
|
1473
|
+
const parsed = JSON.parse(data);
|
|
1474
|
+
const level = String(parsed.level ?? "info");
|
|
1475
|
+
if (levelFilter && level !== levelFilter) return;
|
|
1476
|
+
const timestamp = parsed.timestamp ? new Date(parsed.timestamp).toISOString() : "";
|
|
1477
|
+
const event = String(parsed.event ?? "unknown");
|
|
1478
|
+
const nodeId = parsed.nodeId ? ` [${parsed.nodeId}]` : "";
|
|
1479
|
+
const session = parsed.sessionId ? ` <${parsed.sessionId}>` : "";
|
|
1480
|
+
const count = parsed.count ? ` (${parsed.count})` : "";
|
|
1481
|
+
const error = parsed.error ? ` \u2014 ${parsed.error}` : "";
|
|
1482
|
+
const prefix = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : "\u25CF";
|
|
1483
|
+
const color = level === "error" ? "\x1B[31m" : level === "warn" ? "\x1B[33m" : "\x1B[36m";
|
|
1484
|
+
console.log(
|
|
1485
|
+
`${color}${prefix}${"\x1B[0m"} ${timestamp.slice(11, 23)} ${event}${nodeId}${session}${count}${error}`
|
|
1486
|
+
);
|
|
1487
|
+
} catch {
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
const read = async () => {
|
|
1492
|
+
while (true) {
|
|
1493
|
+
const { done, value } = await reader.read();
|
|
1494
|
+
if (done) break;
|
|
1495
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1496
|
+
const lines = buffer.split("\n");
|
|
1497
|
+
buffer = lines.pop() ?? "";
|
|
1498
|
+
for (const line of lines) {
|
|
1499
|
+
processLine(line.trim());
|
|
1500
|
+
}
|
|
1501
|
+
if (!follow) break;
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
await read();
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
logger.error(`Failed to connect to ${eventsUrl}`);
|
|
1507
|
+
if (error instanceof Error) {
|
|
1508
|
+
logger.error(error.message);
|
|
1509
|
+
}
|
|
1510
|
+
logger.blank();
|
|
1511
|
+
logger.step("Make sure the Kora sync server is running.");
|
|
1512
|
+
logger.step("Start it with: kora dev");
|
|
1513
|
+
process.exit(1);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
|
|
656
1518
|
// src/commands/migrate/migrate-command.ts
|
|
657
|
-
import { mkdir as mkdir2, readFile, readdir, writeFile as
|
|
658
|
-
import { dirname as dirname2, join as
|
|
659
|
-
import { defineCommand as
|
|
1519
|
+
import { mkdir as mkdir2, readFile as readFile3, readdir, writeFile as writeFile3 } from "fs/promises";
|
|
1520
|
+
import { dirname as dirname2, join as join6, resolve as resolve5 } from "path";
|
|
1521
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
660
1522
|
|
|
661
1523
|
// src/commands/migrate/migration-generator.ts
|
|
662
1524
|
import { generateSQL } from "@korajs/core";
|
|
@@ -748,7 +1610,8 @@ function isBreakingChange(change) {
|
|
|
748
1610
|
if (change.type === "field-changed") {
|
|
749
1611
|
if (change.before.kind !== change.after.kind) return true;
|
|
750
1612
|
if (change.before.itemKind !== change.after.itemKind) return true;
|
|
751
|
-
if (serializeEnum(change.before.enumValues) !== serializeEnum(change.after.enumValues))
|
|
1613
|
+
if (serializeEnum(change.before.enumValues) !== serializeEnum(change.after.enumValues))
|
|
1614
|
+
return true;
|
|
752
1615
|
if (change.before.required !== change.after.required && change.after.required) return true;
|
|
753
1616
|
return false;
|
|
754
1617
|
}
|
|
@@ -1131,9 +1994,7 @@ async function runSqliteMigration(path, statements, migrationId, fromVersion, to
|
|
|
1131
1994
|
async function loadSqliteDriver(projectRoot) {
|
|
1132
1995
|
try {
|
|
1133
1996
|
const { createRequire } = await import("module");
|
|
1134
|
-
const requireFrom = createRequire(
|
|
1135
|
-
projectRoot ? `${projectRoot}/package.json` : import.meta.url
|
|
1136
|
-
);
|
|
1997
|
+
const requireFrom = createRequire(projectRoot ? `${projectRoot}/package.json` : import.meta.url);
|
|
1137
1998
|
const Database = requireFrom("better-sqlite3");
|
|
1138
1999
|
return {
|
|
1139
2000
|
open(path) {
|
|
@@ -1222,83 +2083,34 @@ async function loadPostgresModule() {
|
|
|
1222
2083
|
}
|
|
1223
2084
|
}
|
|
1224
2085
|
|
|
1225
|
-
// src/commands/migrate/
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
import {
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
const object = value;
|
|
1248
|
-
return typeof object.version === "number" && typeof object.collections === "object" && object.collections !== null && typeof object.relations === "object" && object.relations !== null;
|
|
1249
|
-
}
|
|
1250
|
-
async function loadTypeScriptModule(schemaPath, projectRoot) {
|
|
1251
|
-
if (!await hasTsxInstalled(projectRoot)) {
|
|
1252
|
-
throw new Error(
|
|
1253
|
-
`Schema file is TypeScript (${schemaPath}) but local "tsx" was not found. Install tsx in the project.`
|
|
1254
|
-
);
|
|
1255
|
-
}
|
|
1256
|
-
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) })";
|
|
1257
|
-
const output = await runCommand2(
|
|
1258
|
-
process.execPath,
|
|
1259
|
-
["--import", "tsx", "--eval", script, schemaPath],
|
|
1260
|
-
projectRoot
|
|
1261
|
-
);
|
|
1262
|
-
try {
|
|
1263
|
-
return JSON.parse(output);
|
|
1264
|
-
} catch {
|
|
1265
|
-
throw new Error(`Failed to parse schema module output for ${schemaPath}`);
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
async function runCommand2(command, args, cwd) {
|
|
1269
|
-
return await new Promise((resolve4, reject) => {
|
|
1270
|
-
const child = spawn3(command, args, {
|
|
1271
|
-
cwd,
|
|
1272
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1273
|
-
env: process.env
|
|
1274
|
-
});
|
|
1275
|
-
let stdout = "";
|
|
1276
|
-
let stderr = "";
|
|
1277
|
-
child.stdout?.on("data", (chunk) => {
|
|
1278
|
-
stdout += chunk.toString("utf-8");
|
|
1279
|
-
});
|
|
1280
|
-
child.stderr?.on("data", (chunk) => {
|
|
1281
|
-
stderr += chunk.toString("utf-8");
|
|
1282
|
-
});
|
|
1283
|
-
child.on("error", (error) => {
|
|
1284
|
-
reject(error);
|
|
1285
|
-
});
|
|
1286
|
-
child.on("exit", (code) => {
|
|
1287
|
-
if (code === 0) {
|
|
1288
|
-
resolve4(stdout.trim());
|
|
1289
|
-
return;
|
|
1290
|
-
}
|
|
1291
|
-
reject(
|
|
1292
|
-
new Error(`Failed to load TypeScript schema (exit ${code ?? "unknown"}): ${stderr.trim()}`)
|
|
1293
|
-
);
|
|
1294
|
-
});
|
|
1295
|
-
});
|
|
2086
|
+
// src/commands/migrate/operation-transform-generator.ts
|
|
2087
|
+
function generateOperationTransformModule(fromVersion, toVersion) {
|
|
2088
|
+
return [
|
|
2089
|
+
"import type { Operation, OperationTransform } from '@korajs/core'",
|
|
2090
|
+
"",
|
|
2091
|
+
"/**",
|
|
2092
|
+
` * Operation transforms for schema v${fromVersion} \u2192 v${toVersion}.`,
|
|
2093
|
+
" * Implement `transform` to rewrite or drop legacy operations during sync.",
|
|
2094
|
+
" * @see https://korajs.dev/docs/migrations",
|
|
2095
|
+
" */",
|
|
2096
|
+
"export const operationTransforms: OperationTransform[] = [",
|
|
2097
|
+
" {",
|
|
2098
|
+
` fromVersion: ${fromVersion},`,
|
|
2099
|
+
` toVersion: ${toVersion},`,
|
|
2100
|
+
" transform(operation: Operation): Operation | null {",
|
|
2101
|
+
" // Return null to drop unmigratable operations.",
|
|
2102
|
+
" return operation",
|
|
2103
|
+
" },",
|
|
2104
|
+
" },",
|
|
2105
|
+
"]",
|
|
2106
|
+
""
|
|
2107
|
+
].join("\n");
|
|
1296
2108
|
}
|
|
1297
2109
|
|
|
1298
2110
|
// src/commands/migrate/migrate-command.ts
|
|
1299
2111
|
var SNAPSHOT_PATH = "kora/schema.snapshot.json";
|
|
1300
2112
|
var MIGRATIONS_DIR = "kora/migrations";
|
|
1301
|
-
var migrateCommand =
|
|
2113
|
+
var migrateCommand = defineCommand7({
|
|
1302
2114
|
meta: {
|
|
1303
2115
|
name: "migrate",
|
|
1304
2116
|
description: "Detect schema changes and generate/apply migrations"
|
|
@@ -1340,12 +2152,12 @@ var migrateCommand = defineCommand3({
|
|
|
1340
2152
|
throw new InvalidProjectError(process.cwd());
|
|
1341
2153
|
}
|
|
1342
2154
|
const config = await loadKoraConfig(projectRoot);
|
|
1343
|
-
const resolvedSchemaPath = typeof args.schema === "string" ?
|
|
2155
|
+
const resolvedSchemaPath = typeof args.schema === "string" ? resolve5(projectRoot, args.schema) : typeof config?.schema === "string" ? resolve5(projectRoot, config.schema) : await findSchemaFile(projectRoot);
|
|
1344
2156
|
if (!resolvedSchemaPath) {
|
|
1345
2157
|
throw new SchemaNotFoundError(["src/schema.ts", "schema.ts", "src/schema.js", "schema.js"]);
|
|
1346
2158
|
}
|
|
1347
2159
|
const currentSchema = await loadSchemaDefinition(resolvedSchemaPath, projectRoot);
|
|
1348
|
-
const snapshotFile =
|
|
2160
|
+
const snapshotFile = join6(projectRoot, SNAPSHOT_PATH);
|
|
1349
2161
|
const previousSchema = await readSchemaSnapshot(snapshotFile);
|
|
1350
2162
|
if (!previousSchema) {
|
|
1351
2163
|
if (args["dry-run"] === true) {
|
|
@@ -1358,7 +2170,7 @@ var migrateCommand = defineCommand3({
|
|
|
1358
2170
|
return;
|
|
1359
2171
|
}
|
|
1360
2172
|
const diff = diffSchemas(previousSchema, currentSchema);
|
|
1361
|
-
const outputDir = typeof args["output-dir"] === "string" ?
|
|
2173
|
+
const outputDir = typeof args["output-dir"] === "string" ? resolve5(projectRoot, args["output-dir"]) : resolve5(projectRoot, MIGRATIONS_DIR);
|
|
1362
2174
|
if (!diff.hasChanges) {
|
|
1363
2175
|
logger.success("No schema changes detected.");
|
|
1364
2176
|
if (args.apply === true) {
|
|
@@ -1411,7 +2223,12 @@ var migrateCommand = defineCommand3({
|
|
|
1411
2223
|
return;
|
|
1412
2224
|
}
|
|
1413
2225
|
await mkdir2(outputDir, { recursive: true });
|
|
1414
|
-
const migrationPath = await writeMigrationFile(
|
|
2226
|
+
const migrationPath = await writeMigrationFile(
|
|
2227
|
+
outputDir,
|
|
2228
|
+
diff.fromVersion,
|
|
2229
|
+
diff.toVersion,
|
|
2230
|
+
generated
|
|
2231
|
+
);
|
|
1415
2232
|
await writeSchemaSnapshot(snapshotFile, currentSchema);
|
|
1416
2233
|
logger.blank();
|
|
1417
2234
|
logger.success(`Generated migration: ${migrationPath}`);
|
|
@@ -1441,7 +2258,7 @@ var migrateCommand = defineCommand3({
|
|
|
1441
2258
|
});
|
|
1442
2259
|
async function readSchemaSnapshot(path) {
|
|
1443
2260
|
try {
|
|
1444
|
-
const content = await
|
|
2261
|
+
const content = await readFile3(path, "utf-8");
|
|
1445
2262
|
return JSON.parse(content);
|
|
1446
2263
|
} catch {
|
|
1447
2264
|
return null;
|
|
@@ -1463,14 +2280,14 @@ function isInteractiveTerminal() {
|
|
|
1463
2280
|
}
|
|
1464
2281
|
async function writeSchemaSnapshot(path, schema) {
|
|
1465
2282
|
await mkdir2(dirname2(path), { recursive: true });
|
|
1466
|
-
await
|
|
2283
|
+
await writeFile3(path, `${JSON.stringify(schema, null, 2)}
|
|
1467
2284
|
`, "utf-8");
|
|
1468
2285
|
}
|
|
1469
2286
|
async function writeMigrationFile(outputDir, fromVersion, toVersion, generated) {
|
|
1470
2287
|
const existing = await readdir(outputDir).catch(() => []);
|
|
1471
2288
|
const sequence = existing.filter((file) => /^\d{3}-/.test(file)).length + 1;
|
|
1472
2289
|
const filename = `${String(sequence).padStart(3, "0")}-v${fromVersion}-to-v${toVersion}.ts`;
|
|
1473
|
-
const path =
|
|
2290
|
+
const path = join6(outputDir, filename);
|
|
1474
2291
|
const migrationId = filename.replace(/\.ts$/, "");
|
|
1475
2292
|
const fileContent = [
|
|
1476
2293
|
`export const up = ${JSON.stringify(generated.up, null, 2)} as const`,
|
|
@@ -1482,8 +2299,10 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
|
|
|
1482
2299
|
`export const containsBreakingChanges = ${generated.containsBreakingChanges}`,
|
|
1483
2300
|
""
|
|
1484
2301
|
].join("\n");
|
|
1485
|
-
await
|
|
1486
|
-
|
|
2302
|
+
await writeFile3(path, fileContent, "utf-8");
|
|
2303
|
+
const transformsPath = join6(outputDir, `${migrationId}.transforms.ts`);
|
|
2304
|
+
await writeFile3(transformsPath, generateOperationTransformModule(fromVersion, toVersion), "utf-8");
|
|
2305
|
+
await writeMigrationManifest(join6(outputDir, `${migrationId}.json`), {
|
|
1487
2306
|
id: migrationId,
|
|
1488
2307
|
fromVersion,
|
|
1489
2308
|
toVersion,
|
|
@@ -1495,7 +2314,7 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
|
|
|
1495
2314
|
return path;
|
|
1496
2315
|
}
|
|
1497
2316
|
async function writeMigrationManifest(path, manifest) {
|
|
1498
|
-
await
|
|
2317
|
+
await writeFile3(path, `${JSON.stringify(manifest, null, 2)}
|
|
1499
2318
|
`, "utf-8");
|
|
1500
2319
|
}
|
|
1501
2320
|
async function listMigrationManifests(outputDir) {
|
|
@@ -1504,13 +2323,13 @@ async function listMigrationManifests(outputDir) {
|
|
|
1504
2323
|
const manifests = [];
|
|
1505
2324
|
for (const file of migrationFiles) {
|
|
1506
2325
|
const id = file.replace(/\.ts$/, "");
|
|
1507
|
-
const manifestPath =
|
|
2326
|
+
const manifestPath = join6(outputDir, `${id}.json`);
|
|
1508
2327
|
const jsonManifest = await readMigrationManifest(manifestPath);
|
|
1509
2328
|
if (jsonManifest) {
|
|
1510
2329
|
manifests.push({ ...jsonManifest, id });
|
|
1511
2330
|
continue;
|
|
1512
2331
|
}
|
|
1513
|
-
const migrationPath =
|
|
2332
|
+
const migrationPath = join6(outputDir, file);
|
|
1514
2333
|
const sourceManifest = await readMigrationManifestFromSource(migrationPath, id);
|
|
1515
2334
|
manifests.push(sourceManifest);
|
|
1516
2335
|
}
|
|
@@ -1518,7 +2337,7 @@ async function listMigrationManifests(outputDir) {
|
|
|
1518
2337
|
}
|
|
1519
2338
|
async function readMigrationManifest(path) {
|
|
1520
2339
|
try {
|
|
1521
|
-
const content = await
|
|
2340
|
+
const content = await readFile3(path, "utf-8");
|
|
1522
2341
|
return JSON.parse(content);
|
|
1523
2342
|
} catch (error) {
|
|
1524
2343
|
const code = error.code;
|
|
@@ -1529,7 +2348,7 @@ async function readMigrationManifest(path) {
|
|
|
1529
2348
|
}
|
|
1530
2349
|
}
|
|
1531
2350
|
async function readMigrationManifestFromSource(path, id) {
|
|
1532
|
-
const content = await
|
|
2351
|
+
const content = await readFile3(path, "utf-8");
|
|
1533
2352
|
const versions = parseVersionsFromMigrationId(id);
|
|
1534
2353
|
return {
|
|
1535
2354
|
id,
|
|
@@ -1546,9 +2365,14 @@ function parseVersionsFromMigrationId(id) {
|
|
|
1546
2365
|
if (!match) {
|
|
1547
2366
|
throw new Error(`Migration id "${id}" does not include a vX-to-vY version suffix.`);
|
|
1548
2367
|
}
|
|
2368
|
+
const fromVersionText = match[1];
|
|
2369
|
+
const toVersionText = match[2];
|
|
2370
|
+
if (fromVersionText === void 0 || toVersionText === void 0) {
|
|
2371
|
+
throw new Error(`Migration id "${id}" does not include a vX-to-vY version suffix.`);
|
|
2372
|
+
}
|
|
1549
2373
|
return {
|
|
1550
|
-
fromVersion: Number.parseInt(
|
|
1551
|
-
toVersion: Number.parseInt(
|
|
2374
|
+
fromVersion: Number.parseInt(fromVersionText, 10),
|
|
2375
|
+
toVersion: Number.parseInt(toVersionText, 10)
|
|
1552
2376
|
};
|
|
1553
2377
|
}
|
|
1554
2378
|
function parseStringArrayExport(source, exportName) {
|
|
@@ -1586,18 +2410,18 @@ function parseLiteralExport(source, exportName) {
|
|
|
1586
2410
|
}
|
|
1587
2411
|
function resolveSqliteApplyPath(dbArg, projectRoot, config) {
|
|
1588
2412
|
if (typeof dbArg === "string") {
|
|
1589
|
-
return
|
|
2413
|
+
return resolve5(projectRoot, dbArg);
|
|
1590
2414
|
}
|
|
1591
2415
|
const sync = config?.dev?.sync;
|
|
1592
2416
|
if (typeof sync === "object" && sync !== null) {
|
|
1593
2417
|
if (sync.store === "sqlite") {
|
|
1594
|
-
return
|
|
2418
|
+
return join6(projectRoot, "kora-sync.db");
|
|
1595
2419
|
}
|
|
1596
2420
|
if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "sqlite") {
|
|
1597
2421
|
if (typeof sync.store.filename === "string" && sync.store.filename.length > 0) {
|
|
1598
|
-
return
|
|
2422
|
+
return resolve5(projectRoot, sync.store.filename);
|
|
1599
2423
|
}
|
|
1600
|
-
return
|
|
2424
|
+
return join6(projectRoot, "kora-sync.db");
|
|
1601
2425
|
}
|
|
1602
2426
|
}
|
|
1603
2427
|
return void 0;
|
|
@@ -1614,18 +2438,155 @@ function resolvePostgresConnectionString(config) {
|
|
|
1614
2438
|
return void 0;
|
|
1615
2439
|
}
|
|
1616
2440
|
|
|
2441
|
+
// src/commands/status/status-command.ts
|
|
2442
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
2443
|
+
var DEFAULT_SYNC_PORT3 = 3001;
|
|
2444
|
+
var STATUS_ENDPOINT2 = "/__kora/status";
|
|
2445
|
+
async function fetchServerStatus(url, token) {
|
|
2446
|
+
const statusUrl = `${url.replace(/\/$/, "")}${STATUS_ENDPOINT2}`;
|
|
2447
|
+
const response = await fetch(statusUrl, {
|
|
2448
|
+
headers: token ? { Authorization: `Bearer ${token}` } : void 0
|
|
2449
|
+
});
|
|
2450
|
+
if (!response.ok) {
|
|
2451
|
+
throw new Error(
|
|
2452
|
+
`Failed to fetch status from ${statusUrl}: ${response.status} ${response.statusText}`
|
|
2453
|
+
);
|
|
2454
|
+
}
|
|
2455
|
+
return await response.json();
|
|
2456
|
+
}
|
|
2457
|
+
function formatUptime(ms) {
|
|
2458
|
+
const seconds = Math.floor(ms / 1e3);
|
|
2459
|
+
const minutes = Math.floor(seconds / 60);
|
|
2460
|
+
const hours = Math.floor(minutes / 60);
|
|
2461
|
+
const days = Math.floor(hours / 24);
|
|
2462
|
+
const parts = [];
|
|
2463
|
+
if (days > 0) parts.push(`${days}d`);
|
|
2464
|
+
if (hours % 24 > 0) parts.push(`${hours % 24}h`);
|
|
2465
|
+
if (minutes % 60 > 0) parts.push(`${minutes % 60}m`);
|
|
2466
|
+
parts.push(`${seconds % 60}s`);
|
|
2467
|
+
return parts.join(" ");
|
|
2468
|
+
}
|
|
2469
|
+
var statusCommand = defineCommand8({
|
|
2470
|
+
meta: {
|
|
2471
|
+
name: "status",
|
|
2472
|
+
description: "Show Kora sync server status"
|
|
2473
|
+
},
|
|
2474
|
+
args: {
|
|
2475
|
+
url: {
|
|
2476
|
+
type: "string",
|
|
2477
|
+
description: "Sync server URL (default: http://localhost:3001)",
|
|
2478
|
+
default: `http://localhost:${DEFAULT_SYNC_PORT3}`
|
|
2479
|
+
},
|
|
2480
|
+
watch: {
|
|
2481
|
+
type: "boolean",
|
|
2482
|
+
description: "Live-updating status (like htop)",
|
|
2483
|
+
default: false,
|
|
2484
|
+
alias: "w"
|
|
2485
|
+
},
|
|
2486
|
+
token: {
|
|
2487
|
+
type: "string",
|
|
2488
|
+
description: "Admin token (defaults to KORA_ADMIN_TOKEN)"
|
|
2489
|
+
}
|
|
2490
|
+
},
|
|
2491
|
+
async run({ args }) {
|
|
2492
|
+
const logger = createLogger();
|
|
2493
|
+
const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT3}`;
|
|
2494
|
+
const watch2 = args.watch === true;
|
|
2495
|
+
const token = typeof args.token === "string" ? args.token : process.env.KORA_ADMIN_TOKEN ?? void 0;
|
|
2496
|
+
try {
|
|
2497
|
+
if (watch2) {
|
|
2498
|
+
console.clear();
|
|
2499
|
+
logger.banner();
|
|
2500
|
+
logger.info(`Connecting to ${url}...`);
|
|
2501
|
+
const interval = setInterval(async () => {
|
|
2502
|
+
try {
|
|
2503
|
+
const status = await fetchServerStatus(url, token);
|
|
2504
|
+
printStatus(status, url, logger);
|
|
2505
|
+
} catch {
|
|
2506
|
+
}
|
|
2507
|
+
}, 2e3);
|
|
2508
|
+
try {
|
|
2509
|
+
const status = await fetchServerStatus(url, token);
|
|
2510
|
+
printStatus(status, url, logger);
|
|
2511
|
+
} catch {
|
|
2512
|
+
}
|
|
2513
|
+
await new Promise(() => {
|
|
2514
|
+
process.on("SIGINT", () => {
|
|
2515
|
+
clearInterval(interval);
|
|
2516
|
+
process.exit(0);
|
|
2517
|
+
});
|
|
2518
|
+
process.on("SIGTERM", () => {
|
|
2519
|
+
clearInterval(interval);
|
|
2520
|
+
process.exit(0);
|
|
2521
|
+
});
|
|
2522
|
+
});
|
|
2523
|
+
} else {
|
|
2524
|
+
const status = await fetchServerStatus(url, token);
|
|
2525
|
+
printStatus(status, url, logger);
|
|
2526
|
+
}
|
|
2527
|
+
} catch (error) {
|
|
2528
|
+
logger.error(`Failed to connect to ${url}`);
|
|
2529
|
+
if (error instanceof Error) {
|
|
2530
|
+
logger.error(error.message);
|
|
2531
|
+
}
|
|
2532
|
+
logger.blank();
|
|
2533
|
+
logger.step("Make sure the Kora sync server is running.");
|
|
2534
|
+
logger.step("Start it with: kora dev");
|
|
2535
|
+
process.exit(1);
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
});
|
|
2539
|
+
function printStatus(status, url, logger) {
|
|
2540
|
+
console.clear();
|
|
2541
|
+
logger.banner();
|
|
2542
|
+
logger.info(`Kora Sync Server \u2014 ${url}`);
|
|
2543
|
+
logger.blank();
|
|
2544
|
+
if (!status.running) {
|
|
2545
|
+
logger.error("Server is not running");
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
logger.step(`Status: Running (uptime: ${formatUptime(status.uptime)}) \u2014 v${status.version}`);
|
|
2549
|
+
logger.step(`Schema: v${status.schemaVersion}`);
|
|
2550
|
+
logger.blank();
|
|
2551
|
+
logger.step(`Connections: ${status.connectedClients} connected`);
|
|
2552
|
+
logger.step(` Peak: ${status.peakConnections}`);
|
|
2553
|
+
logger.step(` Total: ${status.connectionsTotal}`);
|
|
2554
|
+
if (status.connectedNodeIds.length > 0) {
|
|
2555
|
+
logger.step(" Active nodes:");
|
|
2556
|
+
for (const nodeId of status.connectedNodeIds) {
|
|
2557
|
+
logger.step(` \u2022 ${nodeId}`);
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
logger.blank();
|
|
2561
|
+
logger.step("Operations:");
|
|
2562
|
+
logger.step(` Received: ${status.operationsReceived.toLocaleString()}`);
|
|
2563
|
+
logger.step(` Sent: ${status.operationsSent.toLocaleString()}`);
|
|
2564
|
+
logger.step(` Total: ${status.totalOperations.toLocaleString()}`);
|
|
2565
|
+
logger.blank();
|
|
2566
|
+
if (status.errorCount > 0) {
|
|
2567
|
+
logger.warn(`Errors: ${status.errorCount}`);
|
|
2568
|
+
} else {
|
|
2569
|
+
logger.step("Errors: 0");
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
|
|
1617
2573
|
// src/bin.ts
|
|
1618
|
-
var main =
|
|
2574
|
+
var main = defineCommand9({
|
|
1619
2575
|
meta: {
|
|
1620
2576
|
name: "kora",
|
|
1621
2577
|
description: "Kora.js \u2014 Offline-first application framework"
|
|
1622
2578
|
},
|
|
1623
2579
|
subCommands: {
|
|
2580
|
+
backup: backupCommand,
|
|
2581
|
+
compact: compactCommand,
|
|
1624
2582
|
create: createCommand,
|
|
1625
2583
|
dev: devCommand,
|
|
2584
|
+
doctor: doctorCommand,
|
|
1626
2585
|
deploy: deployCommand,
|
|
1627
2586
|
generate: generateCommand,
|
|
1628
|
-
|
|
2587
|
+
logs: logsCommand,
|
|
2588
|
+
migrate: migrateCommand,
|
|
2589
|
+
status: statusCommand
|
|
1629
2590
|
}
|
|
1630
2591
|
});
|
|
1631
2592
|
runMain(main);
|