@korajs/cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +1609 -643
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1086 -139
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-CMSX76KM.js → chunk-3WNFM3QB.js} +103 -96
- package/dist/chunk-3WNFM3QB.js.map +1 -0
- package/dist/{chunk-MVP5PDT4.js → chunk-PSRM56B7.js} +2 -2
- package/dist/{chunk-YGVO4POI.js → chunk-SOTZIWIF.js} +27 -15
- package/dist/chunk-SOTZIWIF.js.map +1 -0
- package/dist/create.cjs +54 -47
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +2 -2
- package/dist/index.cjs +78 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- 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 +1 -1
- package/templates/tauri-react/tsconfig.json.hbs +14 -0
- package/templates/tauri-react/vite.config.ts +9 -9
- package/dist/chunk-CMSX76KM.js.map +0 -1
- package/dist/chunk-YGVO4POI.js.map +0 -1
- package/templates/tauri-react/tsconfig.json +0 -15
- /package/dist/{chunk-MVP5PDT4.js.map → chunk-PSRM56B7.js.map} +0 -0
package/dist/bin.cjs
CHANGED
|
@@ -23,67 +23,600 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/bin.ts
|
|
26
|
-
var
|
|
26
|
+
var import_citty11 = require("citty");
|
|
27
|
+
|
|
28
|
+
// src/commands/backup/backup-command.ts
|
|
29
|
+
var import_promises = require("fs/promises");
|
|
30
|
+
var import_node_path = require("path");
|
|
31
|
+
var import_citty = require("citty");
|
|
32
|
+
|
|
33
|
+
// src/utils/logger.ts
|
|
34
|
+
var RESET = "\x1B[0m";
|
|
35
|
+
var BOLD = "\x1B[1m";
|
|
36
|
+
var DIM = "\x1B[2m";
|
|
37
|
+
var GREEN = "\x1B[32m";
|
|
38
|
+
var YELLOW = "\x1B[33m";
|
|
39
|
+
var RED = "\x1B[31m";
|
|
40
|
+
var CYAN = "\x1B[36m";
|
|
41
|
+
function createLogger(options) {
|
|
42
|
+
const colorDisabled = options?.noColor === true || process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
43
|
+
function color(code, text) {
|
|
44
|
+
return colorDisabled ? text : `${code}${text}${RESET}`;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
info(message) {
|
|
48
|
+
console.log(color(CYAN, message));
|
|
49
|
+
},
|
|
50
|
+
success(message) {
|
|
51
|
+
console.log(color(GREEN, ` \u2713 ${message}`));
|
|
52
|
+
},
|
|
53
|
+
warn(message) {
|
|
54
|
+
console.warn(color(YELLOW, ` \u26A0 ${message}`));
|
|
55
|
+
},
|
|
56
|
+
error(message) {
|
|
57
|
+
console.error(color(RED, ` \u2717 ${message}`));
|
|
58
|
+
},
|
|
59
|
+
step(message) {
|
|
60
|
+
console.log(color(DIM, ` ${message}`));
|
|
61
|
+
},
|
|
62
|
+
blank() {
|
|
63
|
+
console.log();
|
|
64
|
+
},
|
|
65
|
+
banner() {
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(
|
|
68
|
+
color(BOLD + CYAN, " Kora.js") + color(DIM, " \u2014 Offline-first application framework")
|
|
69
|
+
);
|
|
70
|
+
console.log();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/commands/backup/backup-command.ts
|
|
76
|
+
var DEFAULT_SYNC_PORT = 3001;
|
|
77
|
+
var backupCommand = (0, import_citty.defineCommand)({
|
|
78
|
+
meta: {
|
|
79
|
+
name: "backup",
|
|
80
|
+
description: "Backup and restore Kora sync server data"
|
|
81
|
+
},
|
|
82
|
+
subCommands: {
|
|
83
|
+
create: (0, import_citty.defineCommand)({
|
|
84
|
+
meta: {
|
|
85
|
+
name: "create",
|
|
86
|
+
description: "Create a backup of the sync server"
|
|
87
|
+
},
|
|
88
|
+
args: {
|
|
89
|
+
url: {
|
|
90
|
+
type: "string",
|
|
91
|
+
description: "Sync server URL (default: http://localhost:3001)",
|
|
92
|
+
default: `http://localhost:${DEFAULT_SYNC_PORT}`
|
|
93
|
+
},
|
|
94
|
+
out: {
|
|
95
|
+
type: "string",
|
|
96
|
+
description: "Output file path (default: kora-backup-<timestamp>.kora)"
|
|
97
|
+
},
|
|
98
|
+
token: {
|
|
99
|
+
type: "string",
|
|
100
|
+
description: "Backup token (defaults to KORA_BACKUP_TOKEN or KORA_ADMIN_TOKEN)"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
async run({ args }) {
|
|
104
|
+
const logger = createLogger();
|
|
105
|
+
const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT}`;
|
|
106
|
+
const outFile = typeof args.out === "string" ? args.out : `kora-backup-${Date.now()}.kora`;
|
|
107
|
+
const token = typeof args.token === "string" ? args.token : process.env.KORA_BACKUP_TOKEN ?? process.env.KORA_ADMIN_TOKEN;
|
|
108
|
+
logger.banner();
|
|
109
|
+
logger.info(`Exporting backup from ${url}...`);
|
|
110
|
+
try {
|
|
111
|
+
const backupUrl = `${url.replace(/\/$/, "")}/__kora/backup/export`;
|
|
112
|
+
const response = await fetch(backupUrl, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: token ? { Authorization: `Bearer ${token}` } : void 0
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Backup failed: ${error.message ?? response.statusText}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const buffer = await response.arrayBuffer();
|
|
123
|
+
await (0, import_promises.writeFile)(outFile, new Uint8Array(buffer));
|
|
124
|
+
const size = (buffer.byteLength / 1024).toFixed(1);
|
|
125
|
+
logger.success(`Backup saved to ${outFile} (${size} KB)`);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
logger.error("Backup failed");
|
|
128
|
+
if (error instanceof Error) logger.error(error.message);
|
|
129
|
+
logger.blank();
|
|
130
|
+
logger.step("Make sure the Kora sync server is running.");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}),
|
|
135
|
+
restore: (0, import_citty.defineCommand)({
|
|
136
|
+
meta: {
|
|
137
|
+
name: "restore",
|
|
138
|
+
description: "Restore a backup to the sync server"
|
|
139
|
+
},
|
|
140
|
+
args: {
|
|
141
|
+
file: {
|
|
142
|
+
type: "string",
|
|
143
|
+
description: "Backup file path",
|
|
144
|
+
required: true
|
|
145
|
+
},
|
|
146
|
+
url: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "Sync server URL (default: http://localhost:3001)",
|
|
149
|
+
default: `http://localhost:${DEFAULT_SYNC_PORT}`
|
|
150
|
+
},
|
|
151
|
+
merge: {
|
|
152
|
+
type: "boolean",
|
|
153
|
+
description: "Merge with existing data instead of replacing",
|
|
154
|
+
default: false
|
|
155
|
+
},
|
|
156
|
+
token: {
|
|
157
|
+
type: "string",
|
|
158
|
+
description: "Backup token (defaults to KORA_BACKUP_TOKEN or KORA_ADMIN_TOKEN)"
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
async run({ args }) {
|
|
162
|
+
const logger = createLogger();
|
|
163
|
+
const filePath = typeof args.file === "string" ? args.file : "";
|
|
164
|
+
const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT}`;
|
|
165
|
+
const merge = args.merge === true;
|
|
166
|
+
const token = typeof args.token === "string" ? args.token : process.env.KORA_BACKUP_TOKEN ?? process.env.KORA_ADMIN_TOKEN;
|
|
167
|
+
logger.banner();
|
|
168
|
+
logger.info(`Restoring backup from ${filePath} to ${url}...`);
|
|
169
|
+
try {
|
|
170
|
+
const data = await (0, import_promises.readFile)(filePath);
|
|
171
|
+
const restoreUrl = `${url.replace(/\/$/, "")}/__kora/backup/import?merge=${merge}`;
|
|
172
|
+
const response = await fetch(restoreUrl, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: token ? { Authorization: `Bearer ${token}` } : void 0,
|
|
175
|
+
body: data
|
|
176
|
+
});
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
179
|
+
throw new Error(
|
|
180
|
+
`Restore failed: ${error.message ?? response.statusText}`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const result = await response.json();
|
|
184
|
+
if (result.success) {
|
|
185
|
+
logger.success(
|
|
186
|
+
`Restored ${result.operationsRestored} operations${result.duration ? ` in ${result.duration}ms` : ""}`
|
|
187
|
+
);
|
|
188
|
+
} else {
|
|
189
|
+
logger.error("Restore completed with errors");
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
logger.error("Restore failed");
|
|
193
|
+
if (error instanceof Error) logger.error(error.message);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}),
|
|
198
|
+
info: (0, import_citty.defineCommand)({
|
|
199
|
+
meta: {
|
|
200
|
+
name: "info",
|
|
201
|
+
description: "Show backup file information"
|
|
202
|
+
},
|
|
203
|
+
args: {
|
|
204
|
+
file: {
|
|
205
|
+
type: "string",
|
|
206
|
+
description: "Backup file path",
|
|
207
|
+
required: true
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
async run({ args }) {
|
|
211
|
+
const logger = createLogger();
|
|
212
|
+
const filePath = typeof args.file === "string" ? args.file : "";
|
|
213
|
+
try {
|
|
214
|
+
const data = await (0, import_promises.readFile)(filePath);
|
|
215
|
+
const { readBackupManifest } = await import("@korajs/store");
|
|
216
|
+
const manifest = readBackupManifest(
|
|
217
|
+
new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
218
|
+
);
|
|
219
|
+
logger.banner();
|
|
220
|
+
logger.info(`Backup: ${filePath}`);
|
|
221
|
+
logger.blank();
|
|
222
|
+
logger.step(`Created: ${new Date(manifest.createdAt).toISOString()}`);
|
|
223
|
+
logger.step(`Node ID: ${manifest.nodeId}`);
|
|
224
|
+
logger.step(`Schema: v${manifest.schemaVersion}`);
|
|
225
|
+
logger.step(`Operations: ${manifest.operationCount.toLocaleString()}`);
|
|
226
|
+
logger.step(`Collections: ${manifest.collections.join(", ") || "(none)"}`);
|
|
227
|
+
logger.step(`Checksum: ${manifest.checksum.slice(0, 16)}...`);
|
|
228
|
+
logger.blank();
|
|
229
|
+
logger.step(`Format version: ${manifest.version}`);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
logger.error("Failed to read backup file");
|
|
232
|
+
if (error instanceof Error) logger.error(error.message);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// src/commands/compact/compact-command.ts
|
|
241
|
+
var import_node_path5 = require("path");
|
|
242
|
+
var import_citty2 = require("citty");
|
|
243
|
+
|
|
244
|
+
// src/errors.ts
|
|
245
|
+
var import_core = require("@korajs/core");
|
|
246
|
+
var ProjectExistsError = class extends import_core.KoraError {
|
|
247
|
+
constructor(directory) {
|
|
248
|
+
super(
|
|
249
|
+
`Directory "${directory}" already exists. Choose a different name or remove the existing directory.`,
|
|
250
|
+
"PROJECT_EXISTS",
|
|
251
|
+
{ directory }
|
|
252
|
+
);
|
|
253
|
+
this.directory = directory;
|
|
254
|
+
this.name = "ProjectExistsError";
|
|
255
|
+
}
|
|
256
|
+
directory;
|
|
257
|
+
};
|
|
258
|
+
var SchemaNotFoundError = class extends import_core.KoraError {
|
|
259
|
+
constructor(searchedPaths) {
|
|
260
|
+
super(
|
|
261
|
+
`Could not find a schema file. Searched: ${searchedPaths.join(", ")}. Create a schema file using defineSchema() from @korajs/core.`,
|
|
262
|
+
"SCHEMA_NOT_FOUND",
|
|
263
|
+
{ searchedPaths }
|
|
264
|
+
);
|
|
265
|
+
this.searchedPaths = searchedPaths;
|
|
266
|
+
this.name = "SchemaNotFoundError";
|
|
267
|
+
}
|
|
268
|
+
searchedPaths;
|
|
269
|
+
};
|
|
270
|
+
var InvalidProjectError = class extends import_core.KoraError {
|
|
271
|
+
constructor(directory) {
|
|
272
|
+
super(
|
|
273
|
+
`"${directory}" is not a valid Kora project. No package.json with a kora dependency found. Run this command from inside a Kora project.`,
|
|
274
|
+
"INVALID_PROJECT",
|
|
275
|
+
{ directory }
|
|
276
|
+
);
|
|
277
|
+
this.directory = directory;
|
|
278
|
+
this.name = "InvalidProjectError";
|
|
279
|
+
}
|
|
280
|
+
directory;
|
|
281
|
+
};
|
|
282
|
+
var DevServerError = class extends import_core.KoraError {
|
|
283
|
+
constructor(binary, searchPath) {
|
|
284
|
+
super(
|
|
285
|
+
`Could not find required binary "${binary}" at ${searchPath}. Install project dependencies and try again.`,
|
|
286
|
+
"DEV_SERVER_ERROR",
|
|
287
|
+
{ binary, searchPath }
|
|
288
|
+
);
|
|
289
|
+
this.binary = binary;
|
|
290
|
+
this.searchPath = searchPath;
|
|
291
|
+
this.name = "DevServerError";
|
|
292
|
+
}
|
|
293
|
+
binary;
|
|
294
|
+
searchPath;
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// src/utils/fs-helpers.ts
|
|
298
|
+
var import_promises2 = require("fs/promises");
|
|
299
|
+
var import_node_path2 = require("path");
|
|
300
|
+
async function directoryExists(path) {
|
|
301
|
+
try {
|
|
302
|
+
await (0, import_promises2.access)(path);
|
|
303
|
+
return true;
|
|
304
|
+
} catch {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function findProjectRoot(startDir) {
|
|
309
|
+
let current = (0, import_node_path2.resolve)(startDir ?? process.cwd());
|
|
310
|
+
for (; ; ) {
|
|
311
|
+
const pkgPath = (0, import_node_path2.join)(current, "package.json");
|
|
312
|
+
try {
|
|
313
|
+
const content = await (0, import_promises2.readFile)(pkgPath, "utf-8");
|
|
314
|
+
const pkg = JSON.parse(content);
|
|
315
|
+
if (isKoraProject(pkg)) {
|
|
316
|
+
return current;
|
|
317
|
+
}
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
const parent = (0, import_node_path2.dirname)(current);
|
|
321
|
+
if (parent === current) break;
|
|
322
|
+
current = parent;
|
|
323
|
+
}
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
async function findSchemaFile(projectRoot) {
|
|
327
|
+
const candidates = [
|
|
328
|
+
(0, import_node_path2.join)(projectRoot, "src", "schema.ts"),
|
|
329
|
+
(0, import_node_path2.join)(projectRoot, "schema.ts"),
|
|
330
|
+
(0, import_node_path2.join)(projectRoot, "src", "schema.js"),
|
|
331
|
+
(0, import_node_path2.join)(projectRoot, "schema.js")
|
|
332
|
+
];
|
|
333
|
+
for (const candidate of candidates) {
|
|
334
|
+
try {
|
|
335
|
+
await (0, import_promises2.access)(candidate);
|
|
336
|
+
return candidate;
|
|
337
|
+
} catch {
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
async function hasTsxInstalled(projectRoot) {
|
|
343
|
+
try {
|
|
344
|
+
await (0, import_promises2.access)((0, import_node_path2.join)(projectRoot, "node_modules", "tsx", "package.json"));
|
|
345
|
+
return true;
|
|
346
|
+
} catch {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async function resolveProjectBinaryEntryPoint(projectRoot, packageName, binaryName) {
|
|
351
|
+
const pkgJsonPath = (0, import_node_path2.join)(projectRoot, "node_modules", packageName, "package.json");
|
|
352
|
+
try {
|
|
353
|
+
const content = await (0, import_promises2.readFile)(pkgJsonPath, "utf-8");
|
|
354
|
+
const pkg = JSON.parse(content);
|
|
355
|
+
let binPath;
|
|
356
|
+
if (typeof pkg.bin === "string") {
|
|
357
|
+
binPath = pkg.bin;
|
|
358
|
+
} else if (typeof pkg.bin === "object" && pkg.bin !== null) {
|
|
359
|
+
binPath = pkg.bin[binaryName];
|
|
360
|
+
}
|
|
361
|
+
if (!binPath) return null;
|
|
362
|
+
const fullPath = (0, import_node_path2.join)(projectRoot, "node_modules", packageName, binPath);
|
|
363
|
+
await (0, import_promises2.access)(fullPath);
|
|
364
|
+
return fullPath;
|
|
365
|
+
} catch {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function isKoraProject(pkg) {
|
|
370
|
+
if (typeof pkg !== "object" || pkg === null) return false;
|
|
371
|
+
const record = pkg;
|
|
372
|
+
return hasKoraDep(record.dependencies) || hasKoraDep(record.devDependencies);
|
|
373
|
+
}
|
|
374
|
+
function hasKoraDep(deps) {
|
|
375
|
+
if (typeof deps !== "object" || deps === null) return false;
|
|
376
|
+
return Object.keys(deps).some((key) => key === "kora" || key.startsWith("@korajs/"));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/commands/dev/kora-config.ts
|
|
380
|
+
var import_node_child_process = require("child_process");
|
|
381
|
+
var import_promises3 = require("fs/promises");
|
|
382
|
+
var import_node_path3 = require("path");
|
|
383
|
+
var import_node_url = require("url");
|
|
384
|
+
var CONFIG_CANDIDATES = [
|
|
385
|
+
"kora.config.ts",
|
|
386
|
+
"kora.config.mts",
|
|
387
|
+
"kora.config.cts",
|
|
388
|
+
"kora.config.js",
|
|
389
|
+
"kora.config.mjs",
|
|
390
|
+
"kora.config.cjs"
|
|
391
|
+
];
|
|
392
|
+
async function loadKoraConfig(projectRoot) {
|
|
393
|
+
const configPath = await findKoraConfigFile(projectRoot);
|
|
394
|
+
if (!configPath) return null;
|
|
395
|
+
const ext = (0, import_node_path3.extname)(configPath);
|
|
396
|
+
if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
|
|
397
|
+
const loaded2 = await loadTypeScriptConfig(configPath, projectRoot);
|
|
398
|
+
return toConfigObject(loaded2);
|
|
399
|
+
}
|
|
400
|
+
const loaded = await import((0, import_node_url.pathToFileURL)(configPath).href);
|
|
401
|
+
return toConfigObject(loaded);
|
|
402
|
+
}
|
|
403
|
+
async function findKoraConfigFile(projectRoot) {
|
|
404
|
+
for (const file of CONFIG_CANDIDATES) {
|
|
405
|
+
const candidate = (0, import_node_path3.join)(projectRoot, file);
|
|
406
|
+
try {
|
|
407
|
+
await (0, import_promises3.access)(candidate);
|
|
408
|
+
return candidate;
|
|
409
|
+
} catch {
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
async function loadTypeScriptConfig(configPath, projectRoot) {
|
|
415
|
+
if (!await hasTsxInstalled(projectRoot)) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
`Found TypeScript config at ${configPath}, but "tsx" is not installed in this project. Install tsx or use kora.config.js.`
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
const script = "const configPath = process.argv[process.argv.length - 1];import('node:url').then(u => import(u.pathToFileURL(configPath).href)).then(mod => { const v = mod.default ?? mod; process.stdout.write(JSON.stringify(v)) }).catch(e => { process.stderr.write(String(e)); process.exit(1) })";
|
|
421
|
+
const output = await runCommand(
|
|
422
|
+
process.execPath,
|
|
423
|
+
["--import", "tsx", "--eval", script, configPath],
|
|
424
|
+
projectRoot
|
|
425
|
+
);
|
|
426
|
+
try {
|
|
427
|
+
return JSON.parse(output);
|
|
428
|
+
} catch {
|
|
429
|
+
throw new Error(`Failed to parse ${configPath} output as JSON.`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async function runCommand(command, args, cwd) {
|
|
433
|
+
return await new Promise((resolve11, reject) => {
|
|
434
|
+
const child = (0, import_node_child_process.spawn)(command, args, {
|
|
435
|
+
cwd,
|
|
436
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
437
|
+
env: process.env
|
|
438
|
+
});
|
|
439
|
+
let stdout = "";
|
|
440
|
+
let stderr = "";
|
|
441
|
+
child.stdout?.on("data", (chunk) => {
|
|
442
|
+
stdout += chunk.toString("utf-8");
|
|
443
|
+
});
|
|
444
|
+
child.stderr?.on("data", (chunk) => {
|
|
445
|
+
stderr += chunk.toString("utf-8");
|
|
446
|
+
});
|
|
447
|
+
child.on("error", (error) => {
|
|
448
|
+
reject(error);
|
|
449
|
+
});
|
|
450
|
+
child.on("exit", (code) => {
|
|
451
|
+
if (code === 0) {
|
|
452
|
+
resolve11(stdout.trim());
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
reject(new Error(`Failed to load kora config (exit ${code ?? "unknown"}): ${stderr.trim()}`));
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function toConfigObject(mod) {
|
|
460
|
+
if (typeof mod !== "object" || mod === null) {
|
|
461
|
+
throw new Error("kora config must export an object.");
|
|
462
|
+
}
|
|
463
|
+
const value = mod.default ?? mod;
|
|
464
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
465
|
+
throw new Error("kora config must export an object.");
|
|
466
|
+
}
|
|
467
|
+
return value;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// src/commands/migrate/schema-loader.ts
|
|
471
|
+
var import_node_child_process2 = require("child_process");
|
|
472
|
+
var import_node_path4 = require("path");
|
|
473
|
+
var import_node_url2 = require("url");
|
|
474
|
+
async function loadSchemaDefinition(schemaPath, projectRoot) {
|
|
475
|
+
const ext = (0, import_node_path4.extname)(schemaPath);
|
|
476
|
+
const moduleValue = ext === ".ts" || ext === ".mts" || ext === ".cts" ? await loadTypeScriptModule(schemaPath, projectRoot) : await import(`${(0, import_node_url2.pathToFileURL)(schemaPath).href}?t=${Date.now()}-${Math.random()}`);
|
|
477
|
+
return extractSchema(moduleValue);
|
|
478
|
+
}
|
|
479
|
+
function extractSchema(value) {
|
|
480
|
+
if (typeof value !== "object" || value === null) {
|
|
481
|
+
throw new Error("Schema module must export an object.");
|
|
482
|
+
}
|
|
483
|
+
const moduleRecord = value;
|
|
484
|
+
const candidate = moduleRecord.default ?? moduleRecord;
|
|
485
|
+
if (!isSchemaDefinition(candidate)) {
|
|
486
|
+
throw new Error("Schema module must export a valid SchemaDefinition as default export.");
|
|
487
|
+
}
|
|
488
|
+
return candidate;
|
|
489
|
+
}
|
|
490
|
+
function isSchemaDefinition(value) {
|
|
491
|
+
if (typeof value !== "object" || value === null) return false;
|
|
492
|
+
const object = value;
|
|
493
|
+
return typeof object.version === "number" && typeof object.collections === "object" && object.collections !== null && typeof object.relations === "object" && object.relations !== null;
|
|
494
|
+
}
|
|
495
|
+
async function loadTypeScriptModule(schemaPath, projectRoot) {
|
|
496
|
+
if (!await hasTsxInstalled(projectRoot)) {
|
|
497
|
+
throw new Error(
|
|
498
|
+
`Schema file is TypeScript (${schemaPath}) but local "tsx" was not found. Install tsx in the project.`
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
const script = "const modulePath = process.argv[process.argv.length - 1];import('node:url').then(u => import(u.pathToFileURL(modulePath).href)).then(mod => { const v = mod.default ?? mod; process.stdout.write(JSON.stringify(v)) }).catch(e => { process.stderr.write(String(e)); process.exit(1) })";
|
|
502
|
+
const output = await runCommand2(
|
|
503
|
+
process.execPath,
|
|
504
|
+
["--import", "tsx", "--eval", script, schemaPath],
|
|
505
|
+
projectRoot
|
|
506
|
+
);
|
|
507
|
+
try {
|
|
508
|
+
return JSON.parse(output);
|
|
509
|
+
} catch {
|
|
510
|
+
throw new Error(`Failed to parse schema module output for ${schemaPath}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async function runCommand2(command, args, cwd) {
|
|
514
|
+
return await new Promise((resolve11, reject) => {
|
|
515
|
+
const child = (0, import_node_child_process2.spawn)(command, args, {
|
|
516
|
+
cwd,
|
|
517
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
518
|
+
env: process.env
|
|
519
|
+
});
|
|
520
|
+
let stdout = "";
|
|
521
|
+
let stderr = "";
|
|
522
|
+
child.stdout?.on("data", (chunk) => {
|
|
523
|
+
stdout += chunk.toString("utf-8");
|
|
524
|
+
});
|
|
525
|
+
child.stderr?.on("data", (chunk) => {
|
|
526
|
+
stderr += chunk.toString("utf-8");
|
|
527
|
+
});
|
|
528
|
+
child.on("error", (error) => {
|
|
529
|
+
reject(error);
|
|
530
|
+
});
|
|
531
|
+
child.on("exit", (code) => {
|
|
532
|
+
if (code === 0) {
|
|
533
|
+
resolve11(stdout.trim());
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
reject(
|
|
537
|
+
new Error(`Failed to load TypeScript schema (exit ${code ?? "unknown"}): ${stderr.trim()}`)
|
|
538
|
+
);
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/commands/compact/compact-command.ts
|
|
544
|
+
var compactCommand = (0, import_citty2.defineCommand)({
|
|
545
|
+
meta: {
|
|
546
|
+
name: "compact",
|
|
547
|
+
description: "Compact the local Kora operation log after server ack"
|
|
548
|
+
},
|
|
549
|
+
args: {
|
|
550
|
+
db: {
|
|
551
|
+
type: "string",
|
|
552
|
+
description: "Path to the local SQLite database (required)"
|
|
553
|
+
},
|
|
554
|
+
schema: {
|
|
555
|
+
type: "string",
|
|
556
|
+
description: "Path to schema file"
|
|
557
|
+
},
|
|
558
|
+
strategy: {
|
|
559
|
+
type: "string",
|
|
560
|
+
description: "Compaction strategy: after-ack | after-days | never",
|
|
561
|
+
default: "after-ack"
|
|
562
|
+
},
|
|
563
|
+
days: {
|
|
564
|
+
type: "string",
|
|
565
|
+
description: "Age threshold when strategy is after-days",
|
|
566
|
+
default: "30"
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
async run({ args }) {
|
|
570
|
+
const logger = createLogger();
|
|
571
|
+
const projectRoot = await findProjectRoot();
|
|
572
|
+
if (!projectRoot) {
|
|
573
|
+
throw new InvalidProjectError(process.cwd());
|
|
574
|
+
}
|
|
575
|
+
const dbPath = typeof args.db === "string" ? (0, import_node_path5.resolve)(projectRoot, args.db) : void 0;
|
|
576
|
+
if (!dbPath) {
|
|
577
|
+
throw new Error("Missing --db <path> to the local SQLite database file.");
|
|
578
|
+
}
|
|
579
|
+
const config = await loadKoraConfig(projectRoot);
|
|
580
|
+
const schemaPath = typeof args.schema === "string" ? (0, import_node_path5.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path5.resolve)(projectRoot, config.schema) : void 0;
|
|
581
|
+
if (!schemaPath) {
|
|
582
|
+
throw new Error("Could not resolve schema path. Pass --schema <path>.");
|
|
583
|
+
}
|
|
584
|
+
const schema = await loadSchemaDefinition(schemaPath, projectRoot);
|
|
585
|
+
const { BetterSqlite3Adapter } = await import("@korajs/store/better-sqlite3");
|
|
586
|
+
const { Store } = await import("@korajs/store");
|
|
587
|
+
const strategyName = typeof args.strategy === "string" ? args.strategy : "after-ack";
|
|
588
|
+
const days = Number.parseInt(typeof args.days === "string" ? args.days : "30", 10);
|
|
589
|
+
const store = new Store({
|
|
590
|
+
schema,
|
|
591
|
+
adapter: new BetterSqlite3Adapter(dbPath)
|
|
592
|
+
});
|
|
593
|
+
await store.open();
|
|
594
|
+
let result;
|
|
595
|
+
if (strategyName === "never") {
|
|
596
|
+
result = await store.compact({ mode: "never" });
|
|
597
|
+
} else if (strategyName === "after-days") {
|
|
598
|
+
result = await store.compact({ mode: "after-days", days });
|
|
599
|
+
} else if (strategyName === "after-ack") {
|
|
600
|
+
result = await store.compact({ mode: "after-ack" });
|
|
601
|
+
} else {
|
|
602
|
+
await store.close();
|
|
603
|
+
throw new Error(`Unknown strategy "${strategyName}". Use after-ack, after-days, or never.`);
|
|
604
|
+
}
|
|
605
|
+
await store.close();
|
|
606
|
+
logger.banner();
|
|
607
|
+
logger.success(`Compacted ${result.deletedCount} operation log entries.`);
|
|
608
|
+
for (const [nodeId, seq] of result.watermark) {
|
|
609
|
+
logger.step(` ${nodeId}: seq <= ${seq}`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
});
|
|
27
613
|
|
|
28
614
|
// src/commands/create/create-command.ts
|
|
29
|
-
var
|
|
615
|
+
var import_node_child_process4 = require("child_process");
|
|
30
616
|
var import_node_fs2 = require("fs");
|
|
31
|
-
var
|
|
32
|
-
var
|
|
33
|
-
var
|
|
34
|
-
|
|
35
|
-
// src/errors.ts
|
|
36
|
-
var import_core = require("@korajs/core");
|
|
37
|
-
var ProjectExistsError = class extends import_core.KoraError {
|
|
38
|
-
constructor(directory) {
|
|
39
|
-
super(
|
|
40
|
-
`Directory "${directory}" already exists. Choose a different name or remove the existing directory.`,
|
|
41
|
-
"PROJECT_EXISTS",
|
|
42
|
-
{ directory }
|
|
43
|
-
);
|
|
44
|
-
this.directory = directory;
|
|
45
|
-
this.name = "ProjectExistsError";
|
|
46
|
-
}
|
|
47
|
-
directory;
|
|
48
|
-
};
|
|
49
|
-
var SchemaNotFoundError = class extends import_core.KoraError {
|
|
50
|
-
constructor(searchedPaths) {
|
|
51
|
-
super(
|
|
52
|
-
`Could not find a schema file. Searched: ${searchedPaths.join(", ")}. Create a schema file using defineSchema() from @korajs/core.`,
|
|
53
|
-
"SCHEMA_NOT_FOUND",
|
|
54
|
-
{ searchedPaths }
|
|
55
|
-
);
|
|
56
|
-
this.searchedPaths = searchedPaths;
|
|
57
|
-
this.name = "SchemaNotFoundError";
|
|
58
|
-
}
|
|
59
|
-
searchedPaths;
|
|
60
|
-
};
|
|
61
|
-
var InvalidProjectError = class extends import_core.KoraError {
|
|
62
|
-
constructor(directory) {
|
|
63
|
-
super(
|
|
64
|
-
`"${directory}" is not a valid Kora project. No package.json with a kora dependency found. Run this command from inside a Kora project.`,
|
|
65
|
-
"INVALID_PROJECT",
|
|
66
|
-
{ directory }
|
|
67
|
-
);
|
|
68
|
-
this.directory = directory;
|
|
69
|
-
this.name = "InvalidProjectError";
|
|
70
|
-
}
|
|
71
|
-
directory;
|
|
72
|
-
};
|
|
73
|
-
var DevServerError = class extends import_core.KoraError {
|
|
74
|
-
constructor(binary, searchPath) {
|
|
75
|
-
super(
|
|
76
|
-
`Could not find required binary "${binary}" at ${searchPath}. Install project dependencies and try again.`,
|
|
77
|
-
"DEV_SERVER_ERROR",
|
|
78
|
-
{ binary, searchPath }
|
|
79
|
-
);
|
|
80
|
-
this.binary = binary;
|
|
81
|
-
this.searchPath = searchPath;
|
|
82
|
-
this.name = "DevServerError";
|
|
83
|
-
}
|
|
84
|
-
binary;
|
|
85
|
-
searchPath;
|
|
86
|
-
};
|
|
617
|
+
var import_node_path9 = require("path");
|
|
618
|
+
var import_node_url4 = require("url");
|
|
619
|
+
var import_citty3 = require("citty");
|
|
87
620
|
|
|
88
621
|
// src/prompts/preferences.ts
|
|
89
622
|
var import_conf = __toESM(require("conf"), 1);
|
|
@@ -128,18 +661,18 @@ var import_prompts = require("@clack/prompts");
|
|
|
128
661
|
// src/utils/prompt.ts
|
|
129
662
|
var import_node_readline = require("readline");
|
|
130
663
|
function promptText(message, defaultValue, options) {
|
|
131
|
-
return new Promise((
|
|
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,17 +710,17 @@ function promptConfirm(message, defaultValue = false, options) {
|
|
|
177
710
|
const normalized = answer.trim().toLowerCase();
|
|
178
711
|
if (normalized.length === 0) {
|
|
179
712
|
rl.close();
|
|
180
|
-
|
|
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
|
}
|
|
193
726
|
;
|
|
@@ -300,132 +833,8 @@ var TEMPLATES = [
|
|
|
300
833
|
"tauri-react"
|
|
301
834
|
];
|
|
302
835
|
|
|
303
|
-
// src/utils/fs-helpers.ts
|
|
304
|
-
var import_promises = require("fs/promises");
|
|
305
|
-
var import_node_path = require("path");
|
|
306
|
-
async function directoryExists(path) {
|
|
307
|
-
try {
|
|
308
|
-
await (0, import_promises.access)(path);
|
|
309
|
-
return true;
|
|
310
|
-
} catch {
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
async function findProjectRoot(startDir) {
|
|
315
|
-
let current = (0, import_node_path.resolve)(startDir ?? process.cwd());
|
|
316
|
-
for (; ; ) {
|
|
317
|
-
const pkgPath = (0, import_node_path.join)(current, "package.json");
|
|
318
|
-
try {
|
|
319
|
-
const content = await (0, import_promises.readFile)(pkgPath, "utf-8");
|
|
320
|
-
const pkg = JSON.parse(content);
|
|
321
|
-
if (isKoraProject(pkg)) {
|
|
322
|
-
return current;
|
|
323
|
-
}
|
|
324
|
-
} catch {
|
|
325
|
-
}
|
|
326
|
-
const parent = (0, import_node_path.dirname)(current);
|
|
327
|
-
if (parent === current) break;
|
|
328
|
-
current = parent;
|
|
329
|
-
}
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
async function findSchemaFile(projectRoot) {
|
|
333
|
-
const candidates = [
|
|
334
|
-
(0, import_node_path.join)(projectRoot, "src", "schema.ts"),
|
|
335
|
-
(0, import_node_path.join)(projectRoot, "schema.ts"),
|
|
336
|
-
(0, import_node_path.join)(projectRoot, "src", "schema.js"),
|
|
337
|
-
(0, import_node_path.join)(projectRoot, "schema.js")
|
|
338
|
-
];
|
|
339
|
-
for (const candidate of candidates) {
|
|
340
|
-
try {
|
|
341
|
-
await (0, import_promises.access)(candidate);
|
|
342
|
-
return candidate;
|
|
343
|
-
} catch {
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
return null;
|
|
347
|
-
}
|
|
348
|
-
async function hasTsxInstalled(projectRoot) {
|
|
349
|
-
try {
|
|
350
|
-
await (0, import_promises.access)((0, import_node_path.join)(projectRoot, "node_modules", "tsx", "package.json"));
|
|
351
|
-
return true;
|
|
352
|
-
} catch {
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
async function resolveProjectBinaryEntryPoint(projectRoot, packageName, binaryName) {
|
|
357
|
-
const pkgJsonPath = (0, import_node_path.join)(projectRoot, "node_modules", packageName, "package.json");
|
|
358
|
-
try {
|
|
359
|
-
const content = await (0, import_promises.readFile)(pkgJsonPath, "utf-8");
|
|
360
|
-
const pkg = JSON.parse(content);
|
|
361
|
-
let binPath;
|
|
362
|
-
if (typeof pkg.bin === "string") {
|
|
363
|
-
binPath = pkg.bin;
|
|
364
|
-
} else if (typeof pkg.bin === "object" && pkg.bin !== null) {
|
|
365
|
-
binPath = pkg.bin[binaryName];
|
|
366
|
-
}
|
|
367
|
-
if (!binPath) return null;
|
|
368
|
-
const fullPath = (0, import_node_path.join)(projectRoot, "node_modules", packageName, binPath);
|
|
369
|
-
await (0, import_promises.access)(fullPath);
|
|
370
|
-
return fullPath;
|
|
371
|
-
} catch {
|
|
372
|
-
return null;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
function isKoraProject(pkg) {
|
|
376
|
-
if (typeof pkg !== "object" || pkg === null) return false;
|
|
377
|
-
const record = pkg;
|
|
378
|
-
return hasKoraDep(record.dependencies) || hasKoraDep(record.devDependencies);
|
|
379
|
-
}
|
|
380
|
-
function hasKoraDep(deps) {
|
|
381
|
-
if (typeof deps !== "object" || deps === null) return false;
|
|
382
|
-
return Object.keys(deps).some((key) => key === "kora" || key.startsWith("@korajs/"));
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// src/utils/logger.ts
|
|
386
|
-
var RESET = "\x1B[0m";
|
|
387
|
-
var BOLD = "\x1B[1m";
|
|
388
|
-
var DIM = "\x1B[2m";
|
|
389
|
-
var GREEN = "\x1B[32m";
|
|
390
|
-
var YELLOW = "\x1B[33m";
|
|
391
|
-
var RED = "\x1B[31m";
|
|
392
|
-
var CYAN = "\x1B[36m";
|
|
393
|
-
function createLogger(options) {
|
|
394
|
-
const colorDisabled = options?.noColor === true || process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
395
|
-
function color(code, text) {
|
|
396
|
-
return colorDisabled ? text : `${code}${text}${RESET}`;
|
|
397
|
-
}
|
|
398
|
-
return {
|
|
399
|
-
info(message) {
|
|
400
|
-
console.log(color(CYAN, message));
|
|
401
|
-
},
|
|
402
|
-
success(message) {
|
|
403
|
-
console.log(color(GREEN, ` \u2713 ${message}`));
|
|
404
|
-
},
|
|
405
|
-
warn(message) {
|
|
406
|
-
console.warn(color(YELLOW, ` \u26A0 ${message}`));
|
|
407
|
-
},
|
|
408
|
-
error(message) {
|
|
409
|
-
console.error(color(RED, ` \u2717 ${message}`));
|
|
410
|
-
},
|
|
411
|
-
step(message) {
|
|
412
|
-
console.log(color(DIM, ` ${message}`));
|
|
413
|
-
},
|
|
414
|
-
blank() {
|
|
415
|
-
console.log();
|
|
416
|
-
},
|
|
417
|
-
banner() {
|
|
418
|
-
console.log();
|
|
419
|
-
console.log(
|
|
420
|
-
color(BOLD + CYAN, " Kora.js") + color(DIM, " \u2014 Offline-first application framework")
|
|
421
|
-
);
|
|
422
|
-
console.log();
|
|
423
|
-
}
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
|
|
427
836
|
// src/utils/package-manager.ts
|
|
428
|
-
var
|
|
837
|
+
var import_node_child_process3 = require("child_process");
|
|
429
838
|
function detectPackageManager() {
|
|
430
839
|
const userAgent = process.env.npm_config_user_agent;
|
|
431
840
|
if (!userAgent) return "npm";
|
|
@@ -443,8 +852,8 @@ function getRunDevCommand(pm) {
|
|
|
443
852
|
}
|
|
444
853
|
|
|
445
854
|
// src/commands/create/environment-detection.ts
|
|
446
|
-
var
|
|
447
|
-
var
|
|
855
|
+
var import_promises4 = require("fs/promises");
|
|
856
|
+
var import_node_path6 = require("path");
|
|
448
857
|
function detectEditorFromEnvironment(env = process.env) {
|
|
449
858
|
const termProgram = String(env.TERM_PROGRAM ?? "").toLowerCase();
|
|
450
859
|
const editorValue = `${String(env.EDITOR ?? "")} ${String(env.VISUAL ?? "")}`.toLowerCase();
|
|
@@ -470,18 +879,18 @@ async function detectGitContext(startDir) {
|
|
|
470
879
|
};
|
|
471
880
|
}
|
|
472
881
|
async function detectMonorepoContext(startDir) {
|
|
473
|
-
let current = (0,
|
|
882
|
+
let current = (0, import_node_path6.resolve)(startDir);
|
|
474
883
|
for (; ; ) {
|
|
475
|
-
if (await fileExists((0,
|
|
884
|
+
if (await fileExists((0, import_node_path6.join)(current, "pnpm-workspace.yaml"))) {
|
|
476
885
|
return { isMonorepo: true, root: current, kind: "pnpm-workspace" };
|
|
477
886
|
}
|
|
478
|
-
if (await fileExists((0,
|
|
887
|
+
if (await fileExists((0, import_node_path6.join)(current, "turbo.json"))) {
|
|
479
888
|
return { isMonorepo: true, root: current, kind: "turborepo" };
|
|
480
889
|
}
|
|
481
|
-
const packageJsonPath = (0,
|
|
890
|
+
const packageJsonPath = (0, import_node_path6.join)(current, "package.json");
|
|
482
891
|
if (await fileExists(packageJsonPath)) {
|
|
483
892
|
try {
|
|
484
|
-
const packageJsonRaw = await (0,
|
|
893
|
+
const packageJsonRaw = await (0, import_promises4.readFile)(packageJsonPath, "utf-8");
|
|
485
894
|
const parsed = JSON.parse(packageJsonRaw);
|
|
486
895
|
if (Array.isArray(parsed.workspaces) || isNpmWorkspaceObject(parsed.workspaces)) {
|
|
487
896
|
return { isMonorepo: true, root: current, kind: "npm-workspaces" };
|
|
@@ -489,7 +898,7 @@ async function detectMonorepoContext(startDir) {
|
|
|
489
898
|
} catch {
|
|
490
899
|
}
|
|
491
900
|
}
|
|
492
|
-
const parent = (0,
|
|
901
|
+
const parent = (0, import_node_path6.dirname)(current);
|
|
493
902
|
if (parent === current) {
|
|
494
903
|
return { isMonorepo: false, root: null, kind: "none" };
|
|
495
904
|
}
|
|
@@ -501,9 +910,9 @@ async function applyEditorWorkspacePreset(params) {
|
|
|
501
910
|
if (editor !== "vscode" && editor !== "cursor" && editor !== "windsurf") {
|
|
502
911
|
return { applied: false, filePath: null };
|
|
503
912
|
}
|
|
504
|
-
const vscodeDir = (0,
|
|
505
|
-
const extensionsPath = (0,
|
|
506
|
-
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 });
|
|
507
916
|
const recommendations = ["korajs.kora-devtools"];
|
|
508
917
|
const existing = await readJsonObject(extensionsPath);
|
|
509
918
|
const existingRecommendations = Array.isArray(existing?.recommendations) ? existing.recommendations.filter((item) => typeof item === "string") : [];
|
|
@@ -511,19 +920,19 @@ async function applyEditorWorkspacePreset(params) {
|
|
|
511
920
|
const next = {
|
|
512
921
|
recommendations: mergedRecommendations
|
|
513
922
|
};
|
|
514
|
-
await (0,
|
|
923
|
+
await (0, import_promises4.writeFile)(extensionsPath, `${JSON.stringify(next, null, 2)}
|
|
515
924
|
`, "utf-8");
|
|
516
925
|
return { applied: true, filePath: extensionsPath };
|
|
517
926
|
}
|
|
518
927
|
function resolveMonorepoTargetDirectory(monorepoRoot, projectName) {
|
|
519
|
-
return (0,
|
|
928
|
+
return (0, import_node_path6.join)(monorepoRoot, "packages", projectName);
|
|
520
929
|
}
|
|
521
930
|
function dedupeStrings(values) {
|
|
522
931
|
return Array.from(new Set(values));
|
|
523
932
|
}
|
|
524
933
|
async function readJsonObject(path) {
|
|
525
934
|
try {
|
|
526
|
-
const content = await (0,
|
|
935
|
+
const content = await (0, import_promises4.readFile)(path, "utf-8");
|
|
527
936
|
const parsed = JSON.parse(content);
|
|
528
937
|
if (typeof parsed === "object" && parsed !== null) {
|
|
529
938
|
return parsed;
|
|
@@ -540,22 +949,22 @@ function isNpmWorkspaceObject(value) {
|
|
|
540
949
|
}
|
|
541
950
|
async function fileExists(path) {
|
|
542
951
|
try {
|
|
543
|
-
await (0,
|
|
952
|
+
await (0, import_promises4.access)(path);
|
|
544
953
|
return true;
|
|
545
954
|
} catch {
|
|
546
955
|
return false;
|
|
547
956
|
}
|
|
548
957
|
}
|
|
549
958
|
async function findNearestAncestorWithEntry(startDir, entryName) {
|
|
550
|
-
let current = (0,
|
|
959
|
+
let current = (0, import_node_path6.resolve)(startDir);
|
|
551
960
|
for (; ; ) {
|
|
552
|
-
const candidate = (0,
|
|
961
|
+
const candidate = (0, import_node_path6.join)(current, entryName);
|
|
553
962
|
try {
|
|
554
|
-
await (0,
|
|
963
|
+
await (0, import_promises4.stat)(candidate);
|
|
555
964
|
return current;
|
|
556
965
|
} catch {
|
|
557
966
|
}
|
|
558
|
-
const parent = (0,
|
|
967
|
+
const parent = (0, import_node_path6.dirname)(current);
|
|
559
968
|
if (parent === current) {
|
|
560
969
|
return null;
|
|
561
970
|
}
|
|
@@ -626,10 +1035,49 @@ async function resolveCreatePreferencesFlow(params) {
|
|
|
626
1035
|
if (isTauri) {
|
|
627
1036
|
effective.framework = "react";
|
|
628
1037
|
effective.tailwind = false;
|
|
629
|
-
effective.sync = true;
|
|
630
|
-
effective.db = "sqlite";
|
|
631
|
-
effective.dbProvider = "none";
|
|
632
1038
|
effective.auth = "none";
|
|
1039
|
+
if (flags.sync !== void 0) {
|
|
1040
|
+
effective.sync = flags.sync;
|
|
1041
|
+
} else if (!flags.useDefaults && !usedStoredPreferences) {
|
|
1042
|
+
effective.sync = await prompts.confirm("Enable multi-device sync?", true);
|
|
1043
|
+
}
|
|
1044
|
+
if (flags.db !== void 0) {
|
|
1045
|
+
if (!isDatabaseValue(flags.db)) {
|
|
1046
|
+
throw new Error(
|
|
1047
|
+
`Invalid --db value "${flags.db}". Expected one of: none, sqlite, postgres.`
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
effective.db = flags.db;
|
|
1051
|
+
} else if (!effective.sync) {
|
|
1052
|
+
effective.db = "none";
|
|
1053
|
+
} else if (!flags.useDefaults && !usedStoredPreferences) {
|
|
1054
|
+
effective.db = await prompts.select("Sync server database:", [
|
|
1055
|
+
{ label: "SQLite (zero-config; local, LAN, small teams)", value: "sqlite" },
|
|
1056
|
+
{ label: "PostgreSQL (production-scale)", value: "postgres" }
|
|
1057
|
+
]);
|
|
1058
|
+
}
|
|
1059
|
+
if (effective.db === "none") {
|
|
1060
|
+
effective.sync = false;
|
|
1061
|
+
}
|
|
1062
|
+
if (effective.db !== "postgres") {
|
|
1063
|
+
effective.dbProvider = "none";
|
|
1064
|
+
} else if (flags.dbProvider !== void 0) {
|
|
1065
|
+
if (!isDatabaseProviderValue(flags.dbProvider)) {
|
|
1066
|
+
throw new Error(
|
|
1067
|
+
`Invalid --db-provider value "${flags.dbProvider}". Expected one of: none, local, supabase, neon, railway, vercel-postgres, custom.`
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
effective.dbProvider = flags.dbProvider;
|
|
1071
|
+
} else if (!flags.useDefaults && !usedStoredPreferences) {
|
|
1072
|
+
effective.dbProvider = await prompts.select("Database provider:", [
|
|
1073
|
+
{ label: "Local Postgres", value: "local" },
|
|
1074
|
+
{ label: "Supabase", value: "supabase" },
|
|
1075
|
+
{ label: "Neon", value: "neon" },
|
|
1076
|
+
{ label: "Railway", value: "railway" },
|
|
1077
|
+
{ label: "Vercel Postgres", value: "vercel-postgres" },
|
|
1078
|
+
{ label: "Custom connection string", value: "custom" }
|
|
1079
|
+
]);
|
|
1080
|
+
}
|
|
633
1081
|
} else {
|
|
634
1082
|
if (flags.framework !== void 0) {
|
|
635
1083
|
if (!isFrameworkValue(flags.framework)) {
|
|
@@ -778,8 +1226,8 @@ function validateProjectName(name) {
|
|
|
778
1226
|
}
|
|
779
1227
|
|
|
780
1228
|
// src/commands/create/sync-provider-preset.ts
|
|
781
|
-
var
|
|
782
|
-
var
|
|
1229
|
+
var import_promises5 = require("fs/promises");
|
|
1230
|
+
var import_node_path7 = require("path");
|
|
783
1231
|
async function applySyncProviderPreset(options) {
|
|
784
1232
|
if (!isSyncTemplate(options.template)) {
|
|
785
1233
|
return;
|
|
@@ -789,64 +1237,32 @@ async function applySyncProviderPreset(options) {
|
|
|
789
1237
|
}
|
|
790
1238
|
const providerName = getProviderDisplayName(options.dbProvider);
|
|
791
1239
|
const providerConnectionString = getProviderConnectionStringExample(options.dbProvider);
|
|
792
|
-
const
|
|
793
|
-
const
|
|
794
|
-
const
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
"// Ensure DATABASE_URL is set in your environment.",
|
|
800
|
-
"",
|
|
801
|
-
"async function start(): Promise<void> {",
|
|
802
|
-
" const connectionString = process.env.DATABASE_URL || ''",
|
|
803
|
-
" if (connectionString.length === 0) {",
|
|
804
|
-
" throw new Error('DATABASE_URL is required for PostgreSQL sync server store.')",
|
|
805
|
-
" }",
|
|
806
|
-
"",
|
|
807
|
-
" const store = await createPostgresServerStore({ connectionString })",
|
|
808
|
-
" const server = createProductionServer({",
|
|
809
|
-
" store,",
|
|
810
|
-
" port: Number(process.env.PORT) || 3001,",
|
|
811
|
-
" staticDir: './dist',",
|
|
812
|
-
" syncPath: '/kora-sync',",
|
|
813
|
-
" })",
|
|
814
|
-
"",
|
|
815
|
-
" const url = await server.start()",
|
|
816
|
-
" console.log(`Kora app running at ${url}`)",
|
|
817
|
-
"}",
|
|
818
|
-
"",
|
|
819
|
-
"void start()",
|
|
820
|
-
""
|
|
821
|
-
].join("\n");
|
|
822
|
-
const envTemplate = [
|
|
823
|
-
"# Kora Sync Server",
|
|
824
|
-
"# WebSocket URL for the sync server (used by the client)",
|
|
825
|
-
"VITE_SYNC_URL=ws://localhost:3001",
|
|
826
|
-
"",
|
|
827
|
-
"# Sync server port",
|
|
828
|
-
"PORT=3001",
|
|
1240
|
+
const envPath = (0, import_node_path7.join)(options.targetDir, ".env.example");
|
|
1241
|
+
const readmePath = (0, import_node_path7.join)(options.targetDir, "README.md");
|
|
1242
|
+
const existingReadme = await (0, import_promises5.readFile)(readmePath, "utf-8");
|
|
1243
|
+
const existingEnv = await (0, import_promises5.readFile)(envPath, "utf-8");
|
|
1244
|
+
const trimmedReadme = existingReadme.trimEnd();
|
|
1245
|
+
const trimmedEnv = existingEnv.trimEnd();
|
|
1246
|
+
const envSuffix = [
|
|
829
1247
|
"",
|
|
830
|
-
`# PostgreSQL
|
|
831
|
-
`# Example: ${providerConnectionString}
|
|
832
|
-
"DATABASE_URL=",
|
|
833
|
-
""
|
|
1248
|
+
`# PostgreSQL provider preset: ${providerName}`,
|
|
1249
|
+
`# Example: ${providerConnectionString}`
|
|
834
1250
|
].join("\n");
|
|
835
|
-
const existingReadme = await (0, import_promises3.readFile)(readmePath, "utf-8");
|
|
836
|
-
const trimmedReadme = existingReadme.trimEnd();
|
|
837
1251
|
const readmeSuffix = [
|
|
838
1252
|
"",
|
|
839
1253
|
"## PostgreSQL Provider Preset",
|
|
840
1254
|
"",
|
|
841
1255
|
`Selected DB provider: ${options.dbProvider}`,
|
|
842
1256
|
"",
|
|
843
|
-
"This scaffold
|
|
1257
|
+
"This scaffold keeps one sync server entrypoint. When `DATABASE_URL` is set, `server.ts` uses PostgreSQL. When it is empty, the same server uses SQLite at `KORA_SERVER_DB`.",
|
|
1258
|
+
"",
|
|
1259
|
+
"The generated server uses `createPostgresServerStore` automatically when `DATABASE_URL` is present.",
|
|
844
1260
|
""
|
|
845
1261
|
].join("\n");
|
|
846
1262
|
const readmeTemplate = `${trimmedReadme}${readmeSuffix}`;
|
|
847
|
-
await (0,
|
|
848
|
-
|
|
849
|
-
await (0,
|
|
1263
|
+
await (0, import_promises5.writeFile)(envPath, `${trimmedEnv}${envSuffix}
|
|
1264
|
+
`, "utf-8");
|
|
1265
|
+
await (0, import_promises5.writeFile)(readmePath, readmeTemplate, "utf-8");
|
|
850
1266
|
}
|
|
851
1267
|
function isSyncTemplate(template) {
|
|
852
1268
|
return template === "react-sync" || template === "react-tailwind-sync" || template === "tauri-react";
|
|
@@ -890,9 +1306,9 @@ function getProviderConnectionStringExample(provider) {
|
|
|
890
1306
|
|
|
891
1307
|
// src/templates/composer.ts
|
|
892
1308
|
var import_node_fs = require("fs");
|
|
893
|
-
var
|
|
894
|
-
var
|
|
895
|
-
var
|
|
1309
|
+
var import_promises6 = require("fs/promises");
|
|
1310
|
+
var import_node_path8 = require("path");
|
|
1311
|
+
var import_node_url3 = require("url");
|
|
896
1312
|
var import_meta = {};
|
|
897
1313
|
function substituteVariables(template, context) {
|
|
898
1314
|
return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
@@ -901,16 +1317,16 @@ function substituteVariables(template, context) {
|
|
|
901
1317
|
});
|
|
902
1318
|
}
|
|
903
1319
|
function getTemplatePath(templateName) {
|
|
904
|
-
let dir = (0,
|
|
1320
|
+
let dir = (0, import_node_path8.dirname)((0, import_node_url3.fileURLToPath)(import_meta.url));
|
|
905
1321
|
for (let i = 0; i < 7; i++) {
|
|
906
|
-
const candidate = (0,
|
|
1322
|
+
const candidate = (0, import_node_path8.resolve)(dir, "templates", templateName);
|
|
907
1323
|
if ((0, import_node_fs.existsSync)(candidate)) {
|
|
908
1324
|
return candidate;
|
|
909
1325
|
}
|
|
910
|
-
dir = (0,
|
|
1326
|
+
dir = (0, import_node_path8.dirname)(dir);
|
|
911
1327
|
}
|
|
912
|
-
const currentDir = (0,
|
|
913
|
-
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);
|
|
914
1330
|
}
|
|
915
1331
|
function createCompatibilityLayerPlan(templateName) {
|
|
916
1332
|
const baseLayer = { category: "base", name: "base", sourceTemplate: "react-basic" };
|
|
@@ -995,22 +1411,22 @@ async function composeTemplateLayers(plan, targetDir, context) {
|
|
|
995
1411
|
}
|
|
996
1412
|
}
|
|
997
1413
|
async function copyDirectory(src, dest, vars) {
|
|
998
|
-
await (0,
|
|
999
|
-
const entries = await (0,
|
|
1414
|
+
await (0, import_promises6.mkdir)(dest, { recursive: true });
|
|
1415
|
+
const entries = await (0, import_promises6.readdir)(src);
|
|
1000
1416
|
for (const entry of entries) {
|
|
1001
|
-
const srcPath = (0,
|
|
1002
|
-
const srcStat = await (0,
|
|
1417
|
+
const srcPath = (0, import_node_path8.join)(src, entry);
|
|
1418
|
+
const srcStat = await (0, import_promises6.stat)(srcPath);
|
|
1003
1419
|
if (srcStat.isDirectory()) {
|
|
1004
|
-
await copyDirectory(srcPath, (0,
|
|
1420
|
+
await copyDirectory(srcPath, (0, import_node_path8.join)(dest, entry), vars);
|
|
1005
1421
|
continue;
|
|
1006
1422
|
}
|
|
1007
1423
|
if (entry.endsWith(".hbs")) {
|
|
1008
|
-
const content = await (0,
|
|
1424
|
+
const content = await (0, import_promises6.readFile)(srcPath, "utf-8");
|
|
1009
1425
|
const outputName = entry.slice(0, -4);
|
|
1010
|
-
await (0,
|
|
1426
|
+
await (0, import_promises6.writeFile)((0, import_node_path8.join)(dest, outputName), substituteVariables(content, vars), "utf-8");
|
|
1011
1427
|
continue;
|
|
1012
1428
|
}
|
|
1013
|
-
await (0,
|
|
1429
|
+
await (0, import_promises6.copyFile)(srcPath, (0, import_node_path8.join)(dest, entry));
|
|
1014
1430
|
}
|
|
1015
1431
|
}
|
|
1016
1432
|
|
|
@@ -1022,7 +1438,7 @@ async function scaffoldTemplate(templateName, targetDir, context) {
|
|
|
1022
1438
|
|
|
1023
1439
|
// src/commands/create/create-command.ts
|
|
1024
1440
|
var import_meta2 = {};
|
|
1025
|
-
var createCommand = (0,
|
|
1441
|
+
var createCommand = (0, import_citty3.defineCommand)({
|
|
1026
1442
|
meta: {
|
|
1027
1443
|
name: "create",
|
|
1028
1444
|
description: "Create a new Kora application"
|
|
@@ -1161,7 +1577,7 @@ var createCommand = (0, import_citty.defineCommand)({
|
|
|
1161
1577
|
const editorDetection = detectEditorFromEnvironment();
|
|
1162
1578
|
const gitContext = await detectGitContext(process.cwd());
|
|
1163
1579
|
const monorepoContext = await detectMonorepoContext(process.cwd());
|
|
1164
|
-
let targetDir = (0,
|
|
1580
|
+
let targetDir = (0, import_node_path9.resolve)(process.cwd(), projectName);
|
|
1165
1581
|
if (!useDefaults && monorepoContext.isMonorepo && monorepoContext.root !== null) {
|
|
1166
1582
|
const useWorkspaceTarget = await prompts.confirm(
|
|
1167
1583
|
`Detected ${formatMonorepoKind(monorepoContext.kind)} at ${monorepoContext.root}. Create app under packages/${projectName}?`,
|
|
@@ -1231,7 +1647,7 @@ var createCommand = (0, import_citty.defineCommand)({
|
|
|
1231
1647
|
if (!args["skip-install"]) {
|
|
1232
1648
|
logger.step("Installing dependencies...");
|
|
1233
1649
|
try {
|
|
1234
|
-
(0,
|
|
1650
|
+
(0, import_node_child_process4.execSync)(getInstallCommand(pm), { cwd: targetDir, stdio: "inherit" });
|
|
1235
1651
|
logger.success("Dependencies installed");
|
|
1236
1652
|
} catch {
|
|
1237
1653
|
logger.warn("Failed to install dependencies. Run install manually.");
|
|
@@ -1331,9 +1747,9 @@ function relativeToTarget(targetDir, filePath) {
|
|
|
1331
1747
|
}
|
|
1332
1748
|
function resolveKoraVersion() {
|
|
1333
1749
|
try {
|
|
1334
|
-
let dir = (0,
|
|
1750
|
+
let dir = (0, import_node_path9.dirname)((0, import_node_url4.fileURLToPath)(import_meta2.url));
|
|
1335
1751
|
for (let i = 0; i < 5; i++) {
|
|
1336
|
-
const pkgPath = (0,
|
|
1752
|
+
const pkgPath = (0, import_node_path9.resolve)(dir, "package.json");
|
|
1337
1753
|
if ((0, import_node_fs2.existsSync)(pkgPath)) {
|
|
1338
1754
|
const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
|
|
1339
1755
|
if (pkg.name === "@korajs/cli") {
|
|
@@ -1342,7 +1758,7 @@ function resolveKoraVersion() {
|
|
|
1342
1758
|
return `^${parts[0]}.${parts[1]}.0`;
|
|
1343
1759
|
}
|
|
1344
1760
|
}
|
|
1345
|
-
dir = (0,
|
|
1761
|
+
dir = (0, import_node_path9.dirname)(dir);
|
|
1346
1762
|
}
|
|
1347
1763
|
return "latest";
|
|
1348
1764
|
} catch {
|
|
@@ -1351,8 +1767,9 @@ function resolveKoraVersion() {
|
|
|
1351
1767
|
}
|
|
1352
1768
|
|
|
1353
1769
|
// src/commands/deploy/deploy-command.ts
|
|
1354
|
-
var
|
|
1355
|
-
var
|
|
1770
|
+
var import_node_fs4 = require("fs");
|
|
1771
|
+
var import_node_path20 = require("path");
|
|
1772
|
+
var import_citty4 = require("citty");
|
|
1356
1773
|
|
|
1357
1774
|
// src/commands/deploy/adapters/adapter.ts
|
|
1358
1775
|
var DEPLOY_PLATFORMS = [
|
|
@@ -1369,14 +1786,14 @@ function isDeployPlatform(value) {
|
|
|
1369
1786
|
}
|
|
1370
1787
|
|
|
1371
1788
|
// src/commands/deploy/adapters/aws-ecs-adapter.ts
|
|
1372
|
-
var
|
|
1373
|
-
var
|
|
1789
|
+
var import_node_child_process6 = require("child_process");
|
|
1790
|
+
var import_node_path12 = require("path");
|
|
1374
1791
|
|
|
1375
1792
|
// src/commands/deploy/builder/client-builder.ts
|
|
1376
|
-
var
|
|
1793
|
+
var import_node_child_process5 = require("child_process");
|
|
1377
1794
|
var import_node_fs3 = require("fs");
|
|
1378
|
-
var
|
|
1379
|
-
var
|
|
1795
|
+
var import_promises7 = require("fs/promises");
|
|
1796
|
+
var import_node_path10 = require("path");
|
|
1380
1797
|
async function buildClient(options) {
|
|
1381
1798
|
const viteEntryPoint = await resolveProjectBinaryEntryPoint(options.projectRoot, "vite", "vite");
|
|
1382
1799
|
if (!viteEntryPoint) {
|
|
@@ -1397,15 +1814,15 @@ async function buildClient(options) {
|
|
|
1397
1814
|
return { outDir: options.outDir };
|
|
1398
1815
|
}
|
|
1399
1816
|
async function patchSqliteWasmAssets(projectRoot, outDir) {
|
|
1400
|
-
const assetsDir = (0,
|
|
1817
|
+
const assetsDir = (0, import_node_path10.join)(outDir, "assets");
|
|
1401
1818
|
if (!(0, import_node_fs3.existsSync)(assetsDir)) return;
|
|
1402
|
-
const files = await (0,
|
|
1819
|
+
const files = await (0, import_promises7.readdir)(assetsDir);
|
|
1403
1820
|
const hashedWasm = files.find((f) => /^sqlite3-.+\.wasm$/.test(f));
|
|
1404
1821
|
if (hashedWasm && !files.includes("sqlite3.wasm")) {
|
|
1405
|
-
await (0,
|
|
1822
|
+
await (0, import_promises7.copyFile)((0, import_node_path10.join)(assetsDir, hashedWasm), (0, import_node_path10.join)(assetsDir, "sqlite3.wasm"));
|
|
1406
1823
|
}
|
|
1407
1824
|
if (!files.includes("sqlite3-opfs-async-proxy.js")) {
|
|
1408
|
-
const proxyFile = (0,
|
|
1825
|
+
const proxyFile = (0, import_node_path10.resolve)(
|
|
1409
1826
|
projectRoot,
|
|
1410
1827
|
"node_modules",
|
|
1411
1828
|
"@sqlite.org",
|
|
@@ -1415,13 +1832,13 @@ async function patchSqliteWasmAssets(projectRoot, outDir) {
|
|
|
1415
1832
|
"sqlite3-opfs-async-proxy.js"
|
|
1416
1833
|
);
|
|
1417
1834
|
if ((0, import_node_fs3.existsSync)(proxyFile)) {
|
|
1418
|
-
await (0,
|
|
1835
|
+
await (0, import_promises7.copyFile)(proxyFile, (0, import_node_path10.join)(assetsDir, "sqlite3-opfs-async-proxy.js"));
|
|
1419
1836
|
}
|
|
1420
1837
|
}
|
|
1421
1838
|
}
|
|
1422
1839
|
async function runProcess(command, args, cwd) {
|
|
1423
|
-
await new Promise((
|
|
1424
|
-
const child = (0,
|
|
1840
|
+
await new Promise((resolve11, reject) => {
|
|
1841
|
+
const child = (0, import_node_child_process5.spawn)(command, args, {
|
|
1425
1842
|
cwd,
|
|
1426
1843
|
stdio: "inherit",
|
|
1427
1844
|
env: process.env
|
|
@@ -1431,7 +1848,7 @@ async function runProcess(command, args, cwd) {
|
|
|
1431
1848
|
});
|
|
1432
1849
|
child.on("exit", (code) => {
|
|
1433
1850
|
if (code === 0) {
|
|
1434
|
-
|
|
1851
|
+
resolve11();
|
|
1435
1852
|
return;
|
|
1436
1853
|
}
|
|
1437
1854
|
reject(new Error(`Client build failed with exit code ${String(code ?? "unknown")}.`));
|
|
@@ -1440,9 +1857,9 @@ async function runProcess(command, args, cwd) {
|
|
|
1440
1857
|
}
|
|
1441
1858
|
|
|
1442
1859
|
// src/commands/deploy/builder/server-bundler.ts
|
|
1443
|
-
var
|
|
1444
|
-
var
|
|
1445
|
-
var
|
|
1860
|
+
var import_promises8 = require("fs/promises");
|
|
1861
|
+
var import_promises9 = require("fs/promises");
|
|
1862
|
+
var import_node_path11 = require("path");
|
|
1446
1863
|
var import_esbuild = require("esbuild");
|
|
1447
1864
|
var DEFAULT_ENTRY_CANDIDATES = [
|
|
1448
1865
|
"server.ts",
|
|
@@ -1458,8 +1875,8 @@ async function bundleServer(options) {
|
|
|
1458
1875
|
`Could not find a server entry file in ${options.projectRoot}. Looked for: ${candidates.join(", ")}`
|
|
1459
1876
|
);
|
|
1460
1877
|
}
|
|
1461
|
-
await (0,
|
|
1462
|
-
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");
|
|
1463
1880
|
await (0, import_esbuild.build)({
|
|
1464
1881
|
entryPoints: [entryFilePath],
|
|
1465
1882
|
outfile: outputFilePath,
|
|
@@ -1481,9 +1898,9 @@ async function bundleServer(options) {
|
|
|
1481
1898
|
}
|
|
1482
1899
|
async function resolveServerEntry(projectRoot, candidates) {
|
|
1483
1900
|
for (const candidate of candidates) {
|
|
1484
|
-
const fullPath = (0,
|
|
1901
|
+
const fullPath = (0, import_node_path11.join)(projectRoot, candidate);
|
|
1485
1902
|
try {
|
|
1486
|
-
await (0,
|
|
1903
|
+
await (0, import_promises9.access)(fullPath);
|
|
1487
1904
|
return fullPath;
|
|
1488
1905
|
} catch {
|
|
1489
1906
|
}
|
|
@@ -1585,19 +2002,19 @@ var AwsEcsAdapter = class {
|
|
|
1585
2002
|
appName: config.appName,
|
|
1586
2003
|
region: config.region
|
|
1587
2004
|
};
|
|
1588
|
-
const deployDirectory = (0,
|
|
2005
|
+
const deployDirectory = (0, import_node_path12.join)(config.projectRoot, ".kora", "deploy");
|
|
1589
2006
|
await bundleServer({
|
|
1590
2007
|
projectRoot: config.projectRoot,
|
|
1591
2008
|
deployDirectory
|
|
1592
2009
|
});
|
|
1593
|
-
const client = await buildClient({
|
|
2010
|
+
const client = config.deployTarget === "sync-server" ? null : await buildClient({
|
|
1594
2011
|
projectRoot: config.projectRoot,
|
|
1595
|
-
outDir: (0,
|
|
2012
|
+
outDir: (0, import_node_path12.join)(deployDirectory, "dist"),
|
|
1596
2013
|
mode: "production"
|
|
1597
2014
|
});
|
|
1598
2015
|
return {
|
|
1599
|
-
clientDirectory: client
|
|
1600
|
-
serverBundlePath: (0,
|
|
2016
|
+
clientDirectory: client?.outDir ?? null,
|
|
2017
|
+
serverBundlePath: (0, import_node_path12.join)(deployDirectory, "server-bundled.js"),
|
|
1601
2018
|
deployDirectory
|
|
1602
2019
|
};
|
|
1603
2020
|
}
|
|
@@ -1829,8 +2246,8 @@ var AwsEcsAdapter = class {
|
|
|
1829
2246
|
};
|
|
1830
2247
|
var NodeAwsCommandRunner = class {
|
|
1831
2248
|
async run(command, args, cwd) {
|
|
1832
|
-
return new Promise((
|
|
1833
|
-
const child = (0,
|
|
2249
|
+
return new Promise((resolve11) => {
|
|
2250
|
+
const child = (0, import_node_child_process6.spawn)(command, args, {
|
|
1834
2251
|
cwd,
|
|
1835
2252
|
env: process.env,
|
|
1836
2253
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -1844,11 +2261,11 @@ var NodeAwsCommandRunner = class {
|
|
|
1844
2261
|
stderr += chunk.toString("utf-8");
|
|
1845
2262
|
});
|
|
1846
2263
|
child.on("error", (error) => {
|
|
1847
|
-
|
|
2264
|
+
resolve11({ exitCode: 1, stdout, stderr: `${stderr}
|
|
1848
2265
|
${error.message}` });
|
|
1849
2266
|
});
|
|
1850
2267
|
child.on("exit", (code) => {
|
|
1851
|
-
|
|
2268
|
+
resolve11({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
|
|
1852
2269
|
});
|
|
1853
2270
|
});
|
|
1854
2271
|
}
|
|
@@ -1862,8 +2279,8 @@ function inferLogLevel(line) {
|
|
|
1862
2279
|
}
|
|
1863
2280
|
|
|
1864
2281
|
// src/commands/deploy/adapters/aws-lightsail-adapter.ts
|
|
1865
|
-
var
|
|
1866
|
-
var
|
|
2282
|
+
var import_node_child_process7 = require("child_process");
|
|
2283
|
+
var import_node_path13 = require("path");
|
|
1867
2284
|
var AwsLightsailAdapter = class {
|
|
1868
2285
|
name = "aws-lightsail";
|
|
1869
2286
|
logger = createLogger();
|
|
@@ -1936,19 +2353,19 @@ var AwsLightsailAdapter = class {
|
|
|
1936
2353
|
appName: config.appName,
|
|
1937
2354
|
region: config.region
|
|
1938
2355
|
};
|
|
1939
|
-
const deployDirectory = (0,
|
|
2356
|
+
const deployDirectory = (0, import_node_path13.join)(config.projectRoot, ".kora", "deploy");
|
|
1940
2357
|
await bundleServer({
|
|
1941
2358
|
projectRoot: config.projectRoot,
|
|
1942
2359
|
deployDirectory
|
|
1943
2360
|
});
|
|
1944
|
-
const client = await buildClient({
|
|
2361
|
+
const client = config.deployTarget === "sync-server" ? null : await buildClient({
|
|
1945
2362
|
projectRoot: config.projectRoot,
|
|
1946
|
-
outDir: (0,
|
|
2363
|
+
outDir: (0, import_node_path13.join)(deployDirectory, "dist"),
|
|
1947
2364
|
mode: "production"
|
|
1948
2365
|
});
|
|
1949
2366
|
return {
|
|
1950
|
-
clientDirectory: client
|
|
1951
|
-
serverBundlePath: (0,
|
|
2367
|
+
clientDirectory: client?.outDir ?? null,
|
|
2368
|
+
serverBundlePath: (0, import_node_path13.join)(deployDirectory, "server-bundled.js"),
|
|
1952
2369
|
deployDirectory
|
|
1953
2370
|
};
|
|
1954
2371
|
}
|
|
@@ -2176,8 +2593,8 @@ var AwsLightsailAdapter = class {
|
|
|
2176
2593
|
};
|
|
2177
2594
|
var NodeAwsLightsailCommandRunner = class {
|
|
2178
2595
|
async run(command, args, cwd) {
|
|
2179
|
-
return new Promise((
|
|
2180
|
-
const child = (0,
|
|
2596
|
+
return new Promise((resolve11) => {
|
|
2597
|
+
const child = (0, import_node_child_process7.spawn)(command, args, {
|
|
2181
2598
|
cwd,
|
|
2182
2599
|
env: process.env,
|
|
2183
2600
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2191,11 +2608,11 @@ var NodeAwsLightsailCommandRunner = class {
|
|
|
2191
2608
|
stderr += chunk.toString("utf-8");
|
|
2192
2609
|
});
|
|
2193
2610
|
child.on("error", (error) => {
|
|
2194
|
-
|
|
2611
|
+
resolve11({ exitCode: 1, stdout, stderr: `${stderr}
|
|
2195
2612
|
${error.message}` });
|
|
2196
2613
|
});
|
|
2197
2614
|
child.on("exit", (code) => {
|
|
2198
|
-
|
|
2615
|
+
resolve11({ exitCode: code ?? 1, stdout: stdout.trim(), stderr: stderr.trim() });
|
|
2199
2616
|
});
|
|
2200
2617
|
});
|
|
2201
2618
|
}
|
|
@@ -2247,12 +2664,12 @@ function inferLogLevel2(line) {
|
|
|
2247
2664
|
}
|
|
2248
2665
|
|
|
2249
2666
|
// src/commands/deploy/adapters/fly-adapter.ts
|
|
2250
|
-
var
|
|
2251
|
-
var
|
|
2667
|
+
var import_node_child_process8 = require("child_process");
|
|
2668
|
+
var import_node_path15 = require("path");
|
|
2252
2669
|
|
|
2253
2670
|
// src/commands/deploy/artifacts/fly-toml-generator.ts
|
|
2254
|
-
var
|
|
2255
|
-
var
|
|
2671
|
+
var import_promises10 = require("fs/promises");
|
|
2672
|
+
var import_node_path14 = require("path");
|
|
2256
2673
|
var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
|
|
2257
2674
|
function generateFlyToml(options) {
|
|
2258
2675
|
const internalPort = options.internalPort ?? 3e3;
|
|
@@ -2277,14 +2694,14 @@ function generateFlyToml(options) {
|
|
|
2277
2694
|
' interval = "30s"',
|
|
2278
2695
|
' method = "GET"',
|
|
2279
2696
|
' timeout = "5s"',
|
|
2280
|
-
' path = "/"',
|
|
2697
|
+
' path = "/health"',
|
|
2281
2698
|
""
|
|
2282
2699
|
].join("\n");
|
|
2283
2700
|
}
|
|
2284
2701
|
async function writeFlyTomlArtifact(deployDirectory, options) {
|
|
2285
|
-
await (0,
|
|
2286
|
-
const filePath = (0,
|
|
2287
|
-
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");
|
|
2288
2705
|
return filePath;
|
|
2289
2706
|
}
|
|
2290
2707
|
|
|
@@ -2378,7 +2795,7 @@ var FlyAdapter = class {
|
|
|
2378
2795
|
appName: config.appName,
|
|
2379
2796
|
region: config.region
|
|
2380
2797
|
};
|
|
2381
|
-
const deployDirectory = (0,
|
|
2798
|
+
const deployDirectory = (0, import_node_path15.join)(config.projectRoot, ".kora", "deploy");
|
|
2382
2799
|
await writeFlyTomlArtifact(deployDirectory, {
|
|
2383
2800
|
appName: config.appName,
|
|
2384
2801
|
region: config.region ?? "iad"
|
|
@@ -2387,14 +2804,14 @@ var FlyAdapter = class {
|
|
|
2387
2804
|
projectRoot: config.projectRoot,
|
|
2388
2805
|
deployDirectory
|
|
2389
2806
|
});
|
|
2390
|
-
const client = await buildClient({
|
|
2807
|
+
const client = config.deployTarget === "sync-server" ? null : await buildClient({
|
|
2391
2808
|
projectRoot: config.projectRoot,
|
|
2392
|
-
outDir: (0,
|
|
2809
|
+
outDir: (0, import_node_path15.join)(deployDirectory, "dist"),
|
|
2393
2810
|
mode: "production"
|
|
2394
2811
|
});
|
|
2395
2812
|
return {
|
|
2396
|
-
clientDirectory: client
|
|
2397
|
-
serverBundlePath: (0,
|
|
2813
|
+
clientDirectory: client?.outDir ?? null,
|
|
2814
|
+
serverBundlePath: (0, import_node_path15.join)(deployDirectory, "server-bundled.js"),
|
|
2398
2815
|
deployDirectory
|
|
2399
2816
|
};
|
|
2400
2817
|
}
|
|
@@ -2520,8 +2937,8 @@ var FlyAdapter = class {
|
|
|
2520
2937
|
};
|
|
2521
2938
|
var NodeFlyCommandRunner = class {
|
|
2522
2939
|
async run(command, args, cwd) {
|
|
2523
|
-
return await new Promise((
|
|
2524
|
-
const child = (0,
|
|
2940
|
+
return await new Promise((resolve11) => {
|
|
2941
|
+
const child = (0, import_node_child_process8.spawn)(command, args, {
|
|
2525
2942
|
cwd,
|
|
2526
2943
|
env: process.env,
|
|
2527
2944
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2535,7 +2952,7 @@ var NodeFlyCommandRunner = class {
|
|
|
2535
2952
|
stderr += chunk.toString("utf-8");
|
|
2536
2953
|
});
|
|
2537
2954
|
child.on("error", (error) => {
|
|
2538
|
-
|
|
2955
|
+
resolve11({
|
|
2539
2956
|
exitCode: 1,
|
|
2540
2957
|
stdout,
|
|
2541
2958
|
stderr: `${stderr}
|
|
@@ -2543,7 +2960,7 @@ ${error.message}`
|
|
|
2543
2960
|
});
|
|
2544
2961
|
});
|
|
2545
2962
|
child.on("exit", (code) => {
|
|
2546
|
-
|
|
2963
|
+
resolve11({
|
|
2547
2964
|
exitCode: code ?? 1,
|
|
2548
2965
|
stdout: stdout.trim(),
|
|
2549
2966
|
stderr: stderr.trim()
|
|
@@ -2615,12 +3032,12 @@ ${stdout}`.toLowerCase();
|
|
|
2615
3032
|
}
|
|
2616
3033
|
|
|
2617
3034
|
// src/commands/deploy/adapters/railway-adapter.ts
|
|
2618
|
-
var
|
|
2619
|
-
var
|
|
3035
|
+
var import_node_child_process9 = require("child_process");
|
|
3036
|
+
var import_node_path17 = require("path");
|
|
2620
3037
|
|
|
2621
3038
|
// src/commands/deploy/artifacts/railway-json-generator.ts
|
|
2622
|
-
var
|
|
2623
|
-
var
|
|
3039
|
+
var import_promises11 = require("fs/promises");
|
|
3040
|
+
var import_node_path16 = require("path");
|
|
2624
3041
|
var GENERATED_HEADER2 = "Generated by kora deploy - do not edit manually.";
|
|
2625
3042
|
function generateRailwayJson(options) {
|
|
2626
3043
|
const file = {
|
|
@@ -2645,9 +3062,9 @@ function generateRailwayJson(options) {
|
|
|
2645
3062
|
`;
|
|
2646
3063
|
}
|
|
2647
3064
|
async function writeRailwayJsonArtifact(deployDirectory, options) {
|
|
2648
|
-
await (0,
|
|
2649
|
-
const filePath = (0,
|
|
2650
|
-
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");
|
|
2651
3068
|
return filePath;
|
|
2652
3069
|
}
|
|
2653
3070
|
|
|
@@ -2736,7 +3153,7 @@ var RailwayAdapter = class {
|
|
|
2736
3153
|
appName: config.appName,
|
|
2737
3154
|
region: config.region
|
|
2738
3155
|
};
|
|
2739
|
-
const deployDirectory = (0,
|
|
3156
|
+
const deployDirectory = (0, import_node_path17.join)(config.projectRoot, ".kora", "deploy");
|
|
2740
3157
|
await writeRailwayJsonArtifact(deployDirectory, {
|
|
2741
3158
|
appName: config.appName,
|
|
2742
3159
|
environment: config.environment
|
|
@@ -2745,14 +3162,14 @@ var RailwayAdapter = class {
|
|
|
2745
3162
|
projectRoot: config.projectRoot,
|
|
2746
3163
|
deployDirectory
|
|
2747
3164
|
});
|
|
2748
|
-
const client = await buildClient({
|
|
3165
|
+
const client = config.deployTarget === "sync-server" ? null : await buildClient({
|
|
2749
3166
|
projectRoot: config.projectRoot,
|
|
2750
|
-
outDir: (0,
|
|
3167
|
+
outDir: (0, import_node_path17.join)(deployDirectory, "dist"),
|
|
2751
3168
|
mode: "production"
|
|
2752
3169
|
});
|
|
2753
3170
|
return {
|
|
2754
|
-
clientDirectory: client
|
|
2755
|
-
serverBundlePath: (0,
|
|
3171
|
+
clientDirectory: client?.outDir ?? null,
|
|
3172
|
+
serverBundlePath: (0, import_node_path17.join)(deployDirectory, "server-bundled.js"),
|
|
2756
3173
|
deployDirectory
|
|
2757
3174
|
};
|
|
2758
3175
|
}
|
|
@@ -2856,8 +3273,8 @@ var RailwayAdapter = class {
|
|
|
2856
3273
|
};
|
|
2857
3274
|
var NodeRailwayCommandRunner = class {
|
|
2858
3275
|
async run(command, args, cwd) {
|
|
2859
|
-
return await new Promise((
|
|
2860
|
-
const child = (0,
|
|
3276
|
+
return await new Promise((resolve11) => {
|
|
3277
|
+
const child = (0, import_node_child_process9.spawn)(command, args, {
|
|
2861
3278
|
cwd,
|
|
2862
3279
|
env: process.env,
|
|
2863
3280
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2871,7 +3288,7 @@ var NodeRailwayCommandRunner = class {
|
|
|
2871
3288
|
stderr += chunk.toString("utf-8");
|
|
2872
3289
|
});
|
|
2873
3290
|
child.on("error", (error) => {
|
|
2874
|
-
|
|
3291
|
+
resolve11({
|
|
2875
3292
|
exitCode: 1,
|
|
2876
3293
|
stdout,
|
|
2877
3294
|
stderr: `${stderr}
|
|
@@ -2879,7 +3296,7 @@ ${error.message}`
|
|
|
2879
3296
|
});
|
|
2880
3297
|
});
|
|
2881
3298
|
child.on("exit", (code) => {
|
|
2882
|
-
|
|
3299
|
+
resolve11({
|
|
2883
3300
|
exitCode: code ?? 1,
|
|
2884
3301
|
stdout: stdout.trim(),
|
|
2885
3302
|
stderr: stderr.trim()
|
|
@@ -3037,13 +3454,13 @@ function createDeployAdapter(platform) {
|
|
|
3037
3454
|
}
|
|
3038
3455
|
|
|
3039
3456
|
// src/commands/deploy/artifacts/dockerfile-generator.ts
|
|
3040
|
-
var
|
|
3041
|
-
var
|
|
3457
|
+
var import_promises12 = require("fs/promises");
|
|
3458
|
+
var import_node_path18 = require("path");
|
|
3042
3459
|
var GENERATED_HEADER3 = "# Generated by kora deploy \u2014 do not edit manually";
|
|
3043
3460
|
function generateDockerfile(options = {}) {
|
|
3044
3461
|
const nodeVersion = options.nodeVersion ?? "20-alpine";
|
|
3045
3462
|
const port = options.port ?? 3e3;
|
|
3046
|
-
const clientDirectory = options.clientDirectory
|
|
3463
|
+
const clientDirectory = options.clientDirectory === void 0 ? "dist" : options.clientDirectory;
|
|
3047
3464
|
const serverBundleFile = options.serverBundleFile ?? "server-bundled.js";
|
|
3048
3465
|
const hasNativeDeps = options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0;
|
|
3049
3466
|
const lines = [
|
|
@@ -3063,7 +3480,9 @@ function generateDockerfile(options = {}) {
|
|
|
3063
3480
|
lines.push("RUN npm install --omit=dev");
|
|
3064
3481
|
lines.push("");
|
|
3065
3482
|
}
|
|
3066
|
-
|
|
3483
|
+
if (clientDirectory !== null) {
|
|
3484
|
+
lines.push(`COPY ${clientDirectory} ./dist`);
|
|
3485
|
+
}
|
|
3067
3486
|
lines.push(`COPY ${serverBundleFile} ./server-bundled.js`);
|
|
3068
3487
|
lines.push("");
|
|
3069
3488
|
lines.push(`EXPOSE ${String(port)}`);
|
|
@@ -3102,37 +3521,37 @@ function generateDockerIgnore() {
|
|
|
3102
3521
|
].join("\n");
|
|
3103
3522
|
}
|
|
3104
3523
|
async function writeDockerfileArtifact(deployDirectory, options = {}) {
|
|
3105
|
-
await (0,
|
|
3106
|
-
const dockerfilePath = (0,
|
|
3107
|
-
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");
|
|
3108
3527
|
if (options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0) {
|
|
3109
|
-
const pkgJsonPath = (0,
|
|
3110
|
-
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");
|
|
3111
3530
|
}
|
|
3112
3531
|
return dockerfilePath;
|
|
3113
3532
|
}
|
|
3114
3533
|
async function writeDockerIgnoreArtifact(deployDirectory) {
|
|
3115
|
-
await (0,
|
|
3116
|
-
const dockerIgnorePath = (0,
|
|
3117
|
-
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");
|
|
3118
3537
|
return dockerIgnorePath;
|
|
3119
3538
|
}
|
|
3120
3539
|
|
|
3121
3540
|
// src/commands/deploy/state/deploy-state.ts
|
|
3122
|
-
var
|
|
3123
|
-
var
|
|
3124
|
-
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");
|
|
3125
3544
|
var DEPLOY_STATE_FILENAME = "deploy.json";
|
|
3126
3545
|
function resolveDeployDirectory(projectRoot) {
|
|
3127
|
-
return (0,
|
|
3546
|
+
return (0, import_node_path19.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
|
|
3128
3547
|
}
|
|
3129
3548
|
function resolveDeployStatePath(projectRoot) {
|
|
3130
|
-
return (0,
|
|
3549
|
+
return (0, import_node_path19.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
|
|
3131
3550
|
}
|
|
3132
3551
|
async function readDeployState(projectRoot) {
|
|
3133
3552
|
const statePath = resolveDeployStatePath(projectRoot);
|
|
3134
3553
|
try {
|
|
3135
|
-
const source = await (0,
|
|
3554
|
+
const source = await (0, import_promises13.readFile)(statePath, "utf-8");
|
|
3136
3555
|
const parsed = JSON.parse(source);
|
|
3137
3556
|
return parseDeployState(parsed);
|
|
3138
3557
|
} catch (error) {
|
|
@@ -3179,13 +3598,13 @@ async function updateDeployState(projectRoot, patch, now = /* @__PURE__ */ new D
|
|
|
3179
3598
|
return nextState;
|
|
3180
3599
|
}
|
|
3181
3600
|
async function resetDeployState(projectRoot) {
|
|
3182
|
-
await (0,
|
|
3601
|
+
await (0, import_promises13.rm)(resolveDeployDirectory(projectRoot), { recursive: true, force: true });
|
|
3183
3602
|
}
|
|
3184
3603
|
async function persistDeployState(projectRoot, state) {
|
|
3185
3604
|
const deployDir = resolveDeployDirectory(projectRoot);
|
|
3186
3605
|
const statePath = resolveDeployStatePath(projectRoot);
|
|
3187
|
-
await (0,
|
|
3188
|
-
await (0,
|
|
3606
|
+
await (0, import_promises13.mkdir)(deployDir, { recursive: true });
|
|
3607
|
+
await (0, import_promises13.writeFile)(statePath, `${JSON.stringify(state, null, 2)}
|
|
3189
3608
|
`, "utf-8");
|
|
3190
3609
|
}
|
|
3191
3610
|
function parseDeployState(value) {
|
|
@@ -3235,7 +3654,7 @@ function readOptionalString(value, field) {
|
|
|
3235
3654
|
}
|
|
3236
3655
|
|
|
3237
3656
|
// src/commands/deploy/deploy-command.ts
|
|
3238
|
-
var deployCommand = (0,
|
|
3657
|
+
var deployCommand = (0, import_citty4.defineCommand)({
|
|
3239
3658
|
meta: {
|
|
3240
3659
|
name: "deploy",
|
|
3241
3660
|
description: "Deploy a Kora project to your selected platform"
|
|
@@ -3270,7 +3689,7 @@ var deployCommand = (0, import_citty2.defineCommand)({
|
|
|
3270
3689
|
}
|
|
3271
3690
|
},
|
|
3272
3691
|
subCommands: {
|
|
3273
|
-
status: (0,
|
|
3692
|
+
status: (0, import_citty4.defineCommand)({
|
|
3274
3693
|
meta: {
|
|
3275
3694
|
name: "status",
|
|
3276
3695
|
description: "Show current deployment status"
|
|
@@ -3301,7 +3720,7 @@ var deployCommand = (0, import_citty2.defineCommand)({
|
|
|
3301
3720
|
logger.step(`Sync URL: ${state.syncUrl ?? "n/a"}`);
|
|
3302
3721
|
}
|
|
3303
3722
|
}),
|
|
3304
|
-
rollback: (0,
|
|
3723
|
+
rollback: (0, import_citty4.defineCommand)({
|
|
3305
3724
|
meta: {
|
|
3306
3725
|
name: "rollback",
|
|
3307
3726
|
description: "Rollback the current deployment"
|
|
@@ -3332,7 +3751,7 @@ var deployCommand = (0, import_citty2.defineCommand)({
|
|
|
3332
3751
|
logger.success(`Rolled back ${state.platform} deployment to ${deploymentId}.`);
|
|
3333
3752
|
}
|
|
3334
3753
|
}),
|
|
3335
|
-
logs: (0,
|
|
3754
|
+
logs: (0, import_citty4.defineCommand)({
|
|
3336
3755
|
meta: {
|
|
3337
3756
|
name: "logs",
|
|
3338
3757
|
description: "Read deployment logs"
|
|
@@ -3388,12 +3807,14 @@ var deployCommand = (0, import_citty2.defineCommand)({
|
|
|
3388
3807
|
const region = resolveRegion(args.region, existingState?.region, confirmMode);
|
|
3389
3808
|
const deployDirectory = resolveDeployDirectory(projectRoot);
|
|
3390
3809
|
const environment = args.prod === true ? "production" : "preview";
|
|
3810
|
+
const deployTarget = detectDeployTarget(projectRoot);
|
|
3391
3811
|
const config = {
|
|
3392
3812
|
projectRoot,
|
|
3393
3813
|
appName,
|
|
3394
3814
|
region,
|
|
3395
3815
|
environment,
|
|
3396
|
-
confirm: confirmMode
|
|
3816
|
+
confirm: confirmMode,
|
|
3817
|
+
deployTarget
|
|
3397
3818
|
};
|
|
3398
3819
|
const adapter = createDeployAdapter(platform);
|
|
3399
3820
|
configureAdapterContext(adapter, {
|
|
@@ -3405,10 +3826,14 @@ var deployCommand = (0, import_citty2.defineCommand)({
|
|
|
3405
3826
|
logger.info(
|
|
3406
3827
|
`Deploying to ${platform} (${appName}${region ? `, ${region}` : ""}, ${environment})`
|
|
3407
3828
|
);
|
|
3829
|
+
if (deployTarget === "sync-server") {
|
|
3830
|
+
logger.step("Detected Tauri project. Deploying the sync server only.");
|
|
3831
|
+
}
|
|
3408
3832
|
if (confirmMode) {
|
|
3409
3833
|
logger.step("Running in --confirm mode (non-interactive, fail-fast).");
|
|
3410
3834
|
}
|
|
3411
3835
|
await writeDockerfileArtifact(deployDirectory, {
|
|
3836
|
+
clientDirectory: deployTarget === "sync-server" ? null : void 0,
|
|
3412
3837
|
nativeDependencies: {
|
|
3413
3838
|
"better-sqlite3": "^11.0.0",
|
|
3414
3839
|
"drizzle-orm": "^0.45.2",
|
|
@@ -3513,7 +3938,7 @@ function resolveAppName(argValue, storedValue, projectRoot, confirm) {
|
|
|
3513
3938
|
"Missing app name in --confirm mode. Provide --app or run an interactive deploy first."
|
|
3514
3939
|
);
|
|
3515
3940
|
}
|
|
3516
|
-
return sanitizeAppName((0,
|
|
3941
|
+
return sanitizeAppName((0, import_node_path20.basename)(projectRoot));
|
|
3517
3942
|
}
|
|
3518
3943
|
function resolveRegion(argValue, storedValue, confirm) {
|
|
3519
3944
|
if (typeof argValue === "string" && argValue.length > 0) return argValue;
|
|
@@ -3547,109 +3972,21 @@ function configureAdapterContext(adapter, context) {
|
|
|
3547
3972
|
adapter.setContext(context);
|
|
3548
3973
|
}
|
|
3549
3974
|
}
|
|
3975
|
+
function detectDeployTarget(projectRoot) {
|
|
3976
|
+
return (0, import_node_fs4.existsSync)((0, import_node_path20.join)(projectRoot, "src-tauri")) ? "sync-server" : "full-stack";
|
|
3977
|
+
}
|
|
3550
3978
|
function hasContextSetter(adapter) {
|
|
3551
3979
|
return typeof adapter.setContext === "function";
|
|
3552
3980
|
}
|
|
3553
3981
|
|
|
3554
3982
|
// src/commands/dev/dev-command.ts
|
|
3555
|
-
var
|
|
3556
|
-
var
|
|
3557
|
-
var
|
|
3558
|
-
var
|
|
3559
|
-
|
|
3560
|
-
// src/commands/dev/kora-config.ts
|
|
3561
|
-
var import_node_child_process8 = require("child_process");
|
|
3562
|
-
var import_promises12 = require("fs/promises");
|
|
3563
|
-
var import_node_path17 = require("path");
|
|
3564
|
-
var import_node_url3 = require("url");
|
|
3565
|
-
var CONFIG_CANDIDATES = [
|
|
3566
|
-
"kora.config.ts",
|
|
3567
|
-
"kora.config.mts",
|
|
3568
|
-
"kora.config.cts",
|
|
3569
|
-
"kora.config.js",
|
|
3570
|
-
"kora.config.mjs",
|
|
3571
|
-
"kora.config.cjs"
|
|
3572
|
-
];
|
|
3573
|
-
async function loadKoraConfig(projectRoot) {
|
|
3574
|
-
const configPath = await findKoraConfigFile(projectRoot);
|
|
3575
|
-
if (!configPath) return null;
|
|
3576
|
-
const ext = (0, import_node_path17.extname)(configPath);
|
|
3577
|
-
if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
|
|
3578
|
-
const loaded2 = await loadTypeScriptConfig(configPath, projectRoot);
|
|
3579
|
-
return toConfigObject(loaded2);
|
|
3580
|
-
}
|
|
3581
|
-
const loaded = await import((0, import_node_url3.pathToFileURL)(configPath).href);
|
|
3582
|
-
return toConfigObject(loaded);
|
|
3583
|
-
}
|
|
3584
|
-
async function findKoraConfigFile(projectRoot) {
|
|
3585
|
-
for (const file of CONFIG_CANDIDATES) {
|
|
3586
|
-
const candidate = (0, import_node_path17.join)(projectRoot, file);
|
|
3587
|
-
try {
|
|
3588
|
-
await (0, import_promises12.access)(candidate);
|
|
3589
|
-
return candidate;
|
|
3590
|
-
} catch {
|
|
3591
|
-
}
|
|
3592
|
-
}
|
|
3593
|
-
return null;
|
|
3594
|
-
}
|
|
3595
|
-
async function loadTypeScriptConfig(configPath, projectRoot) {
|
|
3596
|
-
if (!await hasTsxInstalled(projectRoot)) {
|
|
3597
|
-
throw new Error(
|
|
3598
|
-
`Found TypeScript config at ${configPath}, but "tsx" is not installed in this project. Install tsx or use kora.config.js.`
|
|
3599
|
-
);
|
|
3600
|
-
}
|
|
3601
|
-
const script = "const configPath = process.argv[process.argv.length - 1];import('node:url').then(u => import(u.pathToFileURL(configPath).href)).then(mod => { const v = mod.default ?? mod; process.stdout.write(JSON.stringify(v)) }).catch(e => { process.stderr.write(String(e)); process.exit(1) })";
|
|
3602
|
-
const output = await runCommand(
|
|
3603
|
-
process.execPath,
|
|
3604
|
-
["--import", "tsx", "--eval", script, configPath],
|
|
3605
|
-
projectRoot
|
|
3606
|
-
);
|
|
3607
|
-
try {
|
|
3608
|
-
return JSON.parse(output);
|
|
3609
|
-
} catch {
|
|
3610
|
-
throw new Error(`Failed to parse ${configPath} output as JSON.`);
|
|
3611
|
-
}
|
|
3612
|
-
}
|
|
3613
|
-
async function runCommand(command, args, cwd) {
|
|
3614
|
-
return await new Promise((resolve9, reject) => {
|
|
3615
|
-
const child = (0, import_node_child_process8.spawn)(command, args, {
|
|
3616
|
-
cwd,
|
|
3617
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3618
|
-
env: process.env
|
|
3619
|
-
});
|
|
3620
|
-
let stdout = "";
|
|
3621
|
-
let stderr = "";
|
|
3622
|
-
child.stdout?.on("data", (chunk) => {
|
|
3623
|
-
stdout += chunk.toString("utf-8");
|
|
3624
|
-
});
|
|
3625
|
-
child.stderr?.on("data", (chunk) => {
|
|
3626
|
-
stderr += chunk.toString("utf-8");
|
|
3627
|
-
});
|
|
3628
|
-
child.on("error", (error) => {
|
|
3629
|
-
reject(error);
|
|
3630
|
-
});
|
|
3631
|
-
child.on("exit", (code) => {
|
|
3632
|
-
if (code === 0) {
|
|
3633
|
-
resolve9(stdout.trim());
|
|
3634
|
-
return;
|
|
3635
|
-
}
|
|
3636
|
-
reject(new Error(`Failed to load kora config (exit ${code ?? "unknown"}): ${stderr.trim()}`));
|
|
3637
|
-
});
|
|
3638
|
-
});
|
|
3639
|
-
}
|
|
3640
|
-
function toConfigObject(mod) {
|
|
3641
|
-
if (typeof mod !== "object" || mod === null) {
|
|
3642
|
-
throw new Error("kora config must export an object.");
|
|
3643
|
-
}
|
|
3644
|
-
const value = mod.default ?? mod;
|
|
3645
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3646
|
-
throw new Error("kora config must export an object.");
|
|
3647
|
-
}
|
|
3648
|
-
return value;
|
|
3649
|
-
}
|
|
3983
|
+
var import_promises14 = require("fs/promises");
|
|
3984
|
+
var import_node_path22 = require("path");
|
|
3985
|
+
var import_node_path23 = require("path");
|
|
3986
|
+
var import_citty5 = require("citty");
|
|
3650
3987
|
|
|
3651
3988
|
// src/commands/dev/process-manager.ts
|
|
3652
|
-
var
|
|
3989
|
+
var import_node_child_process10 = require("child_process");
|
|
3653
3990
|
var ProcessManager = class {
|
|
3654
3991
|
processes = /* @__PURE__ */ new Map();
|
|
3655
3992
|
spawn(config) {
|
|
@@ -3658,12 +3995,12 @@ var ProcessManager = class {
|
|
|
3658
3995
|
env: { ...process.env, ...config.env },
|
|
3659
3996
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3660
3997
|
};
|
|
3661
|
-
const child = (0,
|
|
3998
|
+
const child = (0, import_node_child_process10.spawn)(config.command, config.args, options);
|
|
3662
3999
|
let resolveExit;
|
|
3663
4000
|
const runningProcess = {
|
|
3664
4001
|
child,
|
|
3665
|
-
exitPromise: new Promise((
|
|
3666
|
-
resolveExit =
|
|
4002
|
+
exitPromise: new Promise((resolve11) => {
|
|
4003
|
+
resolveExit = resolve11;
|
|
3667
4004
|
}),
|
|
3668
4005
|
stdoutBuffer: "",
|
|
3669
4006
|
stderrBuffer: ""
|
|
@@ -3730,15 +4067,15 @@ var ProcessManager = class {
|
|
|
3730
4067
|
}
|
|
3731
4068
|
};
|
|
3732
4069
|
function delay(ms) {
|
|
3733
|
-
return new Promise((
|
|
3734
|
-
setTimeout(
|
|
4070
|
+
return new Promise((resolve11) => {
|
|
4071
|
+
setTimeout(resolve11, ms);
|
|
3735
4072
|
});
|
|
3736
4073
|
}
|
|
3737
4074
|
|
|
3738
4075
|
// src/commands/dev/schema-watcher.ts
|
|
3739
|
-
var
|
|
3740
|
-
var
|
|
3741
|
-
var
|
|
4076
|
+
var import_node_child_process11 = require("child_process");
|
|
4077
|
+
var import_node_fs5 = require("fs");
|
|
4078
|
+
var import_node_path21 = require("path");
|
|
3742
4079
|
var SchemaWatcher = class {
|
|
3743
4080
|
constructor(config) {
|
|
3744
4081
|
this.config = config;
|
|
@@ -3750,7 +4087,7 @@ var SchemaWatcher = class {
|
|
|
3750
4087
|
debounceTimer = null;
|
|
3751
4088
|
start() {
|
|
3752
4089
|
if (this.watcher) return;
|
|
3753
|
-
this.watcher = (0,
|
|
4090
|
+
this.watcher = (0, import_node_fs5.watch)(this.config.schemaPath, () => {
|
|
3754
4091
|
this.scheduleRegeneration();
|
|
3755
4092
|
});
|
|
3756
4093
|
this.watcher.on("error", (error) => {
|
|
@@ -3766,7 +4103,7 @@ var SchemaWatcher = class {
|
|
|
3766
4103
|
this.watcher = null;
|
|
3767
4104
|
}
|
|
3768
4105
|
async regenerate() {
|
|
3769
|
-
const koraBinJs = (0,
|
|
4106
|
+
const koraBinJs = (0, import_node_path21.join)(
|
|
3770
4107
|
this.config.projectRoot,
|
|
3771
4108
|
"node_modules",
|
|
3772
4109
|
"@korajs",
|
|
@@ -3793,8 +4130,8 @@ var SchemaWatcher = class {
|
|
|
3793
4130
|
}
|
|
3794
4131
|
};
|
|
3795
4132
|
async function spawnCommand(command, args, cwd) {
|
|
3796
|
-
await new Promise((
|
|
3797
|
-
const child = (0,
|
|
4133
|
+
await new Promise((resolve11, reject) => {
|
|
4134
|
+
const child = (0, import_node_child_process11.spawn)(command, args, {
|
|
3798
4135
|
cwd,
|
|
3799
4136
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3800
4137
|
env: process.env
|
|
@@ -3810,7 +4147,7 @@ async function spawnCommand(command, args, cwd) {
|
|
|
3810
4147
|
});
|
|
3811
4148
|
child.on("exit", (code) => {
|
|
3812
4149
|
if (code === 0) {
|
|
3813
|
-
|
|
4150
|
+
resolve11();
|
|
3814
4151
|
return;
|
|
3815
4152
|
}
|
|
3816
4153
|
reject(new Error(`Type generation exited with code ${code ?? "unknown"}.`));
|
|
@@ -3832,7 +4169,7 @@ function toError(error) {
|
|
|
3832
4169
|
}
|
|
3833
4170
|
|
|
3834
4171
|
// src/commands/dev/dev-command.ts
|
|
3835
|
-
var devCommand = (0,
|
|
4172
|
+
var devCommand = (0, import_citty5.defineCommand)({
|
|
3836
4173
|
meta: {
|
|
3837
4174
|
name: "dev",
|
|
3838
4175
|
description: "Start the Kora development environment"
|
|
@@ -3872,7 +4209,7 @@ var devCommand = (0, import_citty3.defineCommand)({
|
|
|
3872
4209
|
const watchDebounceMs = typeof config?.dev?.watch === "object" && typeof config.dev.watch.debounceMs === "number" ? config.dev.watch.debounceMs : 300;
|
|
3873
4210
|
const viteEntryPoint = await resolveProjectBinaryEntryPoint(projectRoot, "vite", "vite");
|
|
3874
4211
|
if (!viteEntryPoint) {
|
|
3875
|
-
throw new DevServerError("vite", (0,
|
|
4212
|
+
throw new DevServerError("vite", (0, import_node_path22.join)(projectRoot, "node_modules", ".bin", "vite"));
|
|
3876
4213
|
}
|
|
3877
4214
|
const syncServerFile = await findSyncServerFile(projectRoot);
|
|
3878
4215
|
let managedSyncStore = normalizeManagedSyncStore(config, projectRoot);
|
|
@@ -3888,7 +4225,7 @@ var devCommand = (0, import_citty3.defineCommand)({
|
|
|
3888
4225
|
}
|
|
3889
4226
|
if (shouldStartSync && syncServerFile === null && managedSyncStore) {
|
|
3890
4227
|
const hasServerPackage = await fileExists2(
|
|
3891
|
-
(0,
|
|
4228
|
+
(0, import_node_path22.join)(projectRoot, "node_modules", "@korajs", "server", "package.json")
|
|
3892
4229
|
);
|
|
3893
4230
|
if (!hasServerPackage) {
|
|
3894
4231
|
logger.warn(
|
|
@@ -3905,7 +4242,7 @@ var devCommand = (0, import_citty3.defineCommand)({
|
|
|
3905
4242
|
}
|
|
3906
4243
|
let configuredSchemaPath = null;
|
|
3907
4244
|
if (typeof config?.schema === "string") {
|
|
3908
|
-
const candidate = (0,
|
|
4245
|
+
const candidate = (0, import_node_path23.resolve)(projectRoot, config.schema);
|
|
3909
4246
|
if (await fileExists2(candidate)) {
|
|
3910
4247
|
configuredSchemaPath = candidate;
|
|
3911
4248
|
} else {
|
|
@@ -3920,8 +4257,8 @@ var devCommand = (0, import_citty3.defineCommand)({
|
|
|
3920
4257
|
let schemaWatcher = null;
|
|
3921
4258
|
let shuttingDown = false;
|
|
3922
4259
|
let resolveFinished;
|
|
3923
|
-
const finished = new Promise((
|
|
3924
|
-
resolveFinished =
|
|
4260
|
+
const finished = new Promise((resolve11) => {
|
|
4261
|
+
resolveFinished = resolve11;
|
|
3925
4262
|
});
|
|
3926
4263
|
const onManagedProcessExit = () => {
|
|
3927
4264
|
if (!processManager.hasRunning() && !shuttingDown) {
|
|
@@ -3964,11 +4301,15 @@ var devCommand = (0, import_citty3.defineCommand)({
|
|
|
3964
4301
|
logger.step(" Schema watcher disabled (schema.ts not found)");
|
|
3965
4302
|
}
|
|
3966
4303
|
logger.blank();
|
|
4304
|
+
logger.step(" Kora DevTools overlay: Ctrl+Shift+K (Cmd+Shift+K on macOS) when devtools: true");
|
|
3967
4305
|
processManager.spawn({
|
|
3968
4306
|
label: "vite",
|
|
3969
4307
|
command: process.execPath,
|
|
3970
4308
|
args: [viteEntryPoint, "--port", String(vitePort)],
|
|
3971
4309
|
cwd: projectRoot,
|
|
4310
|
+
env: {
|
|
4311
|
+
KORA_DEV: "1"
|
|
4312
|
+
},
|
|
3972
4313
|
onExit: onManagedProcessExit
|
|
3973
4314
|
});
|
|
3974
4315
|
if (shouldStartSync && hasTsx && syncServerFile) {
|
|
@@ -4019,10 +4360,10 @@ var devCommand = (0, import_citty3.defineCommand)({
|
|
|
4019
4360
|
}
|
|
4020
4361
|
});
|
|
4021
4362
|
async function findSyncServerFile(projectRoot) {
|
|
4022
|
-
const candidates = [(0,
|
|
4363
|
+
const candidates = [(0, import_node_path22.join)(projectRoot, "server.ts"), (0, import_node_path22.join)(projectRoot, "server.js")];
|
|
4023
4364
|
for (const candidate of candidates) {
|
|
4024
4365
|
try {
|
|
4025
|
-
await (0,
|
|
4366
|
+
await (0, import_promises14.access)(candidate);
|
|
4026
4367
|
return candidate;
|
|
4027
4368
|
} catch {
|
|
4028
4369
|
}
|
|
@@ -4031,7 +4372,7 @@ async function findSyncServerFile(projectRoot) {
|
|
|
4031
4372
|
}
|
|
4032
4373
|
async function fileExists2(path) {
|
|
4033
4374
|
try {
|
|
4034
|
-
await (0,
|
|
4375
|
+
await (0, import_promises14.access)(path);
|
|
4035
4376
|
return true;
|
|
4036
4377
|
} catch {
|
|
4037
4378
|
return false;
|
|
@@ -4043,7 +4384,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
|
|
|
4043
4384
|
const store = sync.store;
|
|
4044
4385
|
if (store === void 0) return { type: "memory" };
|
|
4045
4386
|
if (store === "memory") return { type: "memory" };
|
|
4046
|
-
if (store === "sqlite") return { type: "sqlite", filename: (0,
|
|
4387
|
+
if (store === "sqlite") return { type: "sqlite", filename: (0, import_node_path22.join)(projectRoot, "kora-sync.db") };
|
|
4047
4388
|
if (store === "postgres") {
|
|
4048
4389
|
const connectionString = process.env.DATABASE_URL;
|
|
4049
4390
|
if (!connectionString) return null;
|
|
@@ -4052,7 +4393,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
|
|
|
4052
4393
|
if (typeof store === "object" && store !== null) {
|
|
4053
4394
|
if (store.type === "memory") return { type: "memory" };
|
|
4054
4395
|
if (store.type === "sqlite") {
|
|
4055
|
-
const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0,
|
|
4396
|
+
const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0, import_node_path23.resolve)(projectRoot, store.filename) : (0, import_node_path22.join)(projectRoot, "kora-sync.db");
|
|
4056
4397
|
return { type: "sqlite", filename };
|
|
4057
4398
|
}
|
|
4058
4399
|
if (store.type === "postgres" && typeof store.connectionString === "string") {
|
|
@@ -4090,35 +4431,403 @@ if (storeConfig.type === 'memory') {
|
|
|
4090
4431
|
if (typeof storeConfig.connectionString !== 'string' || storeConfig.connectionString.length === 0) {
|
|
4091
4432
|
throw new Error('Managed postgres sync requires a connectionString');
|
|
4092
4433
|
}
|
|
4093
|
-
store = await createPostgresServerStore({
|
|
4094
|
-
connectionString: storeConfig.connectionString,
|
|
4095
|
-
});
|
|
4096
|
-
} else {
|
|
4097
|
-
throw new Error('Unsupported managed sync store type: ' + String(storeConfig.type));
|
|
4434
|
+
store = await createPostgresServerStore({
|
|
4435
|
+
connectionString: storeConfig.connectionString,
|
|
4436
|
+
});
|
|
4437
|
+
} else {
|
|
4438
|
+
throw new Error('Unsupported managed sync store type: ' + String(storeConfig.type));
|
|
4439
|
+
}
|
|
4440
|
+
const server = createKoraServer({ store, port: Number(config.port ?? 3001) });
|
|
4441
|
+
const shutdown = async () => {
|
|
4442
|
+
try {
|
|
4443
|
+
await server.stop();
|
|
4444
|
+
} catch {
|
|
4445
|
+
}
|
|
4446
|
+
process.exit(0);
|
|
4447
|
+
};
|
|
4448
|
+
process.on('SIGINT', () => {
|
|
4449
|
+
void shutdown();
|
|
4450
|
+
});
|
|
4451
|
+
process.on('SIGTERM', () => {
|
|
4452
|
+
void shutdown();
|
|
4453
|
+
});
|
|
4454
|
+
await server.start();
|
|
4455
|
+
process.stdout.write('Managed sync server running on ws://localhost:' + String(config.port ?? 3001) + '\\n');
|
|
4456
|
+
await new Promise(() => {});
|
|
4457
|
+
`;
|
|
4458
|
+
|
|
4459
|
+
// src/commands/doctor/doctor-command.ts
|
|
4460
|
+
var import_citty6 = require("citty");
|
|
4461
|
+
|
|
4462
|
+
// src/commands/doctor/doctor-checks.ts
|
|
4463
|
+
var import_promises15 = require("fs/promises");
|
|
4464
|
+
var import_node_path24 = require("path");
|
|
4465
|
+
var STATUS_ENDPOINT = "/__kora/status";
|
|
4466
|
+
async function runDoctorChecks(options) {
|
|
4467
|
+
const { projectRoot } = options;
|
|
4468
|
+
const results = [];
|
|
4469
|
+
results.push({
|
|
4470
|
+
id: "project-root",
|
|
4471
|
+
title: "Project root",
|
|
4472
|
+
status: "pass",
|
|
4473
|
+
message: projectRoot
|
|
4474
|
+
});
|
|
4475
|
+
const config = await loadKoraConfig(projectRoot);
|
|
4476
|
+
if (!config) {
|
|
4477
|
+
results.push({
|
|
4478
|
+
id: "kora-config",
|
|
4479
|
+
title: "kora.config",
|
|
4480
|
+
status: "warn",
|
|
4481
|
+
message: "No kora.config.* file found.",
|
|
4482
|
+
fix: "Add kora.config.ts with schema path and dev settings (see create-kora-app templates)."
|
|
4483
|
+
});
|
|
4484
|
+
} else {
|
|
4485
|
+
results.push({
|
|
4486
|
+
id: "kora-config",
|
|
4487
|
+
title: "kora.config",
|
|
4488
|
+
status: "pass",
|
|
4489
|
+
message: "Configuration file loaded."
|
|
4490
|
+
});
|
|
4491
|
+
}
|
|
4492
|
+
const schemaPath = await resolveSchemaPath(projectRoot, config?.schema);
|
|
4493
|
+
if (!schemaPath) {
|
|
4494
|
+
results.push({
|
|
4495
|
+
id: "schema",
|
|
4496
|
+
title: "Schema",
|
|
4497
|
+
status: "fail",
|
|
4498
|
+
message: "Could not find schema.ts in src/ or project root.",
|
|
4499
|
+
fix: "Add src/schema.ts or set schema in kora.config.ts."
|
|
4500
|
+
});
|
|
4501
|
+
} else {
|
|
4502
|
+
try {
|
|
4503
|
+
const schema = await loadSchemaDefinition(schemaPath, projectRoot);
|
|
4504
|
+
results.push({
|
|
4505
|
+
id: "schema",
|
|
4506
|
+
title: "Schema",
|
|
4507
|
+
status: "pass",
|
|
4508
|
+
message: `Loaded schema v${schema.version} (${Object.keys(schema.collections).length} collection(s)).`
|
|
4509
|
+
});
|
|
4510
|
+
const workerCheck = await checkWorkerFile(projectRoot);
|
|
4511
|
+
results.push(workerCheck);
|
|
4512
|
+
const depsCheck = await checkDependencies(projectRoot);
|
|
4513
|
+
results.push(depsCheck);
|
|
4514
|
+
results.push(opfsBrowserNote());
|
|
4515
|
+
if (!options.skipNetwork) {
|
|
4516
|
+
const syncUrl = options.syncHttpUrl ?? await resolveSyncHttpUrl(projectRoot, config);
|
|
4517
|
+
const syncChecks = await checkSyncServer(syncUrl, schema);
|
|
4518
|
+
results.push(...syncChecks);
|
|
4519
|
+
} else {
|
|
4520
|
+
results.push({
|
|
4521
|
+
id: "sync-server",
|
|
4522
|
+
title: "Sync server",
|
|
4523
|
+
status: "skip",
|
|
4524
|
+
message: "Network checks skipped."
|
|
4525
|
+
});
|
|
4526
|
+
}
|
|
4527
|
+
} catch (error) {
|
|
4528
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4529
|
+
results.push({
|
|
4530
|
+
id: "schema",
|
|
4531
|
+
title: "Schema",
|
|
4532
|
+
status: "fail",
|
|
4533
|
+
message: `Failed to load schema: ${message}`,
|
|
4534
|
+
fix: "Ensure the schema exports defineSchema() as default and TypeScript can be evaluated (install tsx)."
|
|
4535
|
+
});
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
return results;
|
|
4539
|
+
}
|
|
4540
|
+
async function resolveSchemaPath(projectRoot, configSchema) {
|
|
4541
|
+
if (typeof configSchema === "string") {
|
|
4542
|
+
const resolved = (0, import_node_path24.resolve)(projectRoot, configSchema);
|
|
4543
|
+
try {
|
|
4544
|
+
await (0, import_promises15.access)(resolved);
|
|
4545
|
+
return resolved;
|
|
4546
|
+
} catch {
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
return findSchemaFile(projectRoot);
|
|
4550
|
+
}
|
|
4551
|
+
async function checkWorkerFile(projectRoot) {
|
|
4552
|
+
const candidates = [
|
|
4553
|
+
(0, import_node_path24.join)(projectRoot, "src", "kora-worker.ts"),
|
|
4554
|
+
(0, import_node_path24.join)(projectRoot, "src", "kora-worker.js")
|
|
4555
|
+
];
|
|
4556
|
+
for (const path of candidates) {
|
|
4557
|
+
try {
|
|
4558
|
+
await (0, import_promises15.access)(path);
|
|
4559
|
+
return {
|
|
4560
|
+
id: "worker",
|
|
4561
|
+
title: "SQLite WASM worker",
|
|
4562
|
+
status: "pass",
|
|
4563
|
+
message: `Found ${path.replace(projectRoot, ".")}.`
|
|
4564
|
+
};
|
|
4565
|
+
} catch {
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
return {
|
|
4569
|
+
id: "worker",
|
|
4570
|
+
title: "SQLite WASM worker",
|
|
4571
|
+
status: "warn",
|
|
4572
|
+
message: "No src/kora-worker.ts found.",
|
|
4573
|
+
fix: "Add kora-worker.ts and pass store.workerUrl from Vite (?worker&url) in createApp()."
|
|
4574
|
+
};
|
|
4575
|
+
}
|
|
4576
|
+
async function checkDependencies(projectRoot) {
|
|
4577
|
+
try {
|
|
4578
|
+
const raw = await (0, import_promises15.readFile)((0, import_node_path24.join)(projectRoot, "package.json"), "utf-8");
|
|
4579
|
+
const pkg = JSON.parse(raw);
|
|
4580
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
4581
|
+
const hasKora = Boolean(deps.korajs || deps.kora);
|
|
4582
|
+
const hasStore = Boolean(deps["@korajs/store"]);
|
|
4583
|
+
if (hasKora || hasStore) {
|
|
4584
|
+
return {
|
|
4585
|
+
id: "dependencies",
|
|
4586
|
+
title: "Dependencies",
|
|
4587
|
+
status: "pass",
|
|
4588
|
+
message: hasKora ? "korajs is listed in package.json." : "@korajs/store is listed in package.json."
|
|
4589
|
+
};
|
|
4590
|
+
}
|
|
4591
|
+
return {
|
|
4592
|
+
id: "dependencies",
|
|
4593
|
+
title: "Dependencies",
|
|
4594
|
+
status: "fail",
|
|
4595
|
+
message: "No korajs or @korajs/store dependency found.",
|
|
4596
|
+
fix: "Run pnpm add korajs @korajs/react (or your package manager equivalent)."
|
|
4597
|
+
};
|
|
4598
|
+
} catch {
|
|
4599
|
+
return {
|
|
4600
|
+
id: "dependencies",
|
|
4601
|
+
title: "Dependencies",
|
|
4602
|
+
status: "fail",
|
|
4603
|
+
message: "Could not read package.json.",
|
|
4604
|
+
fix: "Run this command from a Kora app directory with a valid package.json."
|
|
4605
|
+
};
|
|
4606
|
+
}
|
|
4607
|
+
}
|
|
4608
|
+
function opfsBrowserNote() {
|
|
4609
|
+
return {
|
|
4610
|
+
id: "opfs",
|
|
4611
|
+
title: "OPFS persistence",
|
|
4612
|
+
status: "skip",
|
|
4613
|
+
message: "OPFS availability is checked in the browser at runtime. Open the app and confirm no IndexedDB fallback warning in the console.",
|
|
4614
|
+
fix: "Use a Chromium-based browser with storage access; avoid private mode if persistence fails."
|
|
4615
|
+
};
|
|
4616
|
+
}
|
|
4617
|
+
async function resolveSyncHttpUrl(projectRoot, config) {
|
|
4618
|
+
const envUrl = await readEnvSyncUrl(projectRoot);
|
|
4619
|
+
if (envUrl) {
|
|
4620
|
+
return toSyncStatusHttpBase(envUrl);
|
|
4621
|
+
}
|
|
4622
|
+
const syncPort = typeof config?.dev?.sync === "object" && typeof config.dev.sync.port === "number" ? config.dev.sync.port : 3001;
|
|
4623
|
+
return `http://localhost:${syncPort}`;
|
|
4624
|
+
}
|
|
4625
|
+
async function readEnvSyncUrl(projectRoot) {
|
|
4626
|
+
for (const file of [".env", ".env.local"]) {
|
|
4627
|
+
const path = (0, import_node_path24.join)(projectRoot, file);
|
|
4628
|
+
try {
|
|
4629
|
+
const content = await (0, import_promises15.readFile)(path, "utf-8");
|
|
4630
|
+
for (const line of content.split("\n")) {
|
|
4631
|
+
const trimmed = line.trim();
|
|
4632
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
4633
|
+
const match = trimmed.match(/^VITE_SYNC_URL=(.+)$/);
|
|
4634
|
+
if (match?.[1]) {
|
|
4635
|
+
return match[1].trim().replace(/^["']|["']$/g, "");
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
} catch {
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
return null;
|
|
4642
|
+
}
|
|
4643
|
+
function toSyncStatusHttpBase(url) {
|
|
4644
|
+
try {
|
|
4645
|
+
const parsed = new URL(url);
|
|
4646
|
+
if (parsed.protocol === "ws:") {
|
|
4647
|
+
parsed.protocol = "http:";
|
|
4648
|
+
} else if (parsed.protocol === "wss:") {
|
|
4649
|
+
parsed.protocol = "https:";
|
|
4650
|
+
}
|
|
4651
|
+
parsed.pathname = "";
|
|
4652
|
+
parsed.search = "";
|
|
4653
|
+
parsed.hash = "";
|
|
4654
|
+
return parsed.toString().replace(/\/$/, "");
|
|
4655
|
+
} catch {
|
|
4656
|
+
return "http://localhost:3001";
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4659
|
+
async function checkSyncServer(httpBase, schema) {
|
|
4660
|
+
const statusUrl = `${httpBase.replace(/\/$/, "")}${STATUS_ENDPOINT}`;
|
|
4661
|
+
try {
|
|
4662
|
+
const response = await fetch(statusUrl, {
|
|
4663
|
+
signal: AbortSignal.timeout(5e3)
|
|
4664
|
+
});
|
|
4665
|
+
if (!response.ok) {
|
|
4666
|
+
return [
|
|
4667
|
+
{
|
|
4668
|
+
id: "sync-server",
|
|
4669
|
+
title: "Sync server",
|
|
4670
|
+
status: "fail",
|
|
4671
|
+
message: `GET ${statusUrl} returned ${response.status}.`,
|
|
4672
|
+
fix: "Start the sync server with `kora dev` or `pnpm run dev` in a sync template."
|
|
4673
|
+
}
|
|
4674
|
+
];
|
|
4675
|
+
}
|
|
4676
|
+
const body = await response.json();
|
|
4677
|
+
const results = [
|
|
4678
|
+
{
|
|
4679
|
+
id: "sync-server",
|
|
4680
|
+
title: "Sync server",
|
|
4681
|
+
status: "pass",
|
|
4682
|
+
message: `Reachable at ${statusUrl} (server v${body.version ?? "unknown"}).`
|
|
4683
|
+
}
|
|
4684
|
+
];
|
|
4685
|
+
if (typeof body.schemaVersion === "number" && body.schemaVersion !== schema.version) {
|
|
4686
|
+
results.push({
|
|
4687
|
+
id: "schema-version",
|
|
4688
|
+
title: "Schema version",
|
|
4689
|
+
status: "warn",
|
|
4690
|
+
message: `Client schema v${schema.version} differs from server schema v${body.schemaVersion}.`,
|
|
4691
|
+
fix: "Run migrations on the server and client, or align schema.version before syncing."
|
|
4692
|
+
});
|
|
4693
|
+
} else {
|
|
4694
|
+
results.push({
|
|
4695
|
+
id: "schema-version",
|
|
4696
|
+
title: "Schema version",
|
|
4697
|
+
status: "pass",
|
|
4698
|
+
message: `Client and server both use schema v${schema.version}.`
|
|
4699
|
+
});
|
|
4700
|
+
}
|
|
4701
|
+
return results;
|
|
4702
|
+
} catch (error) {
|
|
4703
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
4704
|
+
return [
|
|
4705
|
+
{
|
|
4706
|
+
id: "sync-server",
|
|
4707
|
+
title: "Sync server",
|
|
4708
|
+
status: "warn",
|
|
4709
|
+
message: `Could not reach ${statusUrl}: ${detail}`,
|
|
4710
|
+
fix: "Start the sync server, or pass --skip-network for local-only checks."
|
|
4711
|
+
}
|
|
4712
|
+
];
|
|
4713
|
+
}
|
|
4098
4714
|
}
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4715
|
+
function doctorHasFailures(results) {
|
|
4716
|
+
return results.some((r) => r.status === "fail");
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4719
|
+
// src/commands/doctor/doctor-command.ts
|
|
4720
|
+
var doctorCommand = (0, import_citty6.defineCommand)({
|
|
4721
|
+
meta: {
|
|
4722
|
+
name: "doctor",
|
|
4723
|
+
description: "Check Kora project setup (schema, worker, sync server, versions)"
|
|
4724
|
+
},
|
|
4725
|
+
args: {
|
|
4726
|
+
url: {
|
|
4727
|
+
type: "string",
|
|
4728
|
+
description: "Sync server HTTP base URL for status probe (default: from .env or localhost:3001)"
|
|
4729
|
+
},
|
|
4730
|
+
"skip-network": {
|
|
4731
|
+
type: "boolean",
|
|
4732
|
+
description: "Skip sync server and schema version network checks",
|
|
4733
|
+
default: false
|
|
4734
|
+
}
|
|
4735
|
+
},
|
|
4736
|
+
async run({ args }) {
|
|
4737
|
+
const logger = createLogger();
|
|
4738
|
+
const projectRoot = await findProjectRoot();
|
|
4739
|
+
if (!projectRoot) {
|
|
4740
|
+
throw new InvalidProjectError(process.cwd());
|
|
4741
|
+
}
|
|
4742
|
+
const syncHttpUrl = typeof args.url === "string" ? args.url : void 0;
|
|
4743
|
+
const skipNetwork = args["skip-network"] === true;
|
|
4744
|
+
logger.banner();
|
|
4745
|
+
logger.info(`Checking project at ${projectRoot}`);
|
|
4746
|
+
logger.blank();
|
|
4747
|
+
const results = await runDoctorChecks({
|
|
4748
|
+
projectRoot,
|
|
4749
|
+
syncHttpUrl,
|
|
4750
|
+
skipNetwork
|
|
4751
|
+
});
|
|
4752
|
+
for (const check of results) {
|
|
4753
|
+
const prefix = check.status === "pass" ? "success" : check.status === "warn" ? "warn" : check.status === "fail" ? "error" : "step";
|
|
4754
|
+
logger[prefix](`${check.title}: ${check.message}`);
|
|
4755
|
+
if (check.fix && check.status !== "pass" && check.status !== "skip") {
|
|
4756
|
+
logger.step(` Fix: ${check.fix}`);
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
logger.blank();
|
|
4760
|
+
if (doctorHasFailures(results)) {
|
|
4761
|
+
logger.error("Doctor found blocking issues.");
|
|
4762
|
+
process.exitCode = 1;
|
|
4763
|
+
return;
|
|
4764
|
+
}
|
|
4765
|
+
const warnings = results.filter((r) => r.status === "warn").length;
|
|
4766
|
+
if (warnings > 0) {
|
|
4767
|
+
logger.warn(`Doctor finished with ${warnings} warning(s).`);
|
|
4768
|
+
} else {
|
|
4769
|
+
logger.success("All checks passed.");
|
|
4770
|
+
}
|
|
4104
4771
|
}
|
|
4105
|
-
process.exit(0);
|
|
4106
|
-
};
|
|
4107
|
-
process.on('SIGINT', () => {
|
|
4108
|
-
void shutdown();
|
|
4109
|
-
});
|
|
4110
|
-
process.on('SIGTERM', () => {
|
|
4111
|
-
void shutdown();
|
|
4112
4772
|
});
|
|
4113
|
-
await server.start();
|
|
4114
|
-
process.stdout.write('Managed sync server running on ws://localhost:' + String(config.port ?? 3001) + '\\n');
|
|
4115
|
-
await new Promise(() => {});
|
|
4116
|
-
`;
|
|
4117
4773
|
|
|
4118
4774
|
// src/commands/generate/generate-command.ts
|
|
4119
|
-
var
|
|
4120
|
-
var
|
|
4121
|
-
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
|
+
}
|
|
4122
4831
|
|
|
4123
4832
|
// src/commands/generate/type-generator.ts
|
|
4124
4833
|
function generateTypes(schema) {
|
|
@@ -4132,7 +4841,7 @@ function generateTypes(schema) {
|
|
|
4132
4841
|
return lines.join("\n");
|
|
4133
4842
|
}
|
|
4134
4843
|
for (const [name, collection] of Object.entries(schema.collections)) {
|
|
4135
|
-
const pascal =
|
|
4844
|
+
const pascal = toPascalCase2(name);
|
|
4136
4845
|
const fields = collection.fields;
|
|
4137
4846
|
lines.push(`export interface ${pascal}Record {`);
|
|
4138
4847
|
lines.push(" readonly id: string");
|
|
@@ -4209,7 +4918,7 @@ function itemKindToTypeScript(kind) {
|
|
|
4209
4918
|
return "unknown";
|
|
4210
4919
|
}
|
|
4211
4920
|
}
|
|
4212
|
-
function
|
|
4921
|
+
function toPascalCase2(name) {
|
|
4213
4922
|
return name.split(/[_-]/).map((part) => {
|
|
4214
4923
|
if (part.length === 0) return "";
|
|
4215
4924
|
const first = part[0];
|
|
@@ -4218,13 +4927,13 @@ function toPascalCase(name) {
|
|
|
4218
4927
|
}
|
|
4219
4928
|
|
|
4220
4929
|
// src/commands/generate/generate-command.ts
|
|
4221
|
-
var generateCommand = (0,
|
|
4930
|
+
var generateCommand = (0, import_citty7.defineCommand)({
|
|
4222
4931
|
meta: {
|
|
4223
4932
|
name: "generate",
|
|
4224
4933
|
description: "Generate code from your Kora schema"
|
|
4225
4934
|
},
|
|
4226
4935
|
subCommands: {
|
|
4227
|
-
types: (0,
|
|
4936
|
+
types: (0, import_citty7.defineCommand)({
|
|
4228
4937
|
meta: {
|
|
4229
4938
|
name: "types",
|
|
4230
4939
|
description: "Generate TypeScript types from your schema"
|
|
@@ -4248,7 +4957,7 @@ var generateCommand = (0, import_citty4.defineCommand)({
|
|
|
4248
4957
|
}
|
|
4249
4958
|
let schemaPath;
|
|
4250
4959
|
if (args.schema && typeof args.schema === "string") {
|
|
4251
|
-
schemaPath = (0,
|
|
4960
|
+
schemaPath = (0, import_node_path25.resolve)(args.schema);
|
|
4252
4961
|
} else {
|
|
4253
4962
|
const found = await findSchemaFile(projectRoot);
|
|
4254
4963
|
if (!found) {
|
|
@@ -4263,7 +4972,7 @@ var generateCommand = (0, import_citty4.defineCommand)({
|
|
|
4263
4972
|
}
|
|
4264
4973
|
logger.step(`Reading schema from ${schemaPath}...`);
|
|
4265
4974
|
const schemaModule = await import(schemaPath);
|
|
4266
|
-
const schema =
|
|
4975
|
+
const schema = extractSchema2(schemaModule);
|
|
4267
4976
|
if (!schema) {
|
|
4268
4977
|
logger.error("Schema file must export a SchemaDefinition as the default export.");
|
|
4269
4978
|
process.exitCode = 1;
|
|
@@ -4271,31 +4980,193 @@ var generateCommand = (0, import_citty4.defineCommand)({
|
|
|
4271
4980
|
}
|
|
4272
4981
|
const output = generateTypes(schema);
|
|
4273
4982
|
const outputFile = typeof args.output === "string" ? args.output : "kora/generated/types.ts";
|
|
4274
|
-
const outputPath = (0,
|
|
4275
|
-
await (0,
|
|
4276
|
-
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");
|
|
4277
4986
|
logger.success(`Generated types at ${outputPath}`);
|
|
4278
4987
|
}
|
|
4988
|
+
}),
|
|
4989
|
+
hooks: (0, import_citty7.defineCommand)({
|
|
4990
|
+
meta: {
|
|
4991
|
+
name: "hooks",
|
|
4992
|
+
description: "Generate per-collection React hook stubs"
|
|
4993
|
+
},
|
|
4994
|
+
args: {
|
|
4995
|
+
schema: {
|
|
4996
|
+
type: "string",
|
|
4997
|
+
description: "Path to schema file"
|
|
4998
|
+
},
|
|
4999
|
+
output: {
|
|
5000
|
+
type: "string",
|
|
5001
|
+
description: "Output directory for hooks",
|
|
5002
|
+
default: "kora/generated/hooks"
|
|
5003
|
+
},
|
|
5004
|
+
types: {
|
|
5005
|
+
type: "string",
|
|
5006
|
+
description: "Relative import path to generated types from hook files",
|
|
5007
|
+
default: "../types"
|
|
5008
|
+
}
|
|
5009
|
+
},
|
|
5010
|
+
async run({ args }) {
|
|
5011
|
+
const logger = createLogger();
|
|
5012
|
+
const projectRoot = await findProjectRoot();
|
|
5013
|
+
if (!projectRoot) {
|
|
5014
|
+
throw new InvalidProjectError(process.cwd());
|
|
5015
|
+
}
|
|
5016
|
+
let schemaPath;
|
|
5017
|
+
if (args.schema && typeof args.schema === "string") {
|
|
5018
|
+
schemaPath = (0, import_node_path25.resolve)(projectRoot, args.schema);
|
|
5019
|
+
} else {
|
|
5020
|
+
const found = await findSchemaFile(projectRoot);
|
|
5021
|
+
if (!found) {
|
|
5022
|
+
throw new SchemaNotFoundError([
|
|
5023
|
+
"src/schema.ts",
|
|
5024
|
+
"schema.ts",
|
|
5025
|
+
"src/schema.js",
|
|
5026
|
+
"schema.js"
|
|
5027
|
+
]);
|
|
5028
|
+
}
|
|
5029
|
+
schemaPath = found;
|
|
5030
|
+
}
|
|
5031
|
+
const schema = await loadSchemaDefinition(schemaPath, projectRoot);
|
|
5032
|
+
const outputDir = typeof args.output === "string" ? (0, import_node_path25.resolve)(projectRoot, args.output) : (0, import_node_path25.resolve)(projectRoot, "kora/generated/hooks");
|
|
5033
|
+
const files = generateCollectionHooks(schema);
|
|
5034
|
+
for (const [name, content] of files) {
|
|
5035
|
+
const adjusted = typeof args.types === "string" && args.types !== "../types" ? content.replace("from '../types'", `from '${args.types}'`) : content;
|
|
5036
|
+
const filePath = (0, import_node_path25.resolve)(outputDir, name);
|
|
5037
|
+
await (0, import_promises16.mkdir)((0, import_node_path25.dirname)(filePath), { recursive: true });
|
|
5038
|
+
await (0, import_promises16.writeFile)(filePath, adjusted, "utf-8");
|
|
5039
|
+
}
|
|
5040
|
+
logger.success(`Generated ${String(files.size)} hook file(s) at ${outputDir}`);
|
|
5041
|
+
logger.step("Run `kora generate types` first if ../types is missing.");
|
|
5042
|
+
}
|
|
4279
5043
|
})
|
|
4280
5044
|
}
|
|
4281
5045
|
});
|
|
4282
|
-
function
|
|
5046
|
+
function extractSchema2(mod) {
|
|
4283
5047
|
if (typeof mod !== "object" || mod === null) return null;
|
|
4284
5048
|
const record = mod;
|
|
4285
5049
|
const candidate = record.default ?? record;
|
|
4286
|
-
if (
|
|
5050
|
+
if (isSchemaDefinition2(candidate)) return candidate;
|
|
4287
5051
|
return null;
|
|
4288
5052
|
}
|
|
4289
|
-
function
|
|
5053
|
+
function isSchemaDefinition2(value) {
|
|
4290
5054
|
if (typeof value !== "object" || value === null) return false;
|
|
4291
5055
|
const obj = value;
|
|
4292
5056
|
return typeof obj.version === "number" && typeof obj.collections === "object" && obj.collections !== null;
|
|
4293
5057
|
}
|
|
4294
5058
|
|
|
5059
|
+
// src/commands/logs/logs-command.ts
|
|
5060
|
+
var import_citty8 = require("citty");
|
|
5061
|
+
var DEFAULT_SYNC_PORT2 = 3001;
|
|
5062
|
+
var EVENTS_ENDPOINT = "/__kora/events";
|
|
5063
|
+
var logsCommand = (0, import_citty8.defineCommand)({
|
|
5064
|
+
meta: {
|
|
5065
|
+
name: "logs",
|
|
5066
|
+
description: "Stream real-time events from a Kora sync server"
|
|
5067
|
+
},
|
|
5068
|
+
args: {
|
|
5069
|
+
url: {
|
|
5070
|
+
type: "string",
|
|
5071
|
+
description: "Sync server URL (default: http://localhost:3001)",
|
|
5072
|
+
default: `http://localhost:${DEFAULT_SYNC_PORT2}`
|
|
5073
|
+
},
|
|
5074
|
+
follow: {
|
|
5075
|
+
type: "boolean",
|
|
5076
|
+
description: "Follow log output (like tail -f)",
|
|
5077
|
+
default: true,
|
|
5078
|
+
alias: "f"
|
|
5079
|
+
},
|
|
5080
|
+
level: {
|
|
5081
|
+
type: "string",
|
|
5082
|
+
description: "Filter by log level (info, warn, error)"
|
|
5083
|
+
},
|
|
5084
|
+
token: {
|
|
5085
|
+
type: "string",
|
|
5086
|
+
description: "Admin token (defaults to KORA_ADMIN_TOKEN)"
|
|
5087
|
+
}
|
|
5088
|
+
},
|
|
5089
|
+
async run({ args }) {
|
|
5090
|
+
const logger = createLogger();
|
|
5091
|
+
const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT2}`;
|
|
5092
|
+
const follow = args.follow !== false;
|
|
5093
|
+
const levelFilter = typeof args.level === "string" ? args.level : null;
|
|
5094
|
+
const token = typeof args.token === "string" ? args.token : process.env.KORA_ADMIN_TOKEN ?? void 0;
|
|
5095
|
+
const eventsUrl = `${url.replace(/\/$/, "")}${EVENTS_ENDPOINT}`;
|
|
5096
|
+
logger.banner();
|
|
5097
|
+
logger.info(`Connecting to ${eventsUrl}...`);
|
|
5098
|
+
logger.blank();
|
|
5099
|
+
try {
|
|
5100
|
+
const response = await fetch(eventsUrl, {
|
|
5101
|
+
headers: token ? { Authorization: `Bearer ${token}` } : void 0
|
|
5102
|
+
});
|
|
5103
|
+
if (!response.ok) {
|
|
5104
|
+
throw new Error(
|
|
5105
|
+
`Failed to connect to ${eventsUrl}: ${response.status} ${response.statusText}`
|
|
5106
|
+
);
|
|
5107
|
+
}
|
|
5108
|
+
const reader = response.body?.getReader();
|
|
5109
|
+
if (!reader) {
|
|
5110
|
+
throw new Error("Response body is not readable");
|
|
5111
|
+
}
|
|
5112
|
+
const decoder = new TextDecoder();
|
|
5113
|
+
let buffer = "";
|
|
5114
|
+
const processLine = (line) => {
|
|
5115
|
+
if (line.startsWith("event: ")) {
|
|
5116
|
+
return;
|
|
5117
|
+
}
|
|
5118
|
+
if (line.startsWith("data: ")) {
|
|
5119
|
+
const data = line.slice(6);
|
|
5120
|
+
try {
|
|
5121
|
+
const parsed = JSON.parse(data);
|
|
5122
|
+
const level = String(parsed.level ?? "info");
|
|
5123
|
+
if (levelFilter && level !== levelFilter) return;
|
|
5124
|
+
const timestamp = parsed.timestamp ? new Date(parsed.timestamp).toISOString() : "";
|
|
5125
|
+
const event = String(parsed.event ?? "unknown");
|
|
5126
|
+
const nodeId = parsed.nodeId ? ` [${parsed.nodeId}]` : "";
|
|
5127
|
+
const session = parsed.sessionId ? ` <${parsed.sessionId}>` : "";
|
|
5128
|
+
const count = parsed.count ? ` (${parsed.count})` : "";
|
|
5129
|
+
const error = parsed.error ? ` \u2014 ${parsed.error}` : "";
|
|
5130
|
+
const prefix = level === "error" ? "\u2717" : level === "warn" ? "\u26A0" : "\u25CF";
|
|
5131
|
+
const color = level === "error" ? "\x1B[31m" : level === "warn" ? "\x1B[33m" : "\x1B[36m";
|
|
5132
|
+
console.log(
|
|
5133
|
+
`${color}${prefix}${"\x1B[0m"} ${timestamp.slice(11, 23)} ${event}${nodeId}${session}${count}${error}`
|
|
5134
|
+
);
|
|
5135
|
+
} catch {
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
5138
|
+
};
|
|
5139
|
+
const read = async () => {
|
|
5140
|
+
while (true) {
|
|
5141
|
+
const { done, value } = await reader.read();
|
|
5142
|
+
if (done) break;
|
|
5143
|
+
buffer += decoder.decode(value, { stream: true });
|
|
5144
|
+
const lines = buffer.split("\n");
|
|
5145
|
+
buffer = lines.pop() ?? "";
|
|
5146
|
+
for (const line of lines) {
|
|
5147
|
+
processLine(line.trim());
|
|
5148
|
+
}
|
|
5149
|
+
if (!follow) break;
|
|
5150
|
+
}
|
|
5151
|
+
};
|
|
5152
|
+
await read();
|
|
5153
|
+
} catch (error) {
|
|
5154
|
+
logger.error(`Failed to connect to ${eventsUrl}`);
|
|
5155
|
+
if (error instanceof Error) {
|
|
5156
|
+
logger.error(error.message);
|
|
5157
|
+
}
|
|
5158
|
+
logger.blank();
|
|
5159
|
+
logger.step("Make sure the Kora sync server is running.");
|
|
5160
|
+
logger.step("Start it with: kora dev");
|
|
5161
|
+
process.exit(1);
|
|
5162
|
+
}
|
|
5163
|
+
}
|
|
5164
|
+
});
|
|
5165
|
+
|
|
4295
5166
|
// src/commands/migrate/migrate-command.ts
|
|
4296
|
-
var
|
|
4297
|
-
var
|
|
4298
|
-
var
|
|
5167
|
+
var import_promises17 = require("fs/promises");
|
|
5168
|
+
var import_node_path26 = require("path");
|
|
5169
|
+
var import_citty9 = require("citty");
|
|
4299
5170
|
|
|
4300
5171
|
// src/commands/migrate/migration-generator.ts
|
|
4301
5172
|
var import_core2 = require("@korajs/core");
|
|
@@ -4861,83 +5732,34 @@ async function loadPostgresModule() {
|
|
|
4861
5732
|
}
|
|
4862
5733
|
}
|
|
4863
5734
|
|
|
4864
|
-
// src/commands/migrate/
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
const object = value;
|
|
4887
|
-
return typeof object.version === "number" && typeof object.collections === "object" && object.collections !== null && typeof object.relations === "object" && object.relations !== null;
|
|
4888
|
-
}
|
|
4889
|
-
async function loadTypeScriptModule(schemaPath, projectRoot) {
|
|
4890
|
-
if (!await hasTsxInstalled(projectRoot)) {
|
|
4891
|
-
throw new Error(
|
|
4892
|
-
`Schema file is TypeScript (${schemaPath}) but local "tsx" was not found. Install tsx in the project.`
|
|
4893
|
-
);
|
|
4894
|
-
}
|
|
4895
|
-
const script = "const modulePath = process.argv[process.argv.length - 1];import('node:url').then(u => import(u.pathToFileURL(modulePath).href)).then(mod => { const v = mod.default ?? mod; process.stdout.write(JSON.stringify(v)) }).catch(e => { process.stderr.write(String(e)); process.exit(1) })";
|
|
4896
|
-
const output = await runCommand2(
|
|
4897
|
-
process.execPath,
|
|
4898
|
-
["--import", "tsx", "--eval", script, schemaPath],
|
|
4899
|
-
projectRoot
|
|
4900
|
-
);
|
|
4901
|
-
try {
|
|
4902
|
-
return JSON.parse(output);
|
|
4903
|
-
} catch {
|
|
4904
|
-
throw new Error(`Failed to parse schema module output for ${schemaPath}`);
|
|
4905
|
-
}
|
|
4906
|
-
}
|
|
4907
|
-
async function runCommand2(command, args, cwd) {
|
|
4908
|
-
return await new Promise((resolve9, reject) => {
|
|
4909
|
-
const child = (0, import_node_child_process11.spawn)(command, args, {
|
|
4910
|
-
cwd,
|
|
4911
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
4912
|
-
env: process.env
|
|
4913
|
-
});
|
|
4914
|
-
let stdout = "";
|
|
4915
|
-
let stderr = "";
|
|
4916
|
-
child.stdout?.on("data", (chunk) => {
|
|
4917
|
-
stdout += chunk.toString("utf-8");
|
|
4918
|
-
});
|
|
4919
|
-
child.stderr?.on("data", (chunk) => {
|
|
4920
|
-
stderr += chunk.toString("utf-8");
|
|
4921
|
-
});
|
|
4922
|
-
child.on("error", (error) => {
|
|
4923
|
-
reject(error);
|
|
4924
|
-
});
|
|
4925
|
-
child.on("exit", (code) => {
|
|
4926
|
-
if (code === 0) {
|
|
4927
|
-
resolve9(stdout.trim());
|
|
4928
|
-
return;
|
|
4929
|
-
}
|
|
4930
|
-
reject(
|
|
4931
|
-
new Error(`Failed to load TypeScript schema (exit ${code ?? "unknown"}): ${stderr.trim()}`)
|
|
4932
|
-
);
|
|
4933
|
-
});
|
|
4934
|
-
});
|
|
5735
|
+
// src/commands/migrate/operation-transform-generator.ts
|
|
5736
|
+
function generateOperationTransformModule(fromVersion, toVersion) {
|
|
5737
|
+
return [
|
|
5738
|
+
"import type { Operation, OperationTransform } from '@korajs/core'",
|
|
5739
|
+
"",
|
|
5740
|
+
"/**",
|
|
5741
|
+
` * Operation transforms for schema v${fromVersion} \u2192 v${toVersion}.`,
|
|
5742
|
+
" * Implement `transform` to rewrite or drop legacy operations during sync.",
|
|
5743
|
+
" * @see https://korajs.dev/docs/migrations",
|
|
5744
|
+
" */",
|
|
5745
|
+
"export const operationTransforms: OperationTransform[] = [",
|
|
5746
|
+
" {",
|
|
5747
|
+
` fromVersion: ${fromVersion},`,
|
|
5748
|
+
` toVersion: ${toVersion},`,
|
|
5749
|
+
" transform(operation: Operation): Operation | null {",
|
|
5750
|
+
" // Return null to drop unmigratable operations.",
|
|
5751
|
+
" return operation",
|
|
5752
|
+
" },",
|
|
5753
|
+
" },",
|
|
5754
|
+
"]",
|
|
5755
|
+
""
|
|
5756
|
+
].join("\n");
|
|
4935
5757
|
}
|
|
4936
5758
|
|
|
4937
5759
|
// src/commands/migrate/migrate-command.ts
|
|
4938
5760
|
var SNAPSHOT_PATH = "kora/schema.snapshot.json";
|
|
4939
5761
|
var MIGRATIONS_DIR = "kora/migrations";
|
|
4940
|
-
var migrateCommand = (0,
|
|
5762
|
+
var migrateCommand = (0, import_citty9.defineCommand)({
|
|
4941
5763
|
meta: {
|
|
4942
5764
|
name: "migrate",
|
|
4943
5765
|
description: "Detect schema changes and generate/apply migrations"
|
|
@@ -4979,12 +5801,12 @@ var migrateCommand = (0, import_citty5.defineCommand)({
|
|
|
4979
5801
|
throw new InvalidProjectError(process.cwd());
|
|
4980
5802
|
}
|
|
4981
5803
|
const config = await loadKoraConfig(projectRoot);
|
|
4982
|
-
const resolvedSchemaPath = typeof args.schema === "string" ? (0,
|
|
5804
|
+
const resolvedSchemaPath = typeof args.schema === "string" ? (0, import_node_path26.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path26.resolve)(projectRoot, config.schema) : await findSchemaFile(projectRoot);
|
|
4983
5805
|
if (!resolvedSchemaPath) {
|
|
4984
5806
|
throw new SchemaNotFoundError(["src/schema.ts", "schema.ts", "src/schema.js", "schema.js"]);
|
|
4985
5807
|
}
|
|
4986
5808
|
const currentSchema = await loadSchemaDefinition(resolvedSchemaPath, projectRoot);
|
|
4987
|
-
const snapshotFile = (0,
|
|
5809
|
+
const snapshotFile = (0, import_node_path26.join)(projectRoot, SNAPSHOT_PATH);
|
|
4988
5810
|
const previousSchema = await readSchemaSnapshot(snapshotFile);
|
|
4989
5811
|
if (!previousSchema) {
|
|
4990
5812
|
if (args["dry-run"] === true) {
|
|
@@ -4997,7 +5819,7 @@ var migrateCommand = (0, import_citty5.defineCommand)({
|
|
|
4997
5819
|
return;
|
|
4998
5820
|
}
|
|
4999
5821
|
const diff = diffSchemas(previousSchema, currentSchema);
|
|
5000
|
-
const outputDir = typeof args["output-dir"] === "string" ? (0,
|
|
5822
|
+
const outputDir = typeof args["output-dir"] === "string" ? (0, import_node_path26.resolve)(projectRoot, args["output-dir"]) : (0, import_node_path26.resolve)(projectRoot, MIGRATIONS_DIR);
|
|
5001
5823
|
if (!diff.hasChanges) {
|
|
5002
5824
|
logger.success("No schema changes detected.");
|
|
5003
5825
|
if (args.apply === true) {
|
|
@@ -5049,7 +5871,7 @@ var migrateCommand = (0, import_citty5.defineCommand)({
|
|
|
5049
5871
|
logger.warn("Dry run enabled: no files written, no migrations applied.");
|
|
5050
5872
|
return;
|
|
5051
5873
|
}
|
|
5052
|
-
await (0,
|
|
5874
|
+
await (0, import_promises17.mkdir)(outputDir, { recursive: true });
|
|
5053
5875
|
const migrationPath = await writeMigrationFile(
|
|
5054
5876
|
outputDir,
|
|
5055
5877
|
diff.fromVersion,
|
|
@@ -5085,7 +5907,7 @@ var migrateCommand = (0, import_citty5.defineCommand)({
|
|
|
5085
5907
|
});
|
|
5086
5908
|
async function readSchemaSnapshot(path) {
|
|
5087
5909
|
try {
|
|
5088
|
-
const content = await (0,
|
|
5910
|
+
const content = await (0, import_promises17.readFile)(path, "utf-8");
|
|
5089
5911
|
return JSON.parse(content);
|
|
5090
5912
|
} catch {
|
|
5091
5913
|
return null;
|
|
@@ -5106,15 +5928,15 @@ function isInteractiveTerminal2() {
|
|
|
5106
5928
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
5107
5929
|
}
|
|
5108
5930
|
async function writeSchemaSnapshot(path, schema) {
|
|
5109
|
-
await (0,
|
|
5110
|
-
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)}
|
|
5111
5933
|
`, "utf-8");
|
|
5112
5934
|
}
|
|
5113
5935
|
async function writeMigrationFile(outputDir, fromVersion, toVersion, generated) {
|
|
5114
|
-
const existing = await (0,
|
|
5936
|
+
const existing = await (0, import_promises17.readdir)(outputDir).catch(() => []);
|
|
5115
5937
|
const sequence = existing.filter((file) => /^\d{3}-/.test(file)).length + 1;
|
|
5116
5938
|
const filename = `${String(sequence).padStart(3, "0")}-v${fromVersion}-to-v${toVersion}.ts`;
|
|
5117
|
-
const path = (0,
|
|
5939
|
+
const path = (0, import_node_path26.join)(outputDir, filename);
|
|
5118
5940
|
const migrationId = filename.replace(/\.ts$/, "");
|
|
5119
5941
|
const fileContent = [
|
|
5120
5942
|
`export const up = ${JSON.stringify(generated.up, null, 2)} as const`,
|
|
@@ -5126,8 +5948,10 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
|
|
|
5126
5948
|
`export const containsBreakingChanges = ${generated.containsBreakingChanges}`,
|
|
5127
5949
|
""
|
|
5128
5950
|
].join("\n");
|
|
5129
|
-
await (0,
|
|
5130
|
-
|
|
5951
|
+
await (0, import_promises17.writeFile)(path, fileContent, "utf-8");
|
|
5952
|
+
const transformsPath = (0, import_node_path26.join)(outputDir, `${migrationId}.transforms.ts`);
|
|
5953
|
+
await (0, import_promises17.writeFile)(transformsPath, generateOperationTransformModule(fromVersion, toVersion), "utf-8");
|
|
5954
|
+
await writeMigrationManifest((0, import_node_path26.join)(outputDir, `${migrationId}.json`), {
|
|
5131
5955
|
id: migrationId,
|
|
5132
5956
|
fromVersion,
|
|
5133
5957
|
toVersion,
|
|
@@ -5139,22 +5963,22 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
|
|
|
5139
5963
|
return path;
|
|
5140
5964
|
}
|
|
5141
5965
|
async function writeMigrationManifest(path, manifest) {
|
|
5142
|
-
await (0,
|
|
5966
|
+
await (0, import_promises17.writeFile)(path, `${JSON.stringify(manifest, null, 2)}
|
|
5143
5967
|
`, "utf-8");
|
|
5144
5968
|
}
|
|
5145
5969
|
async function listMigrationManifests(outputDir) {
|
|
5146
|
-
const files = await (0,
|
|
5970
|
+
const files = await (0, import_promises17.readdir)(outputDir).catch(() => []);
|
|
5147
5971
|
const migrationFiles = files.filter((file) => /^\d{3}-.*\.ts$/.test(file)).sort((left, right) => left.localeCompare(right));
|
|
5148
5972
|
const manifests = [];
|
|
5149
5973
|
for (const file of migrationFiles) {
|
|
5150
5974
|
const id = file.replace(/\.ts$/, "");
|
|
5151
|
-
const manifestPath = (0,
|
|
5975
|
+
const manifestPath = (0, import_node_path26.join)(outputDir, `${id}.json`);
|
|
5152
5976
|
const jsonManifest = await readMigrationManifest(manifestPath);
|
|
5153
5977
|
if (jsonManifest) {
|
|
5154
5978
|
manifests.push({ ...jsonManifest, id });
|
|
5155
5979
|
continue;
|
|
5156
5980
|
}
|
|
5157
|
-
const migrationPath = (0,
|
|
5981
|
+
const migrationPath = (0, import_node_path26.join)(outputDir, file);
|
|
5158
5982
|
const sourceManifest = await readMigrationManifestFromSource(migrationPath, id);
|
|
5159
5983
|
manifests.push(sourceManifest);
|
|
5160
5984
|
}
|
|
@@ -5162,7 +5986,7 @@ async function listMigrationManifests(outputDir) {
|
|
|
5162
5986
|
}
|
|
5163
5987
|
async function readMigrationManifest(path) {
|
|
5164
5988
|
try {
|
|
5165
|
-
const content = await (0,
|
|
5989
|
+
const content = await (0, import_promises17.readFile)(path, "utf-8");
|
|
5166
5990
|
return JSON.parse(content);
|
|
5167
5991
|
} catch (error) {
|
|
5168
5992
|
const code = error.code;
|
|
@@ -5173,7 +5997,7 @@ async function readMigrationManifest(path) {
|
|
|
5173
5997
|
}
|
|
5174
5998
|
}
|
|
5175
5999
|
async function readMigrationManifestFromSource(path, id) {
|
|
5176
|
-
const content = await (0,
|
|
6000
|
+
const content = await (0, import_promises17.readFile)(path, "utf-8");
|
|
5177
6001
|
const versions = parseVersionsFromMigrationId(id);
|
|
5178
6002
|
return {
|
|
5179
6003
|
id,
|
|
@@ -5190,9 +6014,14 @@ function parseVersionsFromMigrationId(id) {
|
|
|
5190
6014
|
if (!match) {
|
|
5191
6015
|
throw new Error(`Migration id "${id}" does not include a vX-to-vY version suffix.`);
|
|
5192
6016
|
}
|
|
6017
|
+
const fromVersionText = match[1];
|
|
6018
|
+
const toVersionText = match[2];
|
|
6019
|
+
if (fromVersionText === void 0 || toVersionText === void 0) {
|
|
6020
|
+
throw new Error(`Migration id "${id}" does not include a vX-to-vY version suffix.`);
|
|
6021
|
+
}
|
|
5193
6022
|
return {
|
|
5194
|
-
fromVersion: Number.parseInt(
|
|
5195
|
-
toVersion: Number.parseInt(
|
|
6023
|
+
fromVersion: Number.parseInt(fromVersionText, 10),
|
|
6024
|
+
toVersion: Number.parseInt(toVersionText, 10)
|
|
5196
6025
|
};
|
|
5197
6026
|
}
|
|
5198
6027
|
function parseStringArrayExport(source, exportName) {
|
|
@@ -5230,18 +6059,18 @@ function parseLiteralExport(source, exportName) {
|
|
|
5230
6059
|
}
|
|
5231
6060
|
function resolveSqliteApplyPath(dbArg, projectRoot, config) {
|
|
5232
6061
|
if (typeof dbArg === "string") {
|
|
5233
|
-
return (0,
|
|
6062
|
+
return (0, import_node_path26.resolve)(projectRoot, dbArg);
|
|
5234
6063
|
}
|
|
5235
6064
|
const sync = config?.dev?.sync;
|
|
5236
6065
|
if (typeof sync === "object" && sync !== null) {
|
|
5237
6066
|
if (sync.store === "sqlite") {
|
|
5238
|
-
return (0,
|
|
6067
|
+
return (0, import_node_path26.join)(projectRoot, "kora-sync.db");
|
|
5239
6068
|
}
|
|
5240
6069
|
if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "sqlite") {
|
|
5241
6070
|
if (typeof sync.store.filename === "string" && sync.store.filename.length > 0) {
|
|
5242
|
-
return (0,
|
|
6071
|
+
return (0, import_node_path26.resolve)(projectRoot, sync.store.filename);
|
|
5243
6072
|
}
|
|
5244
|
-
return (0,
|
|
6073
|
+
return (0, import_node_path26.join)(projectRoot, "kora-sync.db");
|
|
5245
6074
|
}
|
|
5246
6075
|
}
|
|
5247
6076
|
return void 0;
|
|
@@ -5258,19 +6087,156 @@ function resolvePostgresConnectionString(config) {
|
|
|
5258
6087
|
return void 0;
|
|
5259
6088
|
}
|
|
5260
6089
|
|
|
6090
|
+
// src/commands/status/status-command.ts
|
|
6091
|
+
var import_citty10 = require("citty");
|
|
6092
|
+
var DEFAULT_SYNC_PORT3 = 3001;
|
|
6093
|
+
var STATUS_ENDPOINT2 = "/__kora/status";
|
|
6094
|
+
async function fetchServerStatus(url, token) {
|
|
6095
|
+
const statusUrl = `${url.replace(/\/$/, "")}${STATUS_ENDPOINT2}`;
|
|
6096
|
+
const response = await fetch(statusUrl, {
|
|
6097
|
+
headers: token ? { Authorization: `Bearer ${token}` } : void 0
|
|
6098
|
+
});
|
|
6099
|
+
if (!response.ok) {
|
|
6100
|
+
throw new Error(
|
|
6101
|
+
`Failed to fetch status from ${statusUrl}: ${response.status} ${response.statusText}`
|
|
6102
|
+
);
|
|
6103
|
+
}
|
|
6104
|
+
return await response.json();
|
|
6105
|
+
}
|
|
6106
|
+
function formatUptime(ms) {
|
|
6107
|
+
const seconds = Math.floor(ms / 1e3);
|
|
6108
|
+
const minutes = Math.floor(seconds / 60);
|
|
6109
|
+
const hours = Math.floor(minutes / 60);
|
|
6110
|
+
const days = Math.floor(hours / 24);
|
|
6111
|
+
const parts = [];
|
|
6112
|
+
if (days > 0) parts.push(`${days}d`);
|
|
6113
|
+
if (hours % 24 > 0) parts.push(`${hours % 24}h`);
|
|
6114
|
+
if (minutes % 60 > 0) parts.push(`${minutes % 60}m`);
|
|
6115
|
+
parts.push(`${seconds % 60}s`);
|
|
6116
|
+
return parts.join(" ");
|
|
6117
|
+
}
|
|
6118
|
+
var statusCommand = (0, import_citty10.defineCommand)({
|
|
6119
|
+
meta: {
|
|
6120
|
+
name: "status",
|
|
6121
|
+
description: "Show Kora sync server status"
|
|
6122
|
+
},
|
|
6123
|
+
args: {
|
|
6124
|
+
url: {
|
|
6125
|
+
type: "string",
|
|
6126
|
+
description: "Sync server URL (default: http://localhost:3001)",
|
|
6127
|
+
default: `http://localhost:${DEFAULT_SYNC_PORT3}`
|
|
6128
|
+
},
|
|
6129
|
+
watch: {
|
|
6130
|
+
type: "boolean",
|
|
6131
|
+
description: "Live-updating status (like htop)",
|
|
6132
|
+
default: false,
|
|
6133
|
+
alias: "w"
|
|
6134
|
+
},
|
|
6135
|
+
token: {
|
|
6136
|
+
type: "string",
|
|
6137
|
+
description: "Admin token (defaults to KORA_ADMIN_TOKEN)"
|
|
6138
|
+
}
|
|
6139
|
+
},
|
|
6140
|
+
async run({ args }) {
|
|
6141
|
+
const logger = createLogger();
|
|
6142
|
+
const url = typeof args.url === "string" ? args.url : `http://localhost:${DEFAULT_SYNC_PORT3}`;
|
|
6143
|
+
const watch2 = args.watch === true;
|
|
6144
|
+
const token = typeof args.token === "string" ? args.token : process.env.KORA_ADMIN_TOKEN ?? void 0;
|
|
6145
|
+
try {
|
|
6146
|
+
if (watch2) {
|
|
6147
|
+
console.clear();
|
|
6148
|
+
logger.banner();
|
|
6149
|
+
logger.info(`Connecting to ${url}...`);
|
|
6150
|
+
const interval = setInterval(async () => {
|
|
6151
|
+
try {
|
|
6152
|
+
const status = await fetchServerStatus(url, token);
|
|
6153
|
+
printStatus(status, url, logger);
|
|
6154
|
+
} catch {
|
|
6155
|
+
}
|
|
6156
|
+
}, 2e3);
|
|
6157
|
+
try {
|
|
6158
|
+
const status = await fetchServerStatus(url, token);
|
|
6159
|
+
printStatus(status, url, logger);
|
|
6160
|
+
} catch {
|
|
6161
|
+
}
|
|
6162
|
+
await new Promise(() => {
|
|
6163
|
+
process.on("SIGINT", () => {
|
|
6164
|
+
clearInterval(interval);
|
|
6165
|
+
process.exit(0);
|
|
6166
|
+
});
|
|
6167
|
+
process.on("SIGTERM", () => {
|
|
6168
|
+
clearInterval(interval);
|
|
6169
|
+
process.exit(0);
|
|
6170
|
+
});
|
|
6171
|
+
});
|
|
6172
|
+
} else {
|
|
6173
|
+
const status = await fetchServerStatus(url, token);
|
|
6174
|
+
printStatus(status, url, logger);
|
|
6175
|
+
}
|
|
6176
|
+
} catch (error) {
|
|
6177
|
+
logger.error(`Failed to connect to ${url}`);
|
|
6178
|
+
if (error instanceof Error) {
|
|
6179
|
+
logger.error(error.message);
|
|
6180
|
+
}
|
|
6181
|
+
logger.blank();
|
|
6182
|
+
logger.step("Make sure the Kora sync server is running.");
|
|
6183
|
+
logger.step("Start it with: kora dev");
|
|
6184
|
+
process.exit(1);
|
|
6185
|
+
}
|
|
6186
|
+
}
|
|
6187
|
+
});
|
|
6188
|
+
function printStatus(status, url, logger) {
|
|
6189
|
+
console.clear();
|
|
6190
|
+
logger.banner();
|
|
6191
|
+
logger.info(`Kora Sync Server \u2014 ${url}`);
|
|
6192
|
+
logger.blank();
|
|
6193
|
+
if (!status.running) {
|
|
6194
|
+
logger.error("Server is not running");
|
|
6195
|
+
return;
|
|
6196
|
+
}
|
|
6197
|
+
logger.step(`Status: Running (uptime: ${formatUptime(status.uptime)}) \u2014 v${status.version}`);
|
|
6198
|
+
logger.step(`Schema: v${status.schemaVersion}`);
|
|
6199
|
+
logger.blank();
|
|
6200
|
+
logger.step(`Connections: ${status.connectedClients} connected`);
|
|
6201
|
+
logger.step(` Peak: ${status.peakConnections}`);
|
|
6202
|
+
logger.step(` Total: ${status.connectionsTotal}`);
|
|
6203
|
+
if (status.connectedNodeIds.length > 0) {
|
|
6204
|
+
logger.step(" Active nodes:");
|
|
6205
|
+
for (const nodeId of status.connectedNodeIds) {
|
|
6206
|
+
logger.step(` \u2022 ${nodeId}`);
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
logger.blank();
|
|
6210
|
+
logger.step("Operations:");
|
|
6211
|
+
logger.step(` Received: ${status.operationsReceived.toLocaleString()}`);
|
|
6212
|
+
logger.step(` Sent: ${status.operationsSent.toLocaleString()}`);
|
|
6213
|
+
logger.step(` Total: ${status.totalOperations.toLocaleString()}`);
|
|
6214
|
+
logger.blank();
|
|
6215
|
+
if (status.errorCount > 0) {
|
|
6216
|
+
logger.warn(`Errors: ${status.errorCount}`);
|
|
6217
|
+
} else {
|
|
6218
|
+
logger.step("Errors: 0");
|
|
6219
|
+
}
|
|
6220
|
+
}
|
|
6221
|
+
|
|
5261
6222
|
// src/bin.ts
|
|
5262
|
-
var main = (0,
|
|
6223
|
+
var main = (0, import_citty11.defineCommand)({
|
|
5263
6224
|
meta: {
|
|
5264
6225
|
name: "kora",
|
|
5265
6226
|
description: "Kora.js \u2014 Offline-first application framework"
|
|
5266
6227
|
},
|
|
5267
6228
|
subCommands: {
|
|
6229
|
+
backup: backupCommand,
|
|
6230
|
+
compact: compactCommand,
|
|
5268
6231
|
create: createCommand,
|
|
5269
6232
|
dev: devCommand,
|
|
6233
|
+
doctor: doctorCommand,
|
|
5270
6234
|
deploy: deployCommand,
|
|
5271
6235
|
generate: generateCommand,
|
|
5272
|
-
|
|
6236
|
+
logs: logsCommand,
|
|
6237
|
+
migrate: migrateCommand,
|
|
6238
|
+
status: statusCommand
|
|
5273
6239
|
}
|
|
5274
6240
|
});
|
|
5275
|
-
(0,
|
|
6241
|
+
(0, import_citty11.runMain)(main);
|
|
5276
6242
|
//# sourceMappingURL=bin.cjs.map
|