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