@korajs/cli 0.1.15 → 0.2.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 CHANGED
@@ -23,12 +23,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/bin.ts
26
- var import_citty5 = require("citty");
26
+ var import_citty6 = require("citty");
27
27
 
28
28
  // src/commands/create/create-command.ts
29
29
  var import_node_child_process2 = require("child_process");
30
30
  var import_node_fs2 = require("fs");
31
- var import_node_path3 = require("path");
31
+ var import_node_path5 = require("path");
32
32
  var import_node_url2 = require("url");
33
33
  var import_citty = require("citty");
34
34
 
@@ -85,6 +85,209 @@ var DevServerError = class extends import_core.KoraError {
85
85
  searchPath;
86
86
  };
87
87
 
88
+ // src/prompts/preferences.ts
89
+ var import_conf = __toESM(require("conf"), 1);
90
+ var DEFAULT_PREFERENCES = {
91
+ framework: "react",
92
+ tailwind: true,
93
+ sync: true,
94
+ db: "sqlite",
95
+ dbProvider: "none",
96
+ auth: "none",
97
+ packageManager: "pnpm"
98
+ };
99
+ var PREFERENCES_KEY = "create.defaults";
100
+ var PreferenceStore = class {
101
+ store;
102
+ constructor() {
103
+ this.store = new import_conf.default({
104
+ projectName: "korajs-cli"
105
+ });
106
+ }
107
+ getCreatePreferences() {
108
+ return this.store.get(PREFERENCES_KEY) ?? null;
109
+ }
110
+ saveCreatePreferences(preferences) {
111
+ this.store.set(PREFERENCES_KEY, preferences);
112
+ }
113
+ clearCreatePreferences() {
114
+ this.store.delete(PREFERENCES_KEY);
115
+ }
116
+ };
117
+ function getCreatePreferencesOrDefault(store) {
118
+ return store.getCreatePreferences() ?? DEFAULT_PREFERENCES;
119
+ }
120
+ function getDefaultCreatePreferences() {
121
+ return { ...DEFAULT_PREFERENCES };
122
+ }
123
+
124
+ // src/prompts/prompt-client.ts
125
+ var import_prompts = require("@clack/prompts");
126
+
127
+ // src/utils/prompt.ts
128
+ var import_node_readline = require("readline");
129
+ function promptText(message, defaultValue, options) {
130
+ return new Promise((resolve9) => {
131
+ const rl = createReadline(options);
132
+ const suffix = defaultValue !== void 0 ? ` (${defaultValue})` : "";
133
+ rl.question(` ? ${message}${suffix}: `, (answer) => {
134
+ rl.close();
135
+ const trimmed = answer.trim();
136
+ resolve9(trimmed || defaultValue || "");
137
+ });
138
+ });
139
+ }
140
+ function promptSelect(message, choices, options) {
141
+ return new Promise((resolve9) => {
142
+ const rl = createReadline(options);
143
+ const out = options?.output ?? process.stdout;
144
+ out.write(` ? ${message}
145
+ `);
146
+ for (let i = 0; i < choices.length; i++) {
147
+ const choice = choices[i];
148
+ if (choice) {
149
+ out.write(` ${i + 1}) ${choice.label}
150
+ `);
151
+ }
152
+ }
153
+ const ask = () => {
154
+ rl.question(" > ", (answer) => {
155
+ const index = Number.parseInt(answer.trim(), 10) - 1;
156
+ const selected = choices[index];
157
+ if (selected) {
158
+ rl.close();
159
+ resolve9(selected.value);
160
+ } else {
161
+ out.write(` Please enter a number between 1 and ${choices.length}
162
+ `);
163
+ ask();
164
+ }
165
+ });
166
+ };
167
+ ask();
168
+ });
169
+ }
170
+ function promptConfirm(message, defaultValue = false, options) {
171
+ return new Promise((resolve9) => {
172
+ const rl = createReadline(options);
173
+ const suffix = defaultValue ? "Y/n" : "y/N";
174
+ const ask = () => {
175
+ rl.question(` ? ${message} (${suffix}): `, (answer) => {
176
+ const normalized = answer.trim().toLowerCase();
177
+ if (normalized.length === 0) {
178
+ rl.close();
179
+ resolve9(defaultValue);
180
+ return;
181
+ }
182
+ if (normalized === "y" || normalized === "yes") {
183
+ rl.close();
184
+ resolve9(true);
185
+ return;
186
+ }
187
+ if (normalized === "n" || normalized === "no") {
188
+ rl.close();
189
+ resolve9(false);
190
+ return;
191
+ }
192
+ (options?.output ?? process.stdout).write(" Please answer with y or n\n");
193
+ ask();
194
+ });
195
+ };
196
+ ask();
197
+ });
198
+ }
199
+ function createReadline(options) {
200
+ return (0, import_node_readline.createInterface)({
201
+ input: options?.input ?? process.stdin,
202
+ output: options?.output ?? process.stdout
203
+ });
204
+ }
205
+
206
+ // src/prompts/prompt-client.ts
207
+ var PromptCancelledError = class extends Error {
208
+ constructor(message = "Prompt cancelled by user") {
209
+ super(message);
210
+ this.name = "PromptCancelledError";
211
+ }
212
+ };
213
+ var ReadlinePromptClient = class {
214
+ async text(message, defaultValue) {
215
+ return promptText(message, defaultValue);
216
+ }
217
+ async select(message, options) {
218
+ return promptSelect(
219
+ message,
220
+ options.filter((option) => option.disabled !== true).map((option) => ({ label: option.label, value: option.value }))
221
+ );
222
+ }
223
+ async confirm(message, defaultValue = false) {
224
+ return promptConfirm(message, defaultValue);
225
+ }
226
+ intro(message) {
227
+ void message;
228
+ }
229
+ outro(message) {
230
+ void message;
231
+ }
232
+ };
233
+ function createPromptClient() {
234
+ const canUseInteractiveClack = typeof process !== "undefined" && process.stdin.isTTY && process.stdout.isTTY;
235
+ if (canUseInteractiveClack) {
236
+ return new ClackPromptClient();
237
+ }
238
+ return new ReadlinePromptClient();
239
+ }
240
+ var ClackPromptClient = class {
241
+ async text(message, defaultValue) {
242
+ const result = await (0, import_prompts.text)({
243
+ message,
244
+ placeholder: defaultValue,
245
+ defaultValue
246
+ });
247
+ if ((0, import_prompts.isCancel)(result)) {
248
+ (0, import_prompts.cancel)("Operation cancelled.");
249
+ throw new PromptCancelledError();
250
+ }
251
+ const value = result.trim();
252
+ if (value.length > 0) return value;
253
+ return defaultValue ?? "";
254
+ }
255
+ async select(message, options) {
256
+ const mappedOptions = options.map((option) => ({
257
+ label: option.label,
258
+ value: option.value,
259
+ hint: option.hint,
260
+ disabled: option.disabled
261
+ }));
262
+ const result = await (0, import_prompts.select)({
263
+ message,
264
+ options: mappedOptions
265
+ });
266
+ if ((0, import_prompts.isCancel)(result)) {
267
+ (0, import_prompts.cancel)("Operation cancelled.");
268
+ throw new PromptCancelledError();
269
+ }
270
+ return result;
271
+ }
272
+ async confirm(message, defaultValue = false) {
273
+ const result = await (0, import_prompts.confirm)({
274
+ message,
275
+ initialValue: defaultValue
276
+ });
277
+ if ((0, import_prompts.isCancel)(result)) {
278
+ (0, import_prompts.cancel)("Operation cancelled.");
279
+ throw new PromptCancelledError();
280
+ }
281
+ return result;
282
+ }
283
+ intro(message) {
284
+ (0, import_prompts.intro)(message);
285
+ }
286
+ outro(message) {
287
+ (0, import_prompts.outro)(message);
288
+ }
289
+ };
290
+
88
291
  // src/types.ts
89
292
  var PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
90
293
  var TEMPLATES = [
@@ -93,28 +296,6 @@ var TEMPLATES = [
93
296
  "react-sync",
94
297
  "react-basic"
95
298
  ];
96
- var TEMPLATE_INFO = [
97
- {
98
- name: "react-tailwind-sync",
99
- label: "React + Tailwind (with sync)",
100
- description: "Polished dark-themed app with Tailwind CSS and sync server (Recommended)"
101
- },
102
- {
103
- name: "react-tailwind",
104
- label: "React + Tailwind (local-only)",
105
- description: "Polished dark-themed app with Tailwind CSS \u2014 no sync server"
106
- },
107
- {
108
- name: "react-sync",
109
- label: "React + CSS (with sync)",
110
- description: "Clean CSS app with sync server included"
111
- },
112
- {
113
- name: "react-basic",
114
- label: "React + CSS (local-only)",
115
- description: "Clean CSS app \u2014 no sync server"
116
- }
117
- ];
118
299
 
119
300
  // src/utils/fs-helpers.ts
120
301
  var import_promises = require("fs/promises");
@@ -258,288 +439,2291 @@ function getRunDevCommand(pm) {
258
439
  return `${pm} dev`;
259
440
  }
260
441
 
261
- // src/utils/prompt.ts
262
- var import_node_readline = require("readline");
263
- function promptText(message, defaultValue, options) {
264
- return new Promise((resolve7) => {
265
- const rl = createReadline(options);
266
- const suffix = defaultValue !== void 0 ? ` (${defaultValue})` : "";
267
- rl.question(` ? ${message}${suffix}: `, (answer) => {
268
- rl.close();
269
- const trimmed = answer.trim();
270
- resolve7(trimmed || defaultValue || "");
271
- });
272
- });
442
+ // src/commands/create/environment-detection.ts
443
+ var import_promises2 = require("fs/promises");
444
+ var import_node_path2 = require("path");
445
+ function detectEditorFromEnvironment(env = process.env) {
446
+ const termProgram = String(env.TERM_PROGRAM ?? "").toLowerCase();
447
+ const editorValue = `${String(env.EDITOR ?? "")} ${String(env.VISUAL ?? "")}`.toLowerCase();
448
+ if (termProgram.includes("cursor") || env.CURSOR_TRACE_ID !== void 0 || env.CURSOR_SESSION_ID !== void 0 || editorValue.includes("cursor")) {
449
+ return { editor: "cursor", source: "env" };
450
+ }
451
+ if (termProgram.includes("windsurf") || env.WINDSURF_SESSION_ID !== void 0 || editorValue.includes("windsurf")) {
452
+ return { editor: "windsurf", source: "env" };
453
+ }
454
+ if (termProgram.includes("vscode") || env.VSCODE_GIT_IPC_HANDLE !== void 0 || env.VSCODE_IPC_HOOK !== void 0 || env.VSCODE_PID !== void 0 || editorValue.includes("code")) {
455
+ return { editor: "vscode", source: "env" };
456
+ }
457
+ if (termProgram.includes("zed") || env.ZED_TERM !== void 0 || editorValue.includes("zed")) {
458
+ return { editor: "zed", source: "env" };
459
+ }
460
+ return { editor: "unknown", source: "none" };
273
461
  }
274
- function promptSelect(message, choices, options) {
275
- return new Promise((resolve7) => {
276
- const rl = createReadline(options);
277
- const out = options?.output ?? process.stdout;
278
- out.write(` ? ${message}
279
- `);
280
- for (let i = 0; i < choices.length; i++) {
281
- const choice = choices[i];
282
- if (choice) {
283
- out.write(` ${i + 1}) ${choice.label}
284
- `);
285
- }
462
+ async function detectGitContext(startDir) {
463
+ const root = await findNearestAncestorWithEntry(startDir, ".git");
464
+ return {
465
+ hasRepository: root !== null,
466
+ repositoryRoot: root
467
+ };
468
+ }
469
+ async function detectMonorepoContext(startDir) {
470
+ let current = (0, import_node_path2.resolve)(startDir);
471
+ for (; ; ) {
472
+ if (await fileExists((0, import_node_path2.join)(current, "pnpm-workspace.yaml"))) {
473
+ return { isMonorepo: true, root: current, kind: "pnpm-workspace" };
286
474
  }
287
- const ask = () => {
288
- rl.question(" > ", (answer) => {
289
- const index = Number.parseInt(answer.trim(), 10) - 1;
290
- const selected = choices[index];
291
- if (selected) {
292
- rl.close();
293
- resolve7(selected.value);
294
- } else {
295
- out.write(` Please enter a number between 1 and ${choices.length}
296
- `);
297
- ask();
475
+ if (await fileExists((0, import_node_path2.join)(current, "turbo.json"))) {
476
+ return { isMonorepo: true, root: current, kind: "turborepo" };
477
+ }
478
+ const packageJsonPath = (0, import_node_path2.join)(current, "package.json");
479
+ if (await fileExists(packageJsonPath)) {
480
+ try {
481
+ const packageJsonRaw = await (0, import_promises2.readFile)(packageJsonPath, "utf-8");
482
+ const parsed = JSON.parse(packageJsonRaw);
483
+ if (Array.isArray(parsed.workspaces) || isNpmWorkspaceObject(parsed.workspaces)) {
484
+ return { isMonorepo: true, root: current, kind: "npm-workspaces" };
298
485
  }
299
- });
300
- };
301
- ask();
486
+ } catch {
487
+ }
488
+ }
489
+ const parent = (0, import_node_path2.dirname)(current);
490
+ if (parent === current) {
491
+ return { isMonorepo: false, root: null, kind: "none" };
492
+ }
493
+ current = parent;
494
+ }
495
+ }
496
+ async function applyEditorWorkspacePreset(params) {
497
+ const { targetDir, editor } = params;
498
+ if (editor !== "vscode" && editor !== "cursor" && editor !== "windsurf") {
499
+ return { applied: false, filePath: null };
500
+ }
501
+ const vscodeDir = (0, import_node_path2.join)(targetDir, ".vscode");
502
+ const extensionsPath = (0, import_node_path2.join)(vscodeDir, "extensions.json");
503
+ await (0, import_promises2.mkdir)(vscodeDir, { recursive: true });
504
+ const recommendations = ["korajs.kora-devtools"];
505
+ const existing = await readJsonObject(extensionsPath);
506
+ const existingRecommendations = Array.isArray(existing?.recommendations) ? existing.recommendations.filter((item) => typeof item === "string") : [];
507
+ const mergedRecommendations = dedupeStrings([...existingRecommendations, ...recommendations]);
508
+ const next = {
509
+ recommendations: mergedRecommendations
510
+ };
511
+ await (0, import_promises2.writeFile)(extensionsPath, `${JSON.stringify(next, null, 2)}
512
+ `, "utf-8");
513
+ return { applied: true, filePath: extensionsPath };
514
+ }
515
+ function resolveMonorepoTargetDirectory(monorepoRoot, projectName) {
516
+ return (0, import_node_path2.join)(monorepoRoot, "packages", projectName);
517
+ }
518
+ function dedupeStrings(values) {
519
+ return Array.from(new Set(values));
520
+ }
521
+ async function readJsonObject(path) {
522
+ try {
523
+ const content = await (0, import_promises2.readFile)(path, "utf-8");
524
+ const parsed = JSON.parse(content);
525
+ if (typeof parsed === "object" && parsed !== null) {
526
+ return parsed;
527
+ }
528
+ return null;
529
+ } catch {
530
+ return null;
531
+ }
532
+ }
533
+ function isNpmWorkspaceObject(value) {
534
+ if (typeof value !== "object" || value === null) return false;
535
+ const record = value;
536
+ return Array.isArray(record.packages);
537
+ }
538
+ async function fileExists(path) {
539
+ try {
540
+ await (0, import_promises2.access)(path);
541
+ return true;
542
+ } catch {
543
+ return false;
544
+ }
545
+ }
546
+ async function findNearestAncestorWithEntry(startDir, entryName) {
547
+ let current = (0, import_node_path2.resolve)(startDir);
548
+ for (; ; ) {
549
+ const candidate = (0, import_node_path2.join)(current, entryName);
550
+ try {
551
+ await (0, import_promises2.stat)(candidate);
552
+ return current;
553
+ } catch {
554
+ }
555
+ const parent = (0, import_node_path2.dirname)(current);
556
+ if (parent === current) {
557
+ return null;
558
+ }
559
+ current = parent;
560
+ }
561
+ }
562
+
563
+ // src/commands/create/options.ts
564
+ function determineTemplateFromSelections(input) {
565
+ const shouldSync = input.sync && input.db !== "none";
566
+ if (input.tailwind && shouldSync) return "react-tailwind-sync";
567
+ if (input.tailwind && !shouldSync) return "react-tailwind";
568
+ if (!input.tailwind && shouldSync) return "react-sync";
569
+ return "react-basic";
570
+ }
571
+ function isFrameworkValue(value) {
572
+ return value === "react" || value === "vue" || value === "svelte" || value === "solid";
573
+ }
574
+ function isAuthValue(value) {
575
+ return value === "none" || value === "email-password" || value === "oauth";
576
+ }
577
+ function isDatabaseValue(value) {
578
+ return value === "none" || value === "sqlite" || value === "postgres";
579
+ }
580
+ function isDatabaseProviderValue(value) {
581
+ return value === "none" || value === "local" || value === "supabase" || value === "neon" || value === "railway" || value === "vercel-postgres" || value === "custom";
582
+ }
583
+
584
+ // src/commands/create/preferences-flow.ts
585
+ async function resolveCreatePreferencesFlow(params) {
586
+ const { flags, prompts, store } = params;
587
+ const stored = store.getCreatePreferences();
588
+ const base = getCreatePreferencesOrDefault(store);
589
+ const hasExplicitFlags = hasExplicitPreferenceFlags(flags);
590
+ const canOfferStored = !flags.useDefaults && !hasExplicitFlags && stored !== null && promptSupportsRichOptions();
591
+ let effective = { ...base };
592
+ let usedStoredPreferences = false;
593
+ if (flags.useDefaults) {
594
+ effective = getDefaultCreatePreferences();
595
+ } else if (canOfferStored && stored !== null) {
596
+ const reuseStored = await prompts.select("Welcome back! Choose setup mode:", [
597
+ { label: formatStoredPreferenceLabel(stored), value: "reuse" },
598
+ { label: "Customize", value: "customize" }
599
+ ]);
600
+ if (reuseStored === "reuse") {
601
+ effective = { ...stored };
602
+ usedStoredPreferences = true;
603
+ }
604
+ }
605
+ if (flags.framework !== void 0) {
606
+ if (!isFrameworkValue(flags.framework)) {
607
+ throw new Error(
608
+ `Invalid --framework value "${flags.framework}". Expected one of: react, vue, svelte, solid.`
609
+ );
610
+ }
611
+ effective.framework = flags.framework;
612
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
613
+ effective.framework = await prompts.select("UI framework:", [
614
+ { label: "React", value: "react" },
615
+ { label: "Vue (coming soon)", value: "vue", disabled: true },
616
+ { label: "Svelte (coming soon)", value: "svelte", disabled: true },
617
+ { label: "Solid (coming soon)", value: "solid", disabled: true }
618
+ ]);
619
+ }
620
+ if (flags.auth !== void 0) {
621
+ if (!isAuthValue(flags.auth)) {
622
+ throw new Error(
623
+ `Invalid --auth value "${flags.auth}". Expected one of: none, email-password, oauth.`
624
+ );
625
+ }
626
+ effective.auth = flags.auth;
627
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
628
+ effective.auth = await prompts.select("Authentication:", [
629
+ { label: "None", value: "none" },
630
+ { label: "Email + Password (coming soon)", value: "email-password", disabled: true },
631
+ { label: "OAuth (coming soon)", value: "oauth", disabled: true }
632
+ ]);
633
+ }
634
+ if (flags.tailwind !== void 0) {
635
+ effective.tailwind = flags.tailwind;
636
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
637
+ effective.tailwind = await prompts.confirm("Use Tailwind CSS?", true);
638
+ }
639
+ if (flags.sync !== void 0) {
640
+ effective.sync = flags.sync;
641
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
642
+ effective.sync = await prompts.confirm("Enable multi-device sync?", true);
643
+ }
644
+ if (flags.db !== void 0) {
645
+ if (!isDatabaseValue(flags.db)) {
646
+ throw new Error(`Invalid --db value "${flags.db}". Expected one of: none, sqlite, postgres.`);
647
+ }
648
+ effective.db = flags.db;
649
+ } else if (!effective.sync) {
650
+ effective.db = "none";
651
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
652
+ effective.db = await prompts.select("Server-side database:", [
653
+ { label: "SQLite (zero-config)", value: "sqlite" },
654
+ { label: "PostgreSQL (production-scale)", value: "postgres" }
655
+ ]);
656
+ }
657
+ if (effective.db !== "postgres") {
658
+ effective.dbProvider = "none";
659
+ } else if (flags.dbProvider !== void 0) {
660
+ if (!isDatabaseProviderValue(flags.dbProvider)) {
661
+ throw new Error(
662
+ `Invalid --db-provider value "${flags.dbProvider}". Expected one of: none, local, supabase, neon, railway, vercel-postgres, custom.`
663
+ );
664
+ }
665
+ effective.dbProvider = flags.dbProvider;
666
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
667
+ effective.dbProvider = await prompts.select("Database provider:", [
668
+ { label: "Local Postgres", value: "local" },
669
+ { label: "Supabase", value: "supabase" },
670
+ { label: "Neon", value: "neon" },
671
+ { label: "Railway", value: "railway" },
672
+ { label: "Vercel Postgres", value: "vercel-postgres" },
673
+ { label: "Custom connection string", value: "custom" }
674
+ ]);
675
+ }
676
+ const template = determineTemplateFromSelections({
677
+ tailwind: effective.tailwind,
678
+ sync: effective.sync,
679
+ db: effective.db
302
680
  });
681
+ return {
682
+ framework: effective.framework,
683
+ auth: effective.auth,
684
+ db: effective.db,
685
+ dbProvider: effective.dbProvider,
686
+ tailwind: effective.tailwind,
687
+ sync: effective.sync,
688
+ template,
689
+ usedStoredPreferences
690
+ };
303
691
  }
304
- function promptConfirm(message, defaultValue = false, options) {
305
- return new Promise((resolve7) => {
306
- const rl = createReadline(options);
307
- const suffix = defaultValue ? "Y/n" : "y/N";
308
- const ask = () => {
309
- rl.question(` ? ${message} (${suffix}): `, (answer) => {
310
- const normalized = answer.trim().toLowerCase();
311
- if (normalized.length === 0) {
312
- rl.close();
313
- resolve7(defaultValue);
314
- return;
315
- }
316
- if (normalized === "y" || normalized === "yes") {
317
- rl.close();
318
- resolve7(true);
319
- return;
320
- }
321
- if (normalized === "n" || normalized === "no") {
322
- rl.close();
323
- resolve7(false);
324
- return;
325
- }
326
- (options?.output ?? process.stdout).write(" Please answer with y or n\n");
327
- ask();
328
- });
692
+ function shouldSavePreferences(flags) {
693
+ return !flags.useDefaults;
694
+ }
695
+ function saveResolvedPreferences(store, resolution) {
696
+ store.saveCreatePreferences({
697
+ framework: resolution.framework,
698
+ tailwind: resolution.tailwind,
699
+ sync: resolution.sync,
700
+ db: resolution.db,
701
+ dbProvider: resolution.dbProvider,
702
+ auth: resolution.auth,
703
+ packageManager: resolution.packageManager
704
+ });
705
+ }
706
+ function hasExplicitPreferenceFlags(flags) {
707
+ return flags.framework !== void 0 || flags.auth !== void 0 || flags.db !== void 0 || flags.dbProvider !== void 0 || flags.tailwind !== void 0 || flags.sync !== void 0;
708
+ }
709
+ function promptSupportsRichOptions() {
710
+ return typeof process !== "undefined" && process.stdin.isTTY && process.stdout.isTTY;
711
+ }
712
+ function formatStoredPreferenceLabel(preferences) {
713
+ const syncLabel = preferences.sync ? `sync/${preferences.db}` : "local-only";
714
+ const styleLabel = preferences.tailwind ? "tailwind" : "css";
715
+ return `Use previous settings (${preferences.framework} + ${styleLabel} + ${syncLabel} + ${preferences.packageManager})`;
716
+ }
717
+
718
+ // src/commands/create/project-name.ts
719
+ var import_validate_npm_package_name = __toESM(require("validate-npm-package-name"), 1);
720
+ function validateProjectName(name) {
721
+ const trimmedName = name.trim();
722
+ if (trimmedName.length === 0) {
723
+ return {
724
+ valid: false,
725
+ issues: ["Project name cannot be empty."]
726
+ };
727
+ }
728
+ const validation = (0, import_validate_npm_package_name.default)(trimmedName);
729
+ const issues = [...validation.errors ?? [], ...validation.warnings ?? []];
730
+ if (!validation.validForNewPackages && issues.length === 0) {
731
+ return {
732
+ valid: false,
733
+ issues: ["Project name is not a valid npm package name."]
734
+ };
735
+ }
736
+ return {
737
+ valid: validation.validForNewPackages,
738
+ issues
739
+ };
740
+ }
741
+
742
+ // src/commands/create/sync-provider-preset.ts
743
+ var import_promises3 = require("fs/promises");
744
+ var import_node_path3 = require("path");
745
+ async function applySyncProviderPreset(options) {
746
+ if (!isSyncTemplate(options.template)) {
747
+ return;
748
+ }
749
+ if (options.db !== "postgres") {
750
+ return;
751
+ }
752
+ const providerName = getProviderDisplayName(options.dbProvider);
753
+ const providerConnectionString = getProviderConnectionStringExample(options.dbProvider);
754
+ const serverPath = (0, import_node_path3.join)(options.targetDir, "server.ts");
755
+ const envPath = (0, import_node_path3.join)(options.targetDir, ".env.example");
756
+ const readmePath = (0, import_node_path3.join)(options.targetDir, "README.md");
757
+ const serverTemplate = [
758
+ "import { createPostgresServerStore, createProductionServer } from '@korajs/server'",
759
+ "",
760
+ `// PostgreSQL provider preset: ${providerName}`,
761
+ "// Ensure DATABASE_URL is set in your environment.",
762
+ "",
763
+ "async function start(): Promise<void> {",
764
+ " const connectionString = process.env.DATABASE_URL || ''",
765
+ " if (connectionString.length === 0) {",
766
+ " throw new Error('DATABASE_URL is required for PostgreSQL sync server store.')",
767
+ " }",
768
+ "",
769
+ " const store = await createPostgresServerStore({ connectionString })",
770
+ " const server = createProductionServer({",
771
+ " store,",
772
+ " port: Number(process.env.PORT) || 3001,",
773
+ " staticDir: './dist',",
774
+ " syncPath: '/kora-sync',",
775
+ " })",
776
+ "",
777
+ " const url = await server.start()",
778
+ " console.log(`Kora app running at ${url}`)",
779
+ "}",
780
+ "",
781
+ "void start()",
782
+ ""
783
+ ].join("\n");
784
+ const envTemplate = [
785
+ "# Kora Sync Server",
786
+ "# WebSocket URL for the sync server (used by the client)",
787
+ "VITE_SYNC_URL=ws://localhost:3001",
788
+ "",
789
+ "# Sync server port",
790
+ "PORT=3001",
791
+ "",
792
+ `# PostgreSQL connection string (${providerName})`,
793
+ `# Example: ${providerConnectionString}`,
794
+ "DATABASE_URL=",
795
+ ""
796
+ ].join("\n");
797
+ const existingReadme = await (0, import_promises3.readFile)(readmePath, "utf-8");
798
+ const trimmedReadme = existingReadme.trimEnd();
799
+ const readmeSuffix = [
800
+ "",
801
+ "## PostgreSQL Provider Preset",
802
+ "",
803
+ `Selected DB provider: ${options.dbProvider}`,
804
+ "",
805
+ "This scaffold uses `createPostgresServerStore` in `server.ts` and reads `DATABASE_URL` from the environment. See `.env.example` for provider-specific examples.",
806
+ ""
807
+ ].join("\n");
808
+ const readmeTemplate = `${trimmedReadme}${readmeSuffix}`;
809
+ await (0, import_promises3.writeFile)(serverPath, serverTemplate, "utf-8");
810
+ await (0, import_promises3.writeFile)(envPath, envTemplate, "utf-8");
811
+ await (0, import_promises3.writeFile)(readmePath, readmeTemplate, "utf-8");
812
+ }
813
+ function isSyncTemplate(template) {
814
+ return template === "react-sync" || template === "react-tailwind-sync";
815
+ }
816
+ function getProviderDisplayName(provider) {
817
+ switch (provider) {
818
+ case "supabase":
819
+ return "Supabase";
820
+ case "neon":
821
+ return "Neon";
822
+ case "railway":
823
+ return "Railway";
824
+ case "vercel-postgres":
825
+ return "Vercel Postgres";
826
+ case "custom":
827
+ return "Custom";
828
+ case "local":
829
+ return "Local Postgres";
830
+ case "none":
831
+ return "PostgreSQL";
832
+ }
833
+ }
834
+ function getProviderConnectionStringExample(provider) {
835
+ switch (provider) {
836
+ case "supabase":
837
+ return "postgresql://postgres:<password>@db.<project-ref>.supabase.co:5432/postgres?sslmode=require";
838
+ case "neon":
839
+ return "postgresql://<user>:<password>@<branch>.<project>.neon.tech/neondb?sslmode=require";
840
+ case "railway":
841
+ return "postgresql://postgres:<password>@<host>.railway.app:<port>/railway?sslmode=require";
842
+ case "vercel-postgres":
843
+ return "postgresql://<user>:<password>@<host>.pooler.vercel-storage.com:5432/verceldb?sslmode=require";
844
+ case "custom":
845
+ return "postgresql://<user>:<password>@<host>:5432/<database>";
846
+ case "local":
847
+ return "postgresql://postgres:postgres@localhost:5432/kora";
848
+ case "none":
849
+ return "postgresql://postgres:postgres@localhost:5432/kora";
850
+ }
851
+ }
852
+
853
+ // src/templates/composer.ts
854
+ var import_node_fs = require("fs");
855
+ var import_promises4 = require("fs/promises");
856
+ var import_node_path4 = require("path");
857
+ var import_node_url = require("url");
858
+ var import_meta = {};
859
+ function substituteVariables(template, context) {
860
+ return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
861
+ const value = context[key];
862
+ return value !== void 0 ? value : `{{${key}}}`;
863
+ });
864
+ }
865
+ function getTemplatePath(templateName) {
866
+ let dir = (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
867
+ for (let i = 0; i < 7; i++) {
868
+ const candidate = (0, import_node_path4.resolve)(dir, "templates", templateName);
869
+ if ((0, import_node_fs.existsSync)(candidate)) {
870
+ return candidate;
871
+ }
872
+ dir = (0, import_node_path4.dirname)(dir);
873
+ }
874
+ const currentDir = (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
875
+ return (0, import_node_path4.resolve)(currentDir, "..", "..", "..", "templates", templateName);
876
+ }
877
+ function createCompatibilityLayerPlan(templateName) {
878
+ const baseLayer = { category: "base", name: "base", sourceTemplate: "react-basic" };
879
+ const uiLayer = { category: "ui", name: "react", sourceTemplate: null };
880
+ const authLayer = { category: "auth", name: "none", sourceTemplate: null };
881
+ switch (templateName) {
882
+ case "react-basic":
883
+ return {
884
+ compatibilityTarget: templateName,
885
+ layers: [
886
+ baseLayer,
887
+ uiLayer,
888
+ { category: "style", name: "plain", sourceTemplate: null },
889
+ { category: "sync", name: "disabled", sourceTemplate: null },
890
+ { category: "db", name: "none", sourceTemplate: null },
891
+ authLayer
892
+ ]
893
+ };
894
+ case "react-tailwind":
895
+ return {
896
+ compatibilityTarget: templateName,
897
+ layers: [
898
+ baseLayer,
899
+ uiLayer,
900
+ { category: "style", name: "tailwind", sourceTemplate: "react-tailwind" },
901
+ { category: "sync", name: "disabled", sourceTemplate: null },
902
+ { category: "db", name: "none", sourceTemplate: null },
903
+ authLayer
904
+ ]
905
+ };
906
+ case "react-sync":
907
+ return {
908
+ compatibilityTarget: templateName,
909
+ layers: [
910
+ baseLayer,
911
+ uiLayer,
912
+ { category: "style", name: "plain", sourceTemplate: null },
913
+ { category: "sync", name: "enabled", sourceTemplate: "react-sync" },
914
+ { category: "db", name: "sqlite", sourceTemplate: null },
915
+ authLayer
916
+ ]
917
+ };
918
+ case "react-tailwind-sync":
919
+ return {
920
+ compatibilityTarget: templateName,
921
+ layers: [
922
+ baseLayer,
923
+ uiLayer,
924
+ { category: "style", name: "tailwind", sourceTemplate: "react-tailwind" },
925
+ { category: "sync", name: "enabled", sourceTemplate: "react-tailwind-sync" },
926
+ { category: "db", name: "sqlite", sourceTemplate: null },
927
+ authLayer
928
+ ]
929
+ };
930
+ }
931
+ }
932
+ async function composeTemplateLayers(plan, targetDir, context) {
933
+ const vars = {
934
+ projectName: context.projectName,
935
+ packageManager: context.packageManager,
936
+ koraVersion: context.koraVersion,
937
+ dbProvider: context.dbProvider ?? "none"
938
+ };
939
+ for (const layer of plan.layers) {
940
+ if (layer.sourceTemplate === null) {
941
+ continue;
942
+ }
943
+ const sourceDir = getTemplatePath(layer.sourceTemplate);
944
+ await copyDirectory(sourceDir, targetDir, vars);
945
+ }
946
+ }
947
+ async function copyDirectory(src, dest, vars) {
948
+ await (0, import_promises4.mkdir)(dest, { recursive: true });
949
+ const entries = await (0, import_promises4.readdir)(src);
950
+ for (const entry of entries) {
951
+ const srcPath = (0, import_node_path4.join)(src, entry);
952
+ const srcStat = await (0, import_promises4.stat)(srcPath);
953
+ if (srcStat.isDirectory()) {
954
+ await copyDirectory(srcPath, (0, import_node_path4.join)(dest, entry), vars);
955
+ continue;
956
+ }
957
+ if (entry.endsWith(".hbs")) {
958
+ const content = await (0, import_promises4.readFile)(srcPath, "utf-8");
959
+ const outputName = entry.slice(0, -4);
960
+ await (0, import_promises4.writeFile)((0, import_node_path4.join)(dest, outputName), substituteVariables(content, vars), "utf-8");
961
+ continue;
962
+ }
963
+ await (0, import_promises4.copyFile)(srcPath, (0, import_node_path4.join)(dest, entry));
964
+ }
965
+ }
966
+
967
+ // src/commands/create/template-engine.ts
968
+ async function scaffoldTemplate(templateName, targetDir, context) {
969
+ const plan = createCompatibilityLayerPlan(templateName);
970
+ await composeTemplateLayers(plan, targetDir, context);
971
+ }
972
+
973
+ // src/commands/create/create-command.ts
974
+ var import_meta2 = {};
975
+ var createCommand = (0, import_citty.defineCommand)({
976
+ meta: {
977
+ name: "create",
978
+ description: "Create a new Kora application"
979
+ },
980
+ args: {
981
+ name: {
982
+ type: "positional",
983
+ description: "Project directory name",
984
+ required: false
985
+ },
986
+ template: {
987
+ type: "string",
988
+ description: "Project template (react-tailwind-sync, react-tailwind, react-sync, react-basic)"
989
+ },
990
+ pm: {
991
+ type: "string",
992
+ description: "Package manager (pnpm, npm, yarn, bun)"
993
+ },
994
+ framework: {
995
+ type: "string",
996
+ description: "UI framework (react, vue, svelte, solid)"
997
+ },
998
+ db: {
999
+ type: "string",
1000
+ description: "Database backend for sync templates (sqlite, postgres)"
1001
+ },
1002
+ "db-provider": {
1003
+ type: "string",
1004
+ description: "Database provider for postgres (local, supabase, neon, railway, vercel-postgres, custom)"
1005
+ },
1006
+ auth: {
1007
+ type: "string",
1008
+ description: "Authentication mode (none, email-password, oauth)"
1009
+ },
1010
+ "skip-install": {
1011
+ type: "boolean",
1012
+ description: "Skip installing dependencies",
1013
+ default: false
1014
+ },
1015
+ yes: {
1016
+ type: "boolean",
1017
+ alias: "y",
1018
+ description: "Accept all defaults (react-tailwind-sync + detected package manager)",
1019
+ default: false
1020
+ },
1021
+ tailwind: {
1022
+ type: "boolean",
1023
+ description: "Use Tailwind CSS (use --no-tailwind to skip)"
1024
+ },
1025
+ sync: {
1026
+ type: "boolean",
1027
+ description: "Include sync server (use --no-sync to skip)"
1028
+ }
1029
+ },
1030
+ async run({ args }) {
1031
+ const logger = createLogger();
1032
+ const prompts = createPromptClient();
1033
+ const preferenceStore = new PreferenceStore();
1034
+ logger.banner();
1035
+ try {
1036
+ const useDefaults = args.yes === true;
1037
+ if (!useDefaults) {
1038
+ prompts.intro("Kora.js \u2014 Offline-first application framework");
1039
+ }
1040
+ const projectName = args.name || (useDefaults ? "my-kora-app" : await prompts.text("Project name", "my-kora-app"));
1041
+ if (!projectName) {
1042
+ logger.error("Project name is required");
1043
+ process.exitCode = 1;
1044
+ return;
1045
+ }
1046
+ const nameValidation = validateProjectName(projectName);
1047
+ if (!nameValidation.valid) {
1048
+ logger.error("Invalid project name.");
1049
+ for (const issue of nameValidation.issues) {
1050
+ logger.step(`- ${issue}`);
1051
+ }
1052
+ if (!useDefaults) {
1053
+ prompts.outro("Project creation aborted.");
1054
+ }
1055
+ process.exitCode = 1;
1056
+ return;
1057
+ }
1058
+ const preferenceFlags = {
1059
+ framework: typeof args.framework === "string" ? args.framework : void 0,
1060
+ auth: typeof args.auth === "string" ? args.auth : void 0,
1061
+ db: typeof args.db === "string" ? args.db : void 0,
1062
+ dbProvider: typeof args["db-provider"] === "string" ? args["db-provider"] : void 0,
1063
+ tailwind: args.tailwind,
1064
+ sync: args.sync,
1065
+ useDefaults
1066
+ };
1067
+ const selection = await resolveCreatePreferencesFlow({
1068
+ flags: preferenceFlags,
1069
+ prompts,
1070
+ store: preferenceStore
1071
+ });
1072
+ if (selection.framework !== "react") {
1073
+ logger.error(`Framework "${selection.framework}" is not available yet. Use "react".`);
1074
+ if (!useDefaults) {
1075
+ prompts.outro("Project creation aborted.");
1076
+ }
1077
+ process.exitCode = 1;
1078
+ return;
1079
+ }
1080
+ if (selection.auth !== "none") {
1081
+ logger.error(`Auth mode "${selection.auth}" is not available yet. Use "none".`);
1082
+ if (!useDefaults) {
1083
+ prompts.outro("Project creation aborted.");
1084
+ }
1085
+ process.exitCode = 1;
1086
+ return;
1087
+ }
1088
+ const template = args.template && isValidTemplate(args.template) ? args.template : selection.template;
1089
+ let pm;
1090
+ if (args.pm && isValidPackageManager(args.pm)) {
1091
+ pm = args.pm;
1092
+ } else if (useDefaults) {
1093
+ pm = detectPackageManager();
1094
+ } else if (selection.usedStoredPreferences) {
1095
+ pm = preferenceStore.getCreatePreferences()?.packageManager ?? detectPackageManager();
1096
+ } else {
1097
+ const detected = detectPackageManager();
1098
+ pm = await prompts.select(
1099
+ "Package manager:",
1100
+ PACKAGE_MANAGERS.map((p) => ({
1101
+ label: p === detected ? `${p} (detected)` : p,
1102
+ value: p
1103
+ }))
1104
+ );
1105
+ }
1106
+ const editorDetection = detectEditorFromEnvironment();
1107
+ const gitContext = await detectGitContext(process.cwd());
1108
+ const monorepoContext = await detectMonorepoContext(process.cwd());
1109
+ let targetDir = (0, import_node_path5.resolve)(process.cwd(), projectName);
1110
+ if (!useDefaults && monorepoContext.isMonorepo && monorepoContext.root !== null) {
1111
+ const useWorkspaceTarget = await prompts.confirm(
1112
+ `Detected ${formatMonorepoKind(monorepoContext.kind)} at ${monorepoContext.root}. Create app under packages/${projectName}?`,
1113
+ true
1114
+ );
1115
+ if (useWorkspaceTarget) {
1116
+ targetDir = resolveMonorepoTargetDirectory(monorepoContext.root, projectName);
1117
+ }
1118
+ }
1119
+ if (await directoryExists(targetDir)) {
1120
+ throw new ProjectExistsError(projectName);
1121
+ }
1122
+ const koraVersion = resolveKoraVersion();
1123
+ logger.step(`Creating ${projectName} with ${template} template...`);
1124
+ await scaffoldTemplate(template, targetDir, {
1125
+ projectName,
1126
+ packageManager: pm,
1127
+ koraVersion,
1128
+ dbProvider: selection.dbProvider
1129
+ });
1130
+ await applySyncProviderPreset({
1131
+ targetDir,
1132
+ template,
1133
+ db: selection.db,
1134
+ dbProvider: selection.dbProvider
1135
+ });
1136
+ if (!useDefaults && editorDetection.editor !== "unknown") {
1137
+ const shouldApplyEditorPreset = await prompts.confirm(
1138
+ `Detected ${formatEditor(editorDetection.editor)}. Add workspace recommendations for ${formatEditor(editorDetection.editor)}?`,
1139
+ true
1140
+ );
1141
+ if (shouldApplyEditorPreset) {
1142
+ const appliedPreset = await applyEditorWorkspacePreset({
1143
+ targetDir,
1144
+ editor: editorDetection.editor
1145
+ });
1146
+ if (appliedPreset.applied) {
1147
+ logger.step(
1148
+ `Added editor recommendations at ${relativeToTarget(targetDir, appliedPreset.filePath)}`
1149
+ );
1150
+ }
1151
+ }
1152
+ }
1153
+ if (selection.db === "postgres" && isSyncTemplate2(template)) {
1154
+ logger.info(
1155
+ `Applied PostgreSQL sync preset (${formatDbProviderForLog(selection.dbProvider)}). Update DATABASE_URL in .env.example before running the sync server.`
1156
+ );
1157
+ }
1158
+ if (gitContext.hasRepository) {
1159
+ logger.step(
1160
+ `Detected existing git repository at ${gitContext.repositoryRoot}. Skipping git initialization.`
1161
+ );
1162
+ }
1163
+ logger.success("Project scaffolded");
1164
+ if (shouldSavePreferences(preferenceFlags)) {
1165
+ saveResolvedPreferences(preferenceStore, {
1166
+ framework: selection.framework,
1167
+ auth: selection.auth,
1168
+ db: selection.db,
1169
+ dbProvider: selection.dbProvider,
1170
+ tailwind: selection.tailwind,
1171
+ sync: selection.sync,
1172
+ packageManager: pm
1173
+ });
1174
+ }
1175
+ if (!args["skip-install"]) {
1176
+ logger.step("Installing dependencies...");
1177
+ try {
1178
+ (0, import_node_child_process2.execSync)(getInstallCommand(pm), { cwd: targetDir, stdio: "inherit" });
1179
+ logger.success("Dependencies installed");
1180
+ } catch {
1181
+ logger.warn("Failed to install dependencies. Run install manually.");
1182
+ }
1183
+ }
1184
+ logger.blank();
1185
+ logger.info("Done! Next steps:");
1186
+ logger.blank();
1187
+ logger.step(` cd ${targetDir}`);
1188
+ logger.step(` ${getRunDevCommand(pm)}`);
1189
+ logger.blank();
1190
+ if (!useDefaults) {
1191
+ prompts.outro("Project ready. Happy building with Kora!");
1192
+ }
1193
+ } catch (error) {
1194
+ if (error instanceof PromptCancelledError) {
1195
+ process.exitCode = 1;
1196
+ return;
1197
+ }
1198
+ if (error instanceof Error && error.message.startsWith("Invalid --")) {
1199
+ logger.error(error.message);
1200
+ if (!args.yes) {
1201
+ prompts.outro("Project creation aborted.");
1202
+ }
1203
+ process.exitCode = 1;
1204
+ return;
1205
+ }
1206
+ throw error;
1207
+ }
1208
+ }
1209
+ });
1210
+ function isValidTemplate(value) {
1211
+ return TEMPLATES.includes(value);
1212
+ }
1213
+ function isValidPackageManager(value) {
1214
+ return PACKAGE_MANAGERS.includes(value);
1215
+ }
1216
+ function isSyncTemplate2(template) {
1217
+ return template === "react-sync" || template === "react-tailwind-sync";
1218
+ }
1219
+ function formatDbProviderForLog(dbProvider) {
1220
+ switch (dbProvider) {
1221
+ case "supabase":
1222
+ return "Supabase";
1223
+ case "neon":
1224
+ return "Neon";
1225
+ case "railway":
1226
+ return "Railway";
1227
+ case "vercel-postgres":
1228
+ return "Vercel Postgres";
1229
+ case "custom":
1230
+ return "Custom";
1231
+ case "local":
1232
+ return "Local Postgres";
1233
+ case "none":
1234
+ return "PostgreSQL";
1235
+ default:
1236
+ return dbProvider;
1237
+ }
1238
+ }
1239
+ function formatEditor(editor) {
1240
+ switch (editor) {
1241
+ case "vscode":
1242
+ return "VS Code";
1243
+ case "cursor":
1244
+ return "Cursor";
1245
+ case "windsurf":
1246
+ return "Windsurf";
1247
+ case "zed":
1248
+ return "Zed";
1249
+ default:
1250
+ return editor;
1251
+ }
1252
+ }
1253
+ function formatMonorepoKind(kind) {
1254
+ switch (kind) {
1255
+ case "pnpm-workspace":
1256
+ return "pnpm workspace";
1257
+ case "npm-workspaces":
1258
+ return "npm workspace";
1259
+ case "turborepo":
1260
+ return "Turborepo";
1261
+ default:
1262
+ return "monorepo";
1263
+ }
1264
+ }
1265
+ function relativeToTarget(targetDir, filePath) {
1266
+ if (filePath === null) return ".";
1267
+ const normalizedTarget = targetDir.endsWith("/") ? targetDir : `${targetDir}/`;
1268
+ if (filePath.startsWith(normalizedTarget)) {
1269
+ return filePath.slice(normalizedTarget.length);
1270
+ }
1271
+ if (filePath === targetDir) {
1272
+ return ".";
1273
+ }
1274
+ return filePath;
1275
+ }
1276
+ function resolveKoraVersion() {
1277
+ try {
1278
+ let dir = (0, import_node_path5.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url));
1279
+ for (let i = 0; i < 5; i++) {
1280
+ const pkgPath = (0, import_node_path5.resolve)(dir, "package.json");
1281
+ if ((0, import_node_fs2.existsSync)(pkgPath)) {
1282
+ const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
1283
+ if (pkg.name === "@korajs/cli") {
1284
+ if (pkg.version === "0.0.0") return "latest";
1285
+ const parts = pkg.version.split(".");
1286
+ return `^${parts[0]}.${parts[1]}.0`;
1287
+ }
1288
+ }
1289
+ dir = (0, import_node_path5.dirname)(dir);
1290
+ }
1291
+ return "latest";
1292
+ } catch {
1293
+ return "latest";
1294
+ }
1295
+ }
1296
+
1297
+ // src/commands/deploy/deploy-command.ts
1298
+ var import_node_path14 = require("path");
1299
+ var import_citty2 = require("citty");
1300
+
1301
+ // src/commands/deploy/adapters/adapter.ts
1302
+ var DEPLOY_PLATFORMS = ["fly", "railway", "render", "docker", "kora-cloud"];
1303
+ function isDeployPlatform(value) {
1304
+ return DEPLOY_PLATFORMS.includes(value);
1305
+ }
1306
+
1307
+ // src/commands/deploy/adapters/fly-adapter.ts
1308
+ var import_node_child_process4 = require("child_process");
1309
+ var import_node_path9 = require("path");
1310
+
1311
+ // src/commands/deploy/artifacts/fly-toml-generator.ts
1312
+ var import_promises5 = require("fs/promises");
1313
+ var import_node_path6 = require("path");
1314
+ var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
1315
+ function generateFlyToml(options) {
1316
+ const internalPort = options.internalPort ?? 3e3;
1317
+ return [
1318
+ GENERATED_HEADER,
1319
+ "",
1320
+ `app = "${options.appName}"`,
1321
+ `primary_region = "${options.region}"`,
1322
+ "",
1323
+ "[build]",
1324
+ ` dockerfile = "Dockerfile"`,
1325
+ "",
1326
+ "[http_service]",
1327
+ ` internal_port = ${String(internalPort)}`,
1328
+ " force_https = true",
1329
+ ' auto_stop_machines = "stop"',
1330
+ " auto_start_machines = true",
1331
+ " min_machines_running = 0",
1332
+ "",
1333
+ " [[http_service.checks]]",
1334
+ ' grace_period = "10s"',
1335
+ ' interval = "30s"',
1336
+ ' method = "GET"',
1337
+ ' timeout = "5s"',
1338
+ ' path = "/"',
1339
+ ""
1340
+ ].join("\n");
1341
+ }
1342
+ async function writeFlyTomlArtifact(deployDirectory, options) {
1343
+ await (0, import_promises5.mkdir)(deployDirectory, { recursive: true });
1344
+ const filePath = (0, import_node_path6.join)(deployDirectory, "fly.toml");
1345
+ await (0, import_promises5.writeFile)(filePath, generateFlyToml(options), "utf-8");
1346
+ return filePath;
1347
+ }
1348
+
1349
+ // src/commands/deploy/builder/client-builder.ts
1350
+ var import_node_child_process3 = require("child_process");
1351
+ var import_promises6 = require("fs/promises");
1352
+ var import_node_fs3 = require("fs");
1353
+ var import_node_path7 = require("path");
1354
+ async function buildClient(options) {
1355
+ const viteEntryPoint = await resolveProjectBinaryEntryPoint(options.projectRoot, "vite", "vite");
1356
+ if (!viteEntryPoint) {
1357
+ throw new Error(
1358
+ `Could not find local Vite binary in ${options.projectRoot}. Install dependencies before deploying.`
1359
+ );
1360
+ }
1361
+ const args = [
1362
+ viteEntryPoint,
1363
+ "build",
1364
+ "--outDir",
1365
+ options.outDir,
1366
+ "--mode",
1367
+ options.mode ?? "production"
1368
+ ];
1369
+ await runProcess(process.execPath, args, options.projectRoot);
1370
+ await patchSqliteWasmAssets(options.projectRoot, options.outDir);
1371
+ return { outDir: options.outDir };
1372
+ }
1373
+ async function patchSqliteWasmAssets(projectRoot, outDir) {
1374
+ const assetsDir = (0, import_node_path7.join)(outDir, "assets");
1375
+ if (!(0, import_node_fs3.existsSync)(assetsDir)) return;
1376
+ const files = await (0, import_promises6.readdir)(assetsDir);
1377
+ const hashedWasm = files.find((f) => /^sqlite3-.+\.wasm$/.test(f));
1378
+ if (hashedWasm && !files.includes("sqlite3.wasm")) {
1379
+ await (0, import_promises6.copyFile)((0, import_node_path7.join)(assetsDir, hashedWasm), (0, import_node_path7.join)(assetsDir, "sqlite3.wasm"));
1380
+ }
1381
+ if (!files.includes("sqlite3-opfs-async-proxy.js")) {
1382
+ const proxyFile = (0, import_node_path7.resolve)(
1383
+ projectRoot,
1384
+ "node_modules",
1385
+ "@sqlite.org",
1386
+ "sqlite-wasm",
1387
+ "sqlite-wasm",
1388
+ "jswasm",
1389
+ "sqlite3-opfs-async-proxy.js"
1390
+ );
1391
+ if ((0, import_node_fs3.existsSync)(proxyFile)) {
1392
+ await (0, import_promises6.copyFile)(proxyFile, (0, import_node_path7.join)(assetsDir, "sqlite3-opfs-async-proxy.js"));
1393
+ }
1394
+ }
1395
+ }
1396
+ async function runProcess(command, args, cwd) {
1397
+ await new Promise((resolve9, reject) => {
1398
+ const child = (0, import_node_child_process3.spawn)(command, args, {
1399
+ cwd,
1400
+ stdio: "inherit",
1401
+ env: process.env
1402
+ });
1403
+ child.on("error", (error) => {
1404
+ reject(error);
1405
+ });
1406
+ child.on("exit", (code) => {
1407
+ if (code === 0) {
1408
+ resolve9();
1409
+ return;
1410
+ }
1411
+ reject(new Error(`Client build failed with exit code ${String(code ?? "unknown")}.`));
1412
+ });
1413
+ });
1414
+ }
1415
+
1416
+ // src/commands/deploy/builder/server-bundler.ts
1417
+ var import_promises7 = require("fs/promises");
1418
+ var import_promises8 = require("fs/promises");
1419
+ var import_node_path8 = require("path");
1420
+ var import_esbuild = require("esbuild");
1421
+ var DEFAULT_ENTRY_CANDIDATES = [
1422
+ "server.ts",
1423
+ "server.js",
1424
+ "src/server.ts",
1425
+ "src/server.js"
1426
+ ];
1427
+ async function bundleServer(options) {
1428
+ const candidates = options.entryFileCandidates ?? DEFAULT_ENTRY_CANDIDATES;
1429
+ const entryFilePath = await resolveServerEntry(options.projectRoot, candidates);
1430
+ if (!entryFilePath) {
1431
+ throw new Error(
1432
+ `Could not find a server entry file in ${options.projectRoot}. Looked for: ${candidates.join(", ")}`
1433
+ );
1434
+ }
1435
+ await (0, import_promises7.mkdir)(options.deployDirectory, { recursive: true });
1436
+ const outputFilePath = (0, import_node_path8.join)(options.deployDirectory, "server-bundled.js");
1437
+ await (0, import_esbuild.build)({
1438
+ entryPoints: [entryFilePath],
1439
+ outfile: outputFilePath,
1440
+ bundle: true,
1441
+ platform: "node",
1442
+ format: "esm",
1443
+ target: ["node20"],
1444
+ sourcemap: false,
1445
+ logLevel: "silent",
1446
+ external: ["better-sqlite3"],
1447
+ banner: {
1448
+ js: "import { createRequire as __createRequire } from 'module'; const require = __createRequire(import.meta.url);"
1449
+ }
1450
+ });
1451
+ return {
1452
+ entryFilePath,
1453
+ outputFilePath
1454
+ };
1455
+ }
1456
+ async function resolveServerEntry(projectRoot, candidates) {
1457
+ for (const candidate of candidates) {
1458
+ const fullPath = (0, import_node_path8.join)(projectRoot, candidate);
1459
+ try {
1460
+ await (0, import_promises8.access)(fullPath);
1461
+ return fullPath;
1462
+ } catch {
1463
+ }
1464
+ }
1465
+ return null;
1466
+ }
1467
+
1468
+ // src/commands/deploy/adapters/fly-adapter.ts
1469
+ var FlyAdapter = class {
1470
+ name = "fly";
1471
+ logger = createLogger();
1472
+ runner;
1473
+ commandCandidates;
1474
+ flyCommand = null;
1475
+ currentContext;
1476
+ lastDeploymentId = null;
1477
+ constructor(options = {}) {
1478
+ this.runner = options.runner ?? new NodeFlyCommandRunner();
1479
+ this.commandCandidates = options.commandCandidates ?? DEFAULT_FLY_COMMAND_CANDIDATES;
1480
+ this.currentContext = options.context ?? null;
1481
+ }
1482
+ /**
1483
+ * Seeds runtime context for non-provisioning commands.
1484
+ */
1485
+ setContext(context) {
1486
+ this.currentContext = context;
1487
+ }
1488
+ /**
1489
+ * Checks whether a Fly CLI executable can be resolved.
1490
+ */
1491
+ async detect() {
1492
+ const command = await this.tryResolveFlyCommand(process.cwd());
1493
+ return command !== null;
1494
+ }
1495
+ /**
1496
+ * Ensures Fly CLI is available before deployment.
1497
+ */
1498
+ async install() {
1499
+ const available = await this.detect();
1500
+ if (!available) {
1501
+ throw new Error(
1502
+ "Fly CLI is required but not installed. Install from https://fly.io/docs/hands-on/install-flyctl/."
1503
+ );
1504
+ }
1505
+ }
1506
+ /**
1507
+ * Performs Fly authentication check/login.
1508
+ */
1509
+ async authenticate() {
1510
+ const projectRoot = this.currentContext?.projectRoot ?? process.cwd();
1511
+ const status = await this.runFlyCommand(["auth", "whoami", "--json"], projectRoot, false);
1512
+ if (status.exitCode === 0) return;
1513
+ const login = await this.runFlyCommand(["auth", "login"], projectRoot, true);
1514
+ if (login.exitCode !== 0) {
1515
+ throw new Error(`Fly authentication failed: ${normalizeError(login.stderr, login.stdout)}`);
1516
+ }
1517
+ }
1518
+ /**
1519
+ * Provisions app and optionally region-bound resources in Fly.
1520
+ */
1521
+ async provision(config) {
1522
+ this.currentContext = {
1523
+ projectRoot: config.projectRoot,
1524
+ appName: config.appName,
1525
+ region: config.region ?? "iad"
1526
+ };
1527
+ const appCreateArgs = ["apps", "create", config.appName];
1528
+ const createApp = await this.runFlyCommand(appCreateArgs, config.projectRoot, false);
1529
+ if (createApp.exitCode !== 0 && !isAlreadyExistsResponse(createApp.stderr, createApp.stdout)) {
1530
+ throw new Error(
1531
+ `Fly app provisioning failed for "${config.appName}": ${normalizeError(createApp.stderr, createApp.stdout)}`
1532
+ );
1533
+ }
1534
+ const secrets = [];
1535
+ const portSecret = await this.runFlyCommand(
1536
+ ["secrets", "set", "PORT=3000", "--app", config.appName],
1537
+ config.projectRoot,
1538
+ false
1539
+ );
1540
+ if (portSecret.exitCode === 0) {
1541
+ secrets.push("PORT");
1542
+ }
1543
+ return {
1544
+ applicationId: config.appName,
1545
+ databaseId: null,
1546
+ secretsSet: secrets
1547
+ };
1548
+ }
1549
+ /**
1550
+ * Builds local artifacts consumed by Fly deploy.
1551
+ */
1552
+ async build(config) {
1553
+ this.currentContext = {
1554
+ projectRoot: config.projectRoot,
1555
+ appName: config.appName,
1556
+ region: config.region
1557
+ };
1558
+ const deployDirectory = (0, import_node_path9.join)(config.projectRoot, ".kora", "deploy");
1559
+ await writeFlyTomlArtifact(deployDirectory, {
1560
+ appName: config.appName,
1561
+ region: config.region ?? "iad"
1562
+ });
1563
+ await bundleServer({
1564
+ projectRoot: config.projectRoot,
1565
+ deployDirectory
1566
+ });
1567
+ const client = await buildClient({
1568
+ projectRoot: config.projectRoot,
1569
+ outDir: (0, import_node_path9.join)(deployDirectory, "dist"),
1570
+ mode: "production"
1571
+ });
1572
+ return {
1573
+ clientDirectory: client.outDir,
1574
+ serverBundlePath: (0, import_node_path9.join)(deployDirectory, "server-bundled.js"),
1575
+ deployDirectory
1576
+ };
1577
+ }
1578
+ /**
1579
+ * Deploys generated artifacts to Fly and returns live endpoints.
1580
+ */
1581
+ async deploy(artifacts) {
1582
+ const context = this.requireContext();
1583
+ const deploy = await this.runFlyCommand(
1584
+ ["deploy", "--config", "fly.toml", "--app", context.appName],
1585
+ artifacts.deployDirectory,
1586
+ true
1587
+ );
1588
+ if (deploy.exitCode !== 0) {
1589
+ throw new Error(`Fly deployment failed: ${normalizeError(deploy.stderr, deploy.stdout)}`);
1590
+ }
1591
+ const info = await this.runFlyCommand(
1592
+ ["status", "--app", context.appName, "--json"],
1593
+ context.projectRoot,
1594
+ false
1595
+ );
1596
+ const hostname = parseFlyHostname(info.stdout) ?? `${context.appName}.fly.dev`;
1597
+ const deploymentId = parseFlyDeploymentId(info.stdout) ?? (/* @__PURE__ */ new Date()).toISOString();
1598
+ this.lastDeploymentId = deploymentId;
1599
+ return {
1600
+ deploymentId,
1601
+ liveUrl: `https://${hostname}`,
1602
+ syncUrl: `wss://${hostname}/kora-sync`
1603
+ };
1604
+ }
1605
+ /**
1606
+ * Rolls back to a deployment version.
1607
+ */
1608
+ async rollback(deploymentId) {
1609
+ const context = this.requireContext();
1610
+ const rollback = await this.runFlyCommand(
1611
+ ["releases", "revert", deploymentId, "--app", context.appName],
1612
+ context.projectRoot,
1613
+ true
1614
+ );
1615
+ if (rollback.exitCode !== 0) {
1616
+ throw new Error(`Fly rollback failed: ${normalizeError(rollback.stderr, rollback.stdout)}`);
1617
+ }
1618
+ }
1619
+ /**
1620
+ * Returns deployment logs as an async iterable.
1621
+ */
1622
+ async *logs(options) {
1623
+ const context = this.requireContext();
1624
+ const args = ["logs", "--app", context.appName, "--no-tail"];
1625
+ if (typeof options.since === "string" && options.since.length > 0) {
1626
+ args.push("--region", options.since);
1627
+ }
1628
+ const result = await this.runFlyCommand(args, context.projectRoot, false);
1629
+ if (result.exitCode !== 0) {
1630
+ throw new Error(`Fly logs failed: ${normalizeError(result.stderr, result.stdout)}`);
1631
+ }
1632
+ const lines = result.stdout.split(/\r?\n/).filter((line) => line.length > 0);
1633
+ for (const line of lines) {
1634
+ yield {
1635
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1636
+ level: inferLogLevel(line),
1637
+ message: line
1638
+ };
1639
+ }
1640
+ }
1641
+ /**
1642
+ * Reads current deployment status from Fly.
1643
+ */
1644
+ async status() {
1645
+ const context = this.requireContext();
1646
+ const status = await this.runFlyCommand(
1647
+ ["status", "--app", context.appName, "--json"],
1648
+ context.projectRoot,
1649
+ false
1650
+ );
1651
+ if (status.exitCode !== 0) {
1652
+ return {
1653
+ state: "failed",
1654
+ message: normalizeError(status.stderr, status.stdout)
1655
+ };
1656
+ }
1657
+ const hostname = parseFlyHostname(status.stdout);
1658
+ return {
1659
+ state: "healthy",
1660
+ message: "Fly deployment is healthy.",
1661
+ liveUrl: hostname ? `https://${hostname}` : void 0
1662
+ };
1663
+ }
1664
+ async runFlyCommand(flyArgs, cwd, inheritOutput) {
1665
+ const flyBinary = await this.resolveFlyCommand(cwd);
1666
+ if (inheritOutput) {
1667
+ this.logger.step(`fly ${flyArgs.join(" ")}`);
1668
+ }
1669
+ return await this.runner.run(flyBinary, flyArgs, cwd);
1670
+ }
1671
+ requireContext() {
1672
+ if (!this.currentContext) {
1673
+ throw new Error("Fly adapter context is not initialized. Run provision() first.");
1674
+ }
1675
+ return this.currentContext;
1676
+ }
1677
+ async resolveFlyCommand(cwd) {
1678
+ if (this.flyCommand) {
1679
+ return this.flyCommand;
1680
+ }
1681
+ const resolved = await this.tryResolveFlyCommand(cwd);
1682
+ if (!resolved) {
1683
+ throw new Error("Could not resolve a usable Fly CLI command (tried flyctl, fly).");
1684
+ }
1685
+ this.flyCommand = resolved;
1686
+ return resolved;
1687
+ }
1688
+ async tryResolveFlyCommand(cwd) {
1689
+ for (const command of this.commandCandidates) {
1690
+ const versionCheck = await this.runner.run(command, ["version"], cwd);
1691
+ if (versionCheck.exitCode === 0) {
1692
+ return command;
1693
+ }
1694
+ }
1695
+ return null;
1696
+ }
1697
+ };
1698
+ var NodeFlyCommandRunner = class {
1699
+ async run(command, args, cwd) {
1700
+ return await new Promise((resolve9) => {
1701
+ const child = (0, import_node_child_process4.spawn)(command, args, {
1702
+ cwd,
1703
+ env: process.env,
1704
+ stdio: ["ignore", "pipe", "pipe"]
1705
+ });
1706
+ let stdout = "";
1707
+ let stderr = "";
1708
+ child.stdout?.on("data", (chunk) => {
1709
+ stdout += chunk.toString("utf-8");
1710
+ });
1711
+ child.stderr?.on("data", (chunk) => {
1712
+ stderr += chunk.toString("utf-8");
1713
+ });
1714
+ child.on("error", (error) => {
1715
+ resolve9({
1716
+ exitCode: 1,
1717
+ stdout,
1718
+ stderr: `${stderr}
1719
+ ${error.message}`
1720
+ });
1721
+ });
1722
+ child.on("exit", (code) => {
1723
+ resolve9({
1724
+ exitCode: code ?? 1,
1725
+ stdout: stdout.trim(),
1726
+ stderr: stderr.trim()
1727
+ });
1728
+ });
1729
+ });
1730
+ }
1731
+ };
1732
+ var DEFAULT_FLY_COMMAND_CANDIDATES = ["flyctl", "fly"];
1733
+ function parseFlyHostname(rawJson) {
1734
+ const parsed = parseJsonRecord(rawJson);
1735
+ if (!parsed) return null;
1736
+ const hostname = parsed.Hostname;
1737
+ if (typeof hostname === "string" && hostname.length > 0) {
1738
+ return hostname;
1739
+ }
1740
+ const hostnames = parsed.Hostnames;
1741
+ if (Array.isArray(hostnames)) {
1742
+ const first = hostnames.find((item) => typeof item === "string");
1743
+ if (typeof first === "string" && first.length > 0) {
1744
+ return first;
1745
+ }
1746
+ }
1747
+ return null;
1748
+ }
1749
+ function parseFlyDeploymentId(rawJson) {
1750
+ const parsed = parseJsonRecord(rawJson);
1751
+ if (!parsed) return null;
1752
+ const deploymentId = parsed.DeploymentID;
1753
+ if (typeof deploymentId === "string" && deploymentId.length > 0) {
1754
+ return deploymentId;
1755
+ }
1756
+ const latestDeployment = parsed.LatestDeployment;
1757
+ if (typeof latestDeployment === "object" && latestDeployment !== null) {
1758
+ const id = latestDeployment.ID;
1759
+ if (typeof id === "string" && id.length > 0) {
1760
+ return id;
1761
+ }
1762
+ }
1763
+ return null;
1764
+ }
1765
+ function parseJsonRecord(value) {
1766
+ try {
1767
+ const parsed = JSON.parse(value);
1768
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
1769
+ return parsed;
1770
+ }
1771
+ return null;
1772
+ } catch {
1773
+ return null;
1774
+ }
1775
+ }
1776
+ function inferLogLevel(line) {
1777
+ const normalized = line.toLowerCase();
1778
+ if (normalized.includes("error")) return "error";
1779
+ if (normalized.includes("warn")) return "warn";
1780
+ if (normalized.includes("debug")) return "debug";
1781
+ return "info";
1782
+ }
1783
+ function normalizeError(stderr, stdout) {
1784
+ if (stderr.length > 0) return stderr;
1785
+ if (stdout.length > 0) return stdout;
1786
+ return "unknown fly CLI error";
1787
+ }
1788
+ function isAlreadyExistsResponse(stderr, stdout) {
1789
+ const text = `${stderr}
1790
+ ${stdout}`.toLowerCase();
1791
+ return text.includes("already exists") || text.includes("already been taken");
1792
+ }
1793
+
1794
+ // src/commands/deploy/adapters/railway-adapter.ts
1795
+ var import_node_child_process5 = require("child_process");
1796
+ var import_node_path11 = require("path");
1797
+
1798
+ // src/commands/deploy/artifacts/railway-json-generator.ts
1799
+ var import_promises9 = require("fs/promises");
1800
+ var import_node_path10 = require("path");
1801
+ var GENERATED_HEADER2 = "Generated by kora deploy - do not edit manually.";
1802
+ function generateRailwayJson(options) {
1803
+ const file = {
1804
+ $schema: "https://railway.app/railway.schema.json",
1805
+ build: {
1806
+ builder: "DOCKERFILE",
1807
+ dockerfilePath: "Dockerfile"
1808
+ },
1809
+ deploy: {
1810
+ startCommand: options.startCommand ?? "node ./server-bundled.js",
1811
+ healthcheckPath: options.healthcheckPath ?? "/health",
1812
+ restartPolicyType: "on_failure",
1813
+ restartPolicyMaxRetries: 3
1814
+ },
1815
+ metadata: {
1816
+ generatedBy: GENERATED_HEADER2,
1817
+ appName: options.appName,
1818
+ region: options.region
1819
+ }
1820
+ };
1821
+ return `${JSON.stringify(file, null, 2)}
1822
+ `;
1823
+ }
1824
+ async function writeRailwayJsonArtifact(deployDirectory, options) {
1825
+ await (0, import_promises9.mkdir)(deployDirectory, { recursive: true });
1826
+ const filePath = (0, import_node_path10.join)(deployDirectory, "railway.json");
1827
+ await (0, import_promises9.writeFile)(filePath, generateRailwayJson(options), "utf-8");
1828
+ return filePath;
1829
+ }
1830
+
1831
+ // src/commands/deploy/adapters/railway-adapter.ts
1832
+ var RailwayAdapter = class {
1833
+ name = "railway";
1834
+ logger = createLogger();
1835
+ runner;
1836
+ commandCandidates;
1837
+ railwayCommand = null;
1838
+ currentContext;
1839
+ lastDeploymentId = null;
1840
+ constructor(options = {}) {
1841
+ this.runner = options.runner ?? new NodeRailwayCommandRunner();
1842
+ this.commandCandidates = options.commandCandidates ?? DEFAULT_RAILWAY_COMMAND_CANDIDATES;
1843
+ this.currentContext = options.context ?? null;
1844
+ }
1845
+ setContext(context) {
1846
+ this.currentContext = context;
1847
+ }
1848
+ async detect() {
1849
+ const command = await this.tryResolveRailwayCommand(process.cwd());
1850
+ return command !== null;
1851
+ }
1852
+ async install() {
1853
+ const available = await this.detect();
1854
+ if (!available) {
1855
+ throw new Error(
1856
+ "Railway CLI is required but not installed. Install with `npm i -g @railway/cli` or see https://docs.railway.com/guides/cli."
1857
+ );
1858
+ }
1859
+ }
1860
+ async authenticate() {
1861
+ const projectRoot = this.currentContext?.projectRoot ?? process.cwd();
1862
+ const whoami = await this.runRailwayCommand(["whoami"], projectRoot, false);
1863
+ if (whoami.exitCode === 0) return;
1864
+ const login = await this.runRailwayCommand(["login"], projectRoot, true);
1865
+ if (login.exitCode !== 0) {
1866
+ throw new Error(
1867
+ `Railway authentication failed: ${normalizeError2(login.stderr, login.stdout)}`
1868
+ );
1869
+ }
1870
+ }
1871
+ async provision(config) {
1872
+ this.currentContext = {
1873
+ projectRoot: config.projectRoot,
1874
+ appName: config.appName,
1875
+ region: config.region
1876
+ };
1877
+ const init = await this.runRailwayCommand(
1878
+ ["init", "--name", config.appName, "--confirm"],
1879
+ config.projectRoot,
1880
+ false
1881
+ );
1882
+ if (init.exitCode !== 0 && !isAlreadyExistsResponse2(init.stderr, init.stdout)) {
1883
+ throw new Error(
1884
+ `Railway project provisioning failed for "${config.appName}": ${normalizeError2(init.stderr, init.stdout)}`
1885
+ );
1886
+ }
1887
+ const link = await this.runRailwayCommand(
1888
+ ["link", "--project", config.appName, "--environment", config.environment, "--yes"],
1889
+ config.projectRoot,
1890
+ false
1891
+ );
1892
+ if (link.exitCode !== 0 && !isAlreadyExistsResponse2(link.stderr, link.stdout)) {
1893
+ throw new Error(`Railway link failed: ${normalizeError2(link.stderr, link.stdout)}`);
1894
+ }
1895
+ const vars = [];
1896
+ const setPort = await this.runRailwayCommand(
1897
+ ["variables", "set", "PORT=3000", "--yes"],
1898
+ config.projectRoot,
1899
+ false
1900
+ );
1901
+ if (setPort.exitCode === 0) {
1902
+ vars.push("PORT");
1903
+ }
1904
+ return {
1905
+ applicationId: config.appName,
1906
+ databaseId: null,
1907
+ secretsSet: vars
1908
+ };
1909
+ }
1910
+ async build(config) {
1911
+ this.currentContext = {
1912
+ projectRoot: config.projectRoot,
1913
+ appName: config.appName,
1914
+ region: config.region
1915
+ };
1916
+ const deployDirectory = (0, import_node_path11.join)(config.projectRoot, ".kora", "deploy");
1917
+ await writeRailwayJsonArtifact(deployDirectory, {
1918
+ appName: config.appName,
1919
+ environment: config.environment
1920
+ });
1921
+ await bundleServer({
1922
+ projectRoot: config.projectRoot,
1923
+ deployDirectory
1924
+ });
1925
+ const client = await buildClient({
1926
+ projectRoot: config.projectRoot,
1927
+ outDir: (0, import_node_path11.join)(deployDirectory, "dist"),
1928
+ mode: "production"
1929
+ });
1930
+ return {
1931
+ clientDirectory: client.outDir,
1932
+ serverBundlePath: (0, import_node_path11.join)(deployDirectory, "server-bundled.js"),
1933
+ deployDirectory
1934
+ };
1935
+ }
1936
+ async deploy(artifacts) {
1937
+ const context = this.requireContext();
1938
+ const up = await this.runRailwayCommand(["up", "--yes"], artifacts.deployDirectory, true);
1939
+ if (up.exitCode !== 0) {
1940
+ throw new Error(`Railway deployment failed: ${normalizeError2(up.stderr, up.stdout)}`);
1941
+ }
1942
+ const status = await this.runRailwayCommand(["status", "--json"], context.projectRoot, false);
1943
+ const deploymentId = parseRailwayDeploymentId(status.stdout) ?? (/* @__PURE__ */ new Date()).toISOString();
1944
+ const liveUrl = parseRailwayUrl(status.stdout) ?? `https://${context.appName}.up.railway.app`;
1945
+ this.lastDeploymentId = deploymentId;
1946
+ return {
1947
+ deploymentId,
1948
+ liveUrl,
1949
+ syncUrl: toSyncUrl(liveUrl)
1950
+ };
1951
+ }
1952
+ async rollback(deploymentId) {
1953
+ const context = this.requireContext();
1954
+ const rollback = await this.runRailwayCommand(
1955
+ ["redeploy", "--deployment", deploymentId, "--yes"],
1956
+ context.projectRoot,
1957
+ true
1958
+ );
1959
+ if (rollback.exitCode !== 0) {
1960
+ throw new Error(
1961
+ `Railway rollback failed: ${normalizeError2(rollback.stderr, rollback.stdout)}`
1962
+ );
1963
+ }
1964
+ }
1965
+ async *logs(options) {
1966
+ const context = this.requireContext();
1967
+ const args = ["logs"];
1968
+ if (typeof options.tail === "number" && options.tail > 0) {
1969
+ args.push("--lines", String(options.tail));
1970
+ }
1971
+ const result = await this.runRailwayCommand(args, context.projectRoot, false);
1972
+ if (result.exitCode !== 0) {
1973
+ throw new Error(`Railway logs failed: ${normalizeError2(result.stderr, result.stdout)}`);
1974
+ }
1975
+ const lines = result.stdout.split(/\r?\n/).filter((line) => line.length > 0);
1976
+ for (const line of lines) {
1977
+ yield {
1978
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1979
+ level: inferLogLevel2(line),
1980
+ message: line
1981
+ };
1982
+ }
1983
+ }
1984
+ async status() {
1985
+ const context = this.requireContext();
1986
+ const status = await this.runRailwayCommand(["status", "--json"], context.projectRoot, false);
1987
+ if (status.exitCode !== 0) {
1988
+ return {
1989
+ state: "failed",
1990
+ message: normalizeError2(status.stderr, status.stdout)
1991
+ };
1992
+ }
1993
+ const liveUrl = parseRailwayUrl(status.stdout);
1994
+ return {
1995
+ state: "healthy",
1996
+ message: "Railway deployment is healthy.",
1997
+ liveUrl: liveUrl ?? void 0
1998
+ };
1999
+ }
2000
+ async runRailwayCommand(railwayArgs, cwd, inheritOutput) {
2001
+ const command = await this.resolveRailwayCommand(cwd);
2002
+ if (inheritOutput) {
2003
+ this.logger.step(`railway ${railwayArgs.join(" ")}`);
2004
+ }
2005
+ return await this.runner.run(command, railwayArgs, cwd);
2006
+ }
2007
+ requireContext() {
2008
+ if (!this.currentContext) {
2009
+ throw new Error("Railway adapter context is not initialized. Run provision() first.");
2010
+ }
2011
+ return this.currentContext;
2012
+ }
2013
+ async resolveRailwayCommand(cwd) {
2014
+ if (this.railwayCommand) {
2015
+ return this.railwayCommand;
2016
+ }
2017
+ const resolved = await this.tryResolveRailwayCommand(cwd);
2018
+ if (!resolved) {
2019
+ throw new Error("Could not resolve a usable Railway CLI command (tried railway).");
2020
+ }
2021
+ this.railwayCommand = resolved;
2022
+ return resolved;
2023
+ }
2024
+ async tryResolveRailwayCommand(cwd) {
2025
+ for (const command of this.commandCandidates) {
2026
+ const version = await this.runner.run(command, ["--version"], cwd);
2027
+ if (version.exitCode === 0) {
2028
+ return command;
2029
+ }
2030
+ }
2031
+ return null;
2032
+ }
2033
+ };
2034
+ var NodeRailwayCommandRunner = class {
2035
+ async run(command, args, cwd) {
2036
+ return await new Promise((resolve9) => {
2037
+ const child = (0, import_node_child_process5.spawn)(command, args, {
2038
+ cwd,
2039
+ env: process.env,
2040
+ stdio: ["ignore", "pipe", "pipe"]
2041
+ });
2042
+ let stdout = "";
2043
+ let stderr = "";
2044
+ child.stdout?.on("data", (chunk) => {
2045
+ stdout += chunk.toString("utf-8");
2046
+ });
2047
+ child.stderr?.on("data", (chunk) => {
2048
+ stderr += chunk.toString("utf-8");
2049
+ });
2050
+ child.on("error", (error) => {
2051
+ resolve9({
2052
+ exitCode: 1,
2053
+ stdout,
2054
+ stderr: `${stderr}
2055
+ ${error.message}`
2056
+ });
2057
+ });
2058
+ child.on("exit", (code) => {
2059
+ resolve9({
2060
+ exitCode: code ?? 1,
2061
+ stdout: stdout.trim(),
2062
+ stderr: stderr.trim()
2063
+ });
2064
+ });
2065
+ });
2066
+ }
2067
+ };
2068
+ var DEFAULT_RAILWAY_COMMAND_CANDIDATES = ["railway"];
2069
+ function parseRailwayUrl(rawJson) {
2070
+ const record = parseJsonRecord2(rawJson);
2071
+ if (!record) return null;
2072
+ const url = record.url;
2073
+ if (typeof url === "string" && url.length > 0) {
2074
+ return ensureHttpsUrl(url);
2075
+ }
2076
+ const service = record.service;
2077
+ if (typeof service === "object" && service !== null) {
2078
+ const domain = service.domain;
2079
+ if (typeof domain === "string" && domain.length > 0) {
2080
+ return ensureHttpsUrl(domain);
2081
+ }
2082
+ }
2083
+ return null;
2084
+ }
2085
+ function parseRailwayDeploymentId(rawJson) {
2086
+ const record = parseJsonRecord2(rawJson);
2087
+ if (!record) return null;
2088
+ const deploymentId = record.deploymentId;
2089
+ if (typeof deploymentId === "string" && deploymentId.length > 0) {
2090
+ return deploymentId;
2091
+ }
2092
+ const deployment = record.deployment;
2093
+ if (typeof deployment === "object" && deployment !== null) {
2094
+ const id = deployment.id;
2095
+ if (typeof id === "string" && id.length > 0) {
2096
+ return id;
2097
+ }
2098
+ }
2099
+ return null;
2100
+ }
2101
+ function parseJsonRecord2(value) {
2102
+ try {
2103
+ const parsed = JSON.parse(value);
2104
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
2105
+ return parsed;
2106
+ }
2107
+ return null;
2108
+ } catch {
2109
+ return null;
2110
+ }
2111
+ }
2112
+ function toSyncUrl(liveUrl) {
2113
+ try {
2114
+ const url = new URL(liveUrl);
2115
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
2116
+ url.pathname = "/kora-sync";
2117
+ return url.toString().replace(/\/$/, "");
2118
+ } catch {
2119
+ return null;
2120
+ }
2121
+ }
2122
+ function ensureHttpsUrl(value) {
2123
+ if (value.startsWith("http://") || value.startsWith("https://")) {
2124
+ return value;
2125
+ }
2126
+ return `https://${value}`;
2127
+ }
2128
+ function inferLogLevel2(line) {
2129
+ const normalized = line.toLowerCase();
2130
+ if (normalized.includes("error")) return "error";
2131
+ if (normalized.includes("warn")) return "warn";
2132
+ if (normalized.includes("debug")) return "debug";
2133
+ return "info";
2134
+ }
2135
+ function normalizeError2(stderr, stdout) {
2136
+ if (stderr.length > 0) return stderr;
2137
+ if (stdout.length > 0) return stdout;
2138
+ return "unknown railway CLI error";
2139
+ }
2140
+ function isAlreadyExistsResponse2(stderr, stdout) {
2141
+ const text = `${stderr}
2142
+ ${stdout}`.toLowerCase();
2143
+ return text.includes("already exists");
2144
+ }
2145
+
2146
+ // src/commands/deploy/adapters/stub-adapter.ts
2147
+ var StubDeployAdapter = class {
2148
+ name;
2149
+ contextLabel;
2150
+ constructor(platform) {
2151
+ this.name = platform;
2152
+ this.contextLabel = `Deploy adapter "${platform}" is not implemented yet.`;
2153
+ }
2154
+ setContext(_context) {
2155
+ }
2156
+ async detect() {
2157
+ return false;
2158
+ }
2159
+ async install() {
2160
+ throw this.notImplementedError();
2161
+ }
2162
+ async authenticate() {
2163
+ throw this.notImplementedError();
2164
+ }
2165
+ async provision(_config) {
2166
+ throw this.notImplementedError();
2167
+ }
2168
+ async build(_config) {
2169
+ throw this.notImplementedError();
2170
+ }
2171
+ async deploy(_artifacts) {
2172
+ throw this.notImplementedError();
2173
+ }
2174
+ async rollback(_deploymentId) {
2175
+ throw this.notImplementedError();
2176
+ }
2177
+ logs(_options) {
2178
+ throw this.notImplementedError();
2179
+ }
2180
+ async status() {
2181
+ return {
2182
+ state: "unknown",
2183
+ message: this.notImplementedMessage()
329
2184
  };
330
- ask();
331
- });
2185
+ }
2186
+ notImplementedError() {
2187
+ return new Error(this.notImplementedMessage());
2188
+ }
2189
+ notImplementedMessage() {
2190
+ return `${this.contextLabel} Start with --platform fly for now.`;
2191
+ }
2192
+ };
2193
+
2194
+ // src/commands/deploy/adapters/factory.ts
2195
+ function createDeployAdapter(platform) {
2196
+ switch (platform) {
2197
+ case "fly":
2198
+ return new FlyAdapter();
2199
+ case "railway":
2200
+ return new RailwayAdapter();
2201
+ case "render":
2202
+ case "docker":
2203
+ case "kora-cloud":
2204
+ return new StubDeployAdapter(platform);
2205
+ default: {
2206
+ const exhaustiveCheck = platform;
2207
+ return exhaustiveCheck;
2208
+ }
2209
+ }
332
2210
  }
333
- function createReadline(options) {
334
- return (0, import_node_readline.createInterface)({
335
- input: options?.input ?? process.stdin,
336
- output: options?.output ?? process.stdout
337
- });
2211
+
2212
+ // src/commands/deploy/artifacts/dockerfile-generator.ts
2213
+ var import_promises10 = require("fs/promises");
2214
+ var import_node_path12 = require("path");
2215
+ var GENERATED_HEADER3 = "# Generated by kora deploy \u2014 do not edit manually";
2216
+ function generateDockerfile(options = {}) {
2217
+ const nodeVersion = options.nodeVersion ?? "20-alpine";
2218
+ const port = options.port ?? 3e3;
2219
+ const clientDirectory = options.clientDirectory ?? "dist";
2220
+ const serverBundleFile = options.serverBundleFile ?? "server-bundled.js";
2221
+ const hasNativeDeps = options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0;
2222
+ const lines = [
2223
+ GENERATED_HEADER3,
2224
+ "",
2225
+ `FROM node:${nodeVersion} AS runtime`,
2226
+ "WORKDIR /app",
2227
+ "ENV NODE_ENV=production",
2228
+ `ENV PORT=${String(port)}`,
2229
+ ""
2230
+ ];
2231
+ if (hasNativeDeps) {
2232
+ lines.push("# Install build tools for native modules");
2233
+ lines.push("RUN apk add --no-cache python3 make g++");
2234
+ lines.push("");
2235
+ lines.push("COPY package.json ./package.json");
2236
+ lines.push("RUN npm install --omit=dev");
2237
+ lines.push("");
2238
+ }
2239
+ lines.push(`COPY ${clientDirectory} ./dist`);
2240
+ lines.push(`COPY ${serverBundleFile} ./server-bundled.js`);
2241
+ lines.push("");
2242
+ lines.push(`EXPOSE ${String(port)}`);
2243
+ lines.push('CMD ["node", "./server-bundled.js"]');
2244
+ lines.push("");
2245
+ return lines.join("\n");
2246
+ }
2247
+ function generateDeployPackageJson(nativeDependencies) {
2248
+ const pkg = {
2249
+ name: "kora-deploy",
2250
+ version: "1.0.0",
2251
+ private: true,
2252
+ type: "module",
2253
+ dependencies: nativeDependencies
2254
+ };
2255
+ return JSON.stringify(pkg, null, 2) + "\n";
2256
+ }
2257
+ function generateDockerIgnore() {
2258
+ return [
2259
+ GENERATED_HEADER3,
2260
+ "",
2261
+ "node_modules",
2262
+ "npm-debug.log*",
2263
+ "pnpm-debug.log*",
2264
+ "yarn-error.log*",
2265
+ ".env",
2266
+ ".env.*",
2267
+ ".git",
2268
+ ".gitignore",
2269
+ "coverage",
2270
+ ".turbo",
2271
+ "*.test.ts",
2272
+ "*.spec.ts",
2273
+ ""
2274
+ ].join("\n");
2275
+ }
2276
+ async function writeDockerfileArtifact(deployDirectory, options = {}) {
2277
+ await (0, import_promises10.mkdir)(deployDirectory, { recursive: true });
2278
+ const dockerfilePath = (0, import_node_path12.join)(deployDirectory, "Dockerfile");
2279
+ await (0, import_promises10.writeFile)(dockerfilePath, generateDockerfile(options), "utf-8");
2280
+ if (options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0) {
2281
+ const pkgJsonPath = (0, import_node_path12.join)(deployDirectory, "package.json");
2282
+ await (0, import_promises10.writeFile)(pkgJsonPath, generateDeployPackageJson(options.nativeDependencies), "utf-8");
2283
+ }
2284
+ return dockerfilePath;
2285
+ }
2286
+ async function writeDockerIgnoreArtifact(deployDirectory) {
2287
+ await (0, import_promises10.mkdir)(deployDirectory, { recursive: true });
2288
+ const dockerIgnorePath = (0, import_node_path12.join)(deployDirectory, ".dockerignore");
2289
+ await (0, import_promises10.writeFile)(dockerIgnorePath, generateDockerIgnore(), "utf-8");
2290
+ return dockerIgnorePath;
338
2291
  }
339
2292
 
340
- // src/commands/create/template-engine.ts
341
- var import_node_fs = require("fs");
342
- var import_promises2 = require("fs/promises");
343
- var import_node_path2 = require("path");
344
- var import_node_url = require("url");
345
- var import_meta = {};
346
- function substituteVariables(template, context) {
347
- return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
348
- const value = context[key];
349
- return value !== void 0 ? value : `{{${key}}}`;
350
- });
2293
+ // src/commands/deploy/state/deploy-state.ts
2294
+ var import_promises11 = require("fs/promises");
2295
+ var import_node_path13 = require("path");
2296
+ var KORA_DEPLOY_DIRECTORY = (0, import_node_path13.join)(".kora", "deploy");
2297
+ var DEPLOY_STATE_FILENAME = "deploy.json";
2298
+ function resolveDeployDirectory(projectRoot) {
2299
+ return (0, import_node_path13.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
351
2300
  }
352
- function getTemplatePath(templateName) {
353
- let dir = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
354
- for (let i = 0; i < 5; i++) {
355
- if ((0, import_node_fs.existsSync)((0, import_node_path2.resolve)(dir, "templates"))) {
356
- return (0, import_node_path2.resolve)(dir, "templates", templateName);
357
- }
358
- dir = (0, import_node_path2.dirname)(dir);
2301
+ function resolveDeployStatePath(projectRoot) {
2302
+ return (0, import_node_path13.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
2303
+ }
2304
+ async function readDeployState(projectRoot) {
2305
+ const statePath = resolveDeployStatePath(projectRoot);
2306
+ try {
2307
+ const source = await (0, import_promises11.readFile)(statePath, "utf-8");
2308
+ const parsed = JSON.parse(source);
2309
+ return parseDeployState(parsed);
2310
+ } catch (error) {
2311
+ const code = error.code;
2312
+ if (code === "ENOENT") return null;
2313
+ throw error;
359
2314
  }
360
- const currentDir = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
361
- return (0, import_node_path2.resolve)(currentDir, "..", "templates", templateName);
362
2315
  }
363
- async function scaffoldTemplate(templateName, targetDir, context) {
364
- const templateDir = getTemplatePath(templateName);
365
- const vars = {
366
- projectName: context.projectName,
367
- packageManager: context.packageManager,
368
- koraVersion: context.koraVersion
2316
+ async function writeDeployState(projectRoot, input, now = /* @__PURE__ */ new Date()) {
2317
+ const timestamp = now.toISOString();
2318
+ const state = {
2319
+ platform: input.platform,
2320
+ appName: input.appName,
2321
+ region: input.region,
2322
+ projectRoot: input.projectRoot,
2323
+ liveUrl: input.liveUrl ?? null,
2324
+ syncUrl: input.syncUrl ?? null,
2325
+ databaseId: input.databaseId ?? null,
2326
+ lastDeploymentId: input.lastDeploymentId ?? null,
2327
+ createdAt: timestamp,
2328
+ updatedAt: timestamp
369
2329
  };
370
- await copyDirectory(templateDir, targetDir, vars);
2330
+ await persistDeployState(projectRoot, state);
2331
+ return state;
371
2332
  }
372
- async function copyDirectory(src, dest, vars) {
373
- await (0, import_promises2.mkdir)(dest, { recursive: true });
374
- const entries = await (0, import_promises2.readdir)(src);
375
- for (const entry of entries) {
376
- const srcPath = (0, import_node_path2.join)(src, entry);
377
- const srcStat = await (0, import_promises2.stat)(srcPath);
378
- if (srcStat.isDirectory()) {
379
- await copyDirectory(srcPath, (0, import_node_path2.join)(dest, entry), vars);
380
- } else if (entry.endsWith(".hbs")) {
381
- const content = await (0, import_promises2.readFile)(srcPath, "utf-8");
382
- const outputName = entry.slice(0, -4);
383
- await (0, import_promises2.writeFile)((0, import_node_path2.join)(dest, outputName), substituteVariables(content, vars), "utf-8");
384
- } else {
385
- await (0, import_promises2.copyFile)(srcPath, (0, import_node_path2.join)(dest, entry));
386
- }
2333
+ async function updateDeployState(projectRoot, patch, now = /* @__PURE__ */ new Date()) {
2334
+ const existing = await readDeployState(projectRoot);
2335
+ if (!existing) {
2336
+ throw new Error("Cannot update deploy state because deploy.json does not exist yet.");
387
2337
  }
2338
+ const nextState = {
2339
+ platform: patch.platform ?? existing.platform,
2340
+ appName: patch.appName ?? existing.appName,
2341
+ region: patch.region === void 0 ? existing.region : patch.region,
2342
+ projectRoot: patch.projectRoot ?? existing.projectRoot,
2343
+ liveUrl: patch.liveUrl === void 0 ? existing.liveUrl : patch.liveUrl,
2344
+ syncUrl: patch.syncUrl === void 0 ? existing.syncUrl : patch.syncUrl,
2345
+ databaseId: patch.databaseId === void 0 ? existing.databaseId : patch.databaseId,
2346
+ lastDeploymentId: patch.lastDeploymentId === void 0 ? existing.lastDeploymentId : patch.lastDeploymentId,
2347
+ createdAt: existing.createdAt,
2348
+ updatedAt: now.toISOString()
2349
+ };
2350
+ await persistDeployState(projectRoot, nextState);
2351
+ return nextState;
2352
+ }
2353
+ async function resetDeployState(projectRoot) {
2354
+ await (0, import_promises11.rm)(resolveDeployDirectory(projectRoot), { recursive: true, force: true });
2355
+ }
2356
+ async function persistDeployState(projectRoot, state) {
2357
+ const deployDir = resolveDeployDirectory(projectRoot);
2358
+ const statePath = resolveDeployStatePath(projectRoot);
2359
+ await (0, import_promises11.mkdir)(deployDir, { recursive: true });
2360
+ await (0, import_promises11.writeFile)(statePath, `${JSON.stringify(state, null, 2)}
2361
+ `, "utf-8");
2362
+ }
2363
+ function parseDeployState(value) {
2364
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2365
+ throw new Error("Invalid deploy state: expected an object.");
2366
+ }
2367
+ const record = value;
2368
+ const platform = readPlatform(record.platform);
2369
+ const appName = readString(record.appName, "appName");
2370
+ const region = readOptionalString(record.region, "region");
2371
+ const projectRoot = readString(record.projectRoot, "projectRoot");
2372
+ const liveUrl = readOptionalString(record.liveUrl, "liveUrl");
2373
+ const syncUrl = readOptionalString(record.syncUrl, "syncUrl");
2374
+ const databaseId = readOptionalString(record.databaseId, "databaseId");
2375
+ const lastDeploymentId = readOptionalString(record.lastDeploymentId, "lastDeploymentId");
2376
+ const createdAt = readString(record.createdAt, "createdAt");
2377
+ const updatedAt = readString(record.updatedAt, "updatedAt");
2378
+ return {
2379
+ platform,
2380
+ appName,
2381
+ region,
2382
+ projectRoot,
2383
+ liveUrl,
2384
+ syncUrl,
2385
+ databaseId,
2386
+ lastDeploymentId,
2387
+ createdAt,
2388
+ updatedAt
2389
+ };
2390
+ }
2391
+ function readPlatform(value) {
2392
+ if (typeof value !== "string" || !isDeployPlatform(value)) {
2393
+ throw new Error('Invalid deploy state: "platform" must be a supported platform.');
2394
+ }
2395
+ return value;
2396
+ }
2397
+ function readString(value, field) {
2398
+ if (typeof value !== "string" || value.length === 0) {
2399
+ throw new Error(`Invalid deploy state: "${field}" must be a non-empty string.`);
2400
+ }
2401
+ return value;
2402
+ }
2403
+ function readOptionalString(value, field) {
2404
+ if (value === null) return null;
2405
+ if (typeof value === "string") return value;
2406
+ throw new Error(`Invalid deploy state: "${field}" must be a string or null.`);
388
2407
  }
389
2408
 
390
- // src/commands/create/create-command.ts
391
- var import_meta2 = {};
392
- var createCommand = (0, import_citty.defineCommand)({
2409
+ // src/commands/deploy/deploy-command.ts
2410
+ var deployCommand = (0, import_citty2.defineCommand)({
393
2411
  meta: {
394
- name: "create",
395
- description: "Create a new Kora application"
2412
+ name: "deploy",
2413
+ description: "Deploy a Kora project to your selected platform"
396
2414
  },
397
2415
  args: {
398
- name: {
399
- type: "positional",
400
- description: "Project directory name",
401
- required: false
2416
+ platform: {
2417
+ type: "string",
2418
+ description: `Deployment platform (${DEPLOY_PLATFORMS.join(", ")})`
402
2419
  },
403
- template: {
2420
+ app: {
404
2421
  type: "string",
405
- description: "Project template (react-tailwind-sync, react-tailwind, react-sync, react-basic)"
2422
+ description: "Application name used by the target platform"
406
2423
  },
407
- pm: {
2424
+ region: {
408
2425
  type: "string",
409
- description: "Package manager (pnpm, npm, yarn, bun)"
2426
+ description: "Preferred deployment region (for example: iad, lhr, syd)"
410
2427
  },
411
- "skip-install": {
2428
+ prod: {
412
2429
  type: "boolean",
413
- description: "Skip installing dependencies",
2430
+ description: "Deploy to production environment",
414
2431
  default: false
415
2432
  },
416
- yes: {
2433
+ confirm: {
417
2434
  type: "boolean",
418
- alias: "y",
419
- description: "Accept all defaults (react-tailwind-sync + detected package manager)",
2435
+ description: "Non-interactive mode (fail fast on missing required data)",
420
2436
  default: false
421
2437
  },
422
- tailwind: {
423
- type: "boolean",
424
- description: "Use Tailwind CSS (use --no-tailwind to skip)"
425
- },
426
- sync: {
2438
+ reset: {
427
2439
  type: "boolean",
428
- description: "Include sync server (use --no-sync to skip)"
2440
+ description: "Delete .kora/deploy state and generated artifacts",
2441
+ default: false
429
2442
  }
430
2443
  },
431
- async run({ args }) {
2444
+ subCommands: {
2445
+ status: (0, import_citty2.defineCommand)({
2446
+ meta: {
2447
+ name: "status",
2448
+ description: "Show current deployment status"
2449
+ },
2450
+ async run() {
2451
+ const logger = createLogger();
2452
+ const projectRoot = await requireProjectRoot();
2453
+ const state = await readDeployState(projectRoot);
2454
+ if (!state) {
2455
+ logger.warn("No deployment state found. Run `kora deploy` first.");
2456
+ return;
2457
+ }
2458
+ logger.banner();
2459
+ logger.info(`Platform: ${state.platform}`);
2460
+ logger.step(`App: ${state.appName}`);
2461
+ logger.step(`Region: ${state.region ?? "n/a"}`);
2462
+ logger.step(`Last deployment: ${state.lastDeploymentId ?? "n/a"}`);
2463
+ const adapter = createDeployAdapter(state.platform);
2464
+ configureAdapterContext(adapter, {
2465
+ projectRoot,
2466
+ appName: state.appName,
2467
+ region: state.region
2468
+ });
2469
+ const adapterStatus = await adapter.status();
2470
+ logger.step(`Status: ${adapterStatus.state}`);
2471
+ logger.step(`Message: ${adapterStatus.message}`);
2472
+ logger.step(`Live URL: ${adapterStatus.liveUrl ?? state.liveUrl ?? "n/a"}`);
2473
+ logger.step(`Sync URL: ${state.syncUrl ?? "n/a"}`);
2474
+ }
2475
+ }),
2476
+ rollback: (0, import_citty2.defineCommand)({
2477
+ meta: {
2478
+ name: "rollback",
2479
+ description: "Rollback the current deployment"
2480
+ },
2481
+ args: {
2482
+ id: {
2483
+ type: "positional",
2484
+ description: "Optional deployment identifier",
2485
+ required: false
2486
+ }
2487
+ },
2488
+ async run({ args }) {
2489
+ const logger = createLogger();
2490
+ const projectRoot = await requireProjectRoot();
2491
+ const state = await readDeployState(projectRoot);
2492
+ if (!state) {
2493
+ logger.warn("No deployment state found. Run `kora deploy` first.");
2494
+ return;
2495
+ }
2496
+ const adapter = createDeployAdapter(state.platform);
2497
+ configureAdapterContext(adapter, {
2498
+ projectRoot,
2499
+ appName: state.appName,
2500
+ region: state.region
2501
+ });
2502
+ const deploymentId = typeof args.id === "string" && args.id.length > 0 ? args.id : state.lastDeploymentId ?? "latest";
2503
+ await adapter.rollback(deploymentId);
2504
+ logger.success(`Rolled back ${state.platform} deployment to ${deploymentId}.`);
2505
+ }
2506
+ }),
2507
+ logs: (0, import_citty2.defineCommand)({
2508
+ meta: {
2509
+ name: "logs",
2510
+ description: "Read deployment logs"
2511
+ },
2512
+ async run() {
2513
+ const logger = createLogger();
2514
+ const projectRoot = await requireProjectRoot();
2515
+ const state = await readDeployState(projectRoot);
2516
+ if (!state) {
2517
+ logger.warn("No deployment state found. Run `kora deploy` first.");
2518
+ return;
2519
+ }
2520
+ const adapter = createDeployAdapter(state.platform);
2521
+ configureAdapterContext(adapter, {
2522
+ projectRoot,
2523
+ appName: state.appName,
2524
+ region: state.region
2525
+ });
2526
+ const logLines = adapter.logs({ tail: 200 });
2527
+ let hasLines = false;
2528
+ for await (const line of logLines) {
2529
+ hasLines = true;
2530
+ logger.step(`[${line.level}] ${line.message}`);
2531
+ }
2532
+ if (!hasLines) {
2533
+ logger.warn(`No logs returned from ${state.platform}.`);
2534
+ }
2535
+ }
2536
+ })
2537
+ },
2538
+ async run({ args, rawArgs }) {
2539
+ const subCommandNames = ["status", "rollback", "logs"];
2540
+ if (rawArgs.some((arg) => subCommandNames.includes(arg))) {
2541
+ return;
2542
+ }
432
2543
  const logger = createLogger();
433
- logger.banner();
434
- const useDefaults = args.yes === true;
435
- const projectName = args.name || (useDefaults ? "my-kora-app" : await promptText("Project name", "my-kora-app"));
436
- if (!projectName) {
437
- logger.error("Project name is required");
438
- process.exitCode = 1;
2544
+ const prompts = createPromptClient();
2545
+ const projectRoot = await requireProjectRoot();
2546
+ if (args.reset === true) {
2547
+ await resetDeployState(projectRoot);
2548
+ logger.success("Reset .kora/deploy state.");
439
2549
  return;
440
2550
  }
441
- let template;
442
- if (args.template && isValidTemplate(args.template)) {
443
- template = args.template;
444
- } else if (useDefaults) {
445
- template = "react-tailwind-sync";
446
- } else if (args.tailwind !== void 0 || args.sync !== void 0) {
447
- template = resolveTemplateFromFlags(args.tailwind, args.sync);
448
- } else {
449
- template = await promptSelect(
450
- "Select a template:",
451
- TEMPLATE_INFO.map((t) => ({ label: `${t.label} \u2014 ${t.description}`, value: t.name }))
452
- );
2551
+ const existingState = await readDeployState(projectRoot);
2552
+ const confirmMode = args.confirm === true;
2553
+ const platform = await resolvePlatform({
2554
+ promptClient: prompts,
2555
+ platformArg: args.platform,
2556
+ storedPlatform: existingState?.platform,
2557
+ confirm: confirmMode
2558
+ });
2559
+ const appName = resolveAppName(args.app, existingState?.appName, projectRoot, confirmMode);
2560
+ const region = resolveRegion(args.region, existingState?.region, confirmMode);
2561
+ const deployDirectory = resolveDeployDirectory(projectRoot);
2562
+ const environment = args.prod === true ? "production" : "preview";
2563
+ const config = {
2564
+ projectRoot,
2565
+ appName,
2566
+ region,
2567
+ environment,
2568
+ confirm: confirmMode
2569
+ };
2570
+ const adapter = createDeployAdapter(platform);
2571
+ configureAdapterContext(adapter, {
2572
+ projectRoot,
2573
+ appName,
2574
+ region
2575
+ });
2576
+ logger.banner();
2577
+ logger.info(
2578
+ `Deploying to ${platform} (${appName}${region ? `, ${region}` : ""}, ${environment})`
2579
+ );
2580
+ if (confirmMode) {
2581
+ logger.step("Running in --confirm mode (non-interactive, fail-fast).");
453
2582
  }
454
- let pm;
455
- if (args.pm && isValidPackageManager(args.pm)) {
456
- pm = args.pm;
457
- } else if (useDefaults) {
458
- pm = detectPackageManager();
459
- } else {
460
- const detected = detectPackageManager();
461
- pm = await promptSelect(
462
- "Package manager:",
463
- PACKAGE_MANAGERS.map((p) => ({
464
- label: p === detected ? `${p} (detected)` : p,
465
- value: p
466
- }))
467
- );
2583
+ await writeDockerfileArtifact(deployDirectory, {
2584
+ nativeDependencies: {
2585
+ "better-sqlite3": "^11.0.0",
2586
+ "drizzle-orm": "^0.45.2"
2587
+ }
2588
+ });
2589
+ await writeDockerIgnoreArtifact(deployDirectory);
2590
+ const detected = await adapter.detect();
2591
+ if (!detected) {
2592
+ await adapter.install();
468
2593
  }
469
- const targetDir = (0, import_node_path3.resolve)(process.cwd(), projectName);
470
- if (await directoryExists(targetDir)) {
471
- throw new ProjectExistsError(projectName);
2594
+ await adapter.authenticate();
2595
+ const provisionResult = await adapter.provision(config);
2596
+ const artifacts = await adapter.build(config);
2597
+ const deployResult = await adapter.deploy(artifacts);
2598
+ if (existingState) {
2599
+ await updateDeployState(projectRoot, {
2600
+ platform,
2601
+ appName,
2602
+ region,
2603
+ projectRoot,
2604
+ liveUrl: deployResult.liveUrl,
2605
+ syncUrl: deployResult.syncUrl,
2606
+ databaseId: provisionResult.databaseId,
2607
+ lastDeploymentId: deployResult.deploymentId
2608
+ });
2609
+ } else {
2610
+ await writeDeployState(projectRoot, {
2611
+ platform,
2612
+ appName,
2613
+ region,
2614
+ projectRoot,
2615
+ liveUrl: deployResult.liveUrl,
2616
+ syncUrl: deployResult.syncUrl,
2617
+ databaseId: provisionResult.databaseId,
2618
+ lastDeploymentId: deployResult.deploymentId
2619
+ });
472
2620
  }
473
- const koraVersion = resolveKoraVersion();
474
- logger.step(`Creating ${projectName} with ${template} template...`);
475
- await scaffoldTemplate(template, targetDir, {
476
- projectName,
477
- packageManager: pm,
478
- koraVersion
479
- });
480
- logger.success("Project scaffolded");
481
- if (!args["skip-install"]) {
482
- logger.step("Installing dependencies...");
483
- try {
484
- (0, import_node_child_process2.execSync)(getInstallCommand(pm), { cwd: targetDir, stdio: "inherit" });
485
- logger.success("Dependencies installed");
486
- } catch {
487
- logger.warn("Failed to install dependencies. Run install manually.");
488
- }
2621
+ logger.success(`Deployment completed: ${deployResult.liveUrl}`);
2622
+ if (deployResult.syncUrl) {
2623
+ logger.step(`Sync endpoint: ${deployResult.syncUrl}`);
489
2624
  }
490
- logger.blank();
491
- logger.info("Done! Next steps:");
492
- logger.blank();
493
- logger.step(` cd ${projectName}`);
494
- logger.step(` ${getRunDevCommand(pm)}`);
495
- logger.blank();
496
2625
  }
497
2626
  });
498
- function isValidTemplate(value) {
499
- return TEMPLATES.includes(value);
2627
+ async function resolvePlatform(options) {
2628
+ if (typeof options.platformArg === "string") {
2629
+ if (!isDeployPlatform(options.platformArg)) {
2630
+ throw new Error(
2631
+ `Invalid --platform value "${options.platformArg}". Valid options: ${DEPLOY_PLATFORMS.join(", ")}`
2632
+ );
2633
+ }
2634
+ return options.platformArg;
2635
+ }
2636
+ if (options.storedPlatform) {
2637
+ return options.storedPlatform;
2638
+ }
2639
+ if (options.confirm || !isInteractiveTerminal()) {
2640
+ throw new Error(
2641
+ "Missing deploy platform in --confirm mode. Provide --platform or run an interactive deploy first."
2642
+ );
2643
+ }
2644
+ return await options.promptClient.select("Where do you want to deploy?", [
2645
+ {
2646
+ label: "Fly.io (recommended for sync apps)",
2647
+ value: "fly"
2648
+ },
2649
+ {
2650
+ label: "Railway",
2651
+ value: "railway"
2652
+ },
2653
+ {
2654
+ label: "Render",
2655
+ value: "render"
2656
+ },
2657
+ {
2658
+ label: "Docker (self-hosted)",
2659
+ value: "docker"
2660
+ },
2661
+ {
2662
+ label: "Kora Cloud (coming soon)",
2663
+ value: "kora-cloud"
2664
+ }
2665
+ ]);
500
2666
  }
501
- function isValidPackageManager(value) {
502
- return PACKAGE_MANAGERS.includes(value);
2667
+ function resolveAppName(argValue, storedValue, projectRoot, confirm) {
2668
+ if (typeof argValue === "string" && argValue.length > 0) {
2669
+ return sanitizeAppName(argValue);
2670
+ }
2671
+ if (storedValue && storedValue.length > 0) {
2672
+ return storedValue;
2673
+ }
2674
+ if (confirm) {
2675
+ throw new Error(
2676
+ "Missing app name in --confirm mode. Provide --app or run an interactive deploy first."
2677
+ );
2678
+ }
2679
+ return sanitizeAppName((0, import_node_path14.basename)(projectRoot));
503
2680
  }
504
- function resolveTemplateFromFlags(tailwind, sync) {
505
- const useTailwind = tailwind !== false;
506
- const useSync = sync !== false;
507
- if (useTailwind && useSync) return "react-tailwind-sync";
508
- if (useTailwind && !useSync) return "react-tailwind";
509
- if (!useTailwind && useSync) return "react-sync";
510
- return "react-basic";
2681
+ function resolveRegion(argValue, storedValue, confirm) {
2682
+ if (typeof argValue === "string" && argValue.length > 0) return argValue;
2683
+ if (storedValue !== void 0) return storedValue;
2684
+ if (confirm) {
2685
+ throw new Error(
2686
+ "Missing region in --confirm mode. Provide --region or run an interactive deploy first."
2687
+ );
2688
+ }
2689
+ return "iad";
511
2690
  }
512
- function resolveKoraVersion() {
513
- try {
514
- let dir = (0, import_node_path3.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url));
515
- for (let i = 0; i < 5; i++) {
516
- const pkgPath = (0, import_node_path3.resolve)(dir, "package.json");
517
- if ((0, import_node_fs2.existsSync)(pkgPath)) {
518
- const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
519
- if (pkg.name === "@korajs/cli") {
520
- if (pkg.version === "0.0.0") return "latest";
521
- const parts = pkg.version.split(".");
522
- return `^${parts[0]}.${parts[1]}.0`;
523
- }
524
- }
525
- dir = (0, import_node_path3.dirname)(dir);
526
- }
527
- return "latest";
528
- } catch {
529
- return "latest";
2691
+ function sanitizeAppName(value) {
2692
+ const normalized = value.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
2693
+ if (normalized.length === 0) {
2694
+ return "kora-app";
2695
+ }
2696
+ return normalized;
2697
+ }
2698
+ async function requireProjectRoot() {
2699
+ const projectRoot = await findProjectRoot();
2700
+ if (!projectRoot) {
2701
+ throw new InvalidProjectError(process.cwd());
2702
+ }
2703
+ return projectRoot;
2704
+ }
2705
+ function isInteractiveTerminal() {
2706
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
2707
+ }
2708
+ function configureAdapterContext(adapter, context) {
2709
+ if (hasContextSetter(adapter)) {
2710
+ adapter.setContext(context);
530
2711
  }
531
2712
  }
2713
+ function hasContextSetter(adapter) {
2714
+ return typeof adapter.setContext === "function";
2715
+ }
532
2716
 
533
2717
  // src/commands/dev/dev-command.ts
534
- var import_promises4 = require("fs/promises");
535
- var import_node_path6 = require("path");
536
- var import_node_path7 = require("path");
537
- var import_citty2 = require("citty");
2718
+ var import_promises13 = require("fs/promises");
2719
+ var import_node_path17 = require("path");
2720
+ var import_node_path18 = require("path");
2721
+ var import_citty3 = require("citty");
538
2722
 
539
2723
  // src/commands/dev/kora-config.ts
540
- var import_node_child_process3 = require("child_process");
541
- var import_promises3 = require("fs/promises");
542
- var import_node_path4 = require("path");
2724
+ var import_node_child_process6 = require("child_process");
2725
+ var import_promises12 = require("fs/promises");
2726
+ var import_node_path15 = require("path");
543
2727
  var import_node_url3 = require("url");
544
2728
  var CONFIG_CANDIDATES = [
545
2729
  "kora.config.ts",
@@ -552,7 +2736,7 @@ var CONFIG_CANDIDATES = [
552
2736
  async function loadKoraConfig(projectRoot) {
553
2737
  const configPath = await findKoraConfigFile(projectRoot);
554
2738
  if (!configPath) return null;
555
- const ext = (0, import_node_path4.extname)(configPath);
2739
+ const ext = (0, import_node_path15.extname)(configPath);
556
2740
  if (ext === ".ts" || ext === ".mts" || ext === ".cts") {
557
2741
  const loaded2 = await loadTypeScriptConfig(configPath, projectRoot);
558
2742
  return toConfigObject(loaded2);
@@ -562,9 +2746,9 @@ async function loadKoraConfig(projectRoot) {
562
2746
  }
563
2747
  async function findKoraConfigFile(projectRoot) {
564
2748
  for (const file of CONFIG_CANDIDATES) {
565
- const candidate = (0, import_node_path4.join)(projectRoot, file);
2749
+ const candidate = (0, import_node_path15.join)(projectRoot, file);
566
2750
  try {
567
- await (0, import_promises3.access)(candidate);
2751
+ await (0, import_promises12.access)(candidate);
568
2752
  return candidate;
569
2753
  } catch {
570
2754
  }
@@ -590,8 +2774,8 @@ async function loadTypeScriptConfig(configPath, projectRoot) {
590
2774
  }
591
2775
  }
592
2776
  async function runCommand(command, args, cwd) {
593
- return await new Promise((resolve7, reject) => {
594
- const child = (0, import_node_child_process3.spawn)(command, args, {
2777
+ return await new Promise((resolve9, reject) => {
2778
+ const child = (0, import_node_child_process6.spawn)(command, args, {
595
2779
  cwd,
596
2780
  stdio: ["ignore", "pipe", "pipe"],
597
2781
  env: process.env
@@ -609,7 +2793,7 @@ async function runCommand(command, args, cwd) {
609
2793
  });
610
2794
  child.on("exit", (code) => {
611
2795
  if (code === 0) {
612
- resolve7(stdout.trim());
2796
+ resolve9(stdout.trim());
613
2797
  return;
614
2798
  }
615
2799
  reject(new Error(`Failed to load kora config (exit ${code ?? "unknown"}): ${stderr.trim()}`));
@@ -628,7 +2812,7 @@ function toConfigObject(mod) {
628
2812
  }
629
2813
 
630
2814
  // src/commands/dev/process-manager.ts
631
- var import_node_child_process4 = require("child_process");
2815
+ var import_node_child_process7 = require("child_process");
632
2816
  var ProcessManager = class {
633
2817
  processes = /* @__PURE__ */ new Map();
634
2818
  spawn(config) {
@@ -637,12 +2821,12 @@ var ProcessManager = class {
637
2821
  env: { ...process.env, ...config.env },
638
2822
  stdio: ["ignore", "pipe", "pipe"]
639
2823
  };
640
- const child = (0, import_node_child_process4.spawn)(config.command, config.args, options);
2824
+ const child = (0, import_node_child_process7.spawn)(config.command, config.args, options);
641
2825
  let resolveExit;
642
2826
  const runningProcess = {
643
2827
  child,
644
- exitPromise: new Promise((resolve7) => {
645
- resolveExit = resolve7;
2828
+ exitPromise: new Promise((resolve9) => {
2829
+ resolveExit = resolve9;
646
2830
  }),
647
2831
  stdoutBuffer: "",
648
2832
  stderrBuffer: ""
@@ -709,15 +2893,15 @@ var ProcessManager = class {
709
2893
  }
710
2894
  };
711
2895
  function delay(ms) {
712
- return new Promise((resolve7) => {
713
- setTimeout(resolve7, ms);
2896
+ return new Promise((resolve9) => {
2897
+ setTimeout(resolve9, ms);
714
2898
  });
715
2899
  }
716
2900
 
717
2901
  // src/commands/dev/schema-watcher.ts
718
- var import_node_child_process5 = require("child_process");
719
- var import_node_fs3 = require("fs");
720
- var import_node_path5 = require("path");
2902
+ var import_node_child_process8 = require("child_process");
2903
+ var import_node_fs4 = require("fs");
2904
+ var import_node_path16 = require("path");
721
2905
  var SchemaWatcher = class {
722
2906
  constructor(config) {
723
2907
  this.config = config;
@@ -729,7 +2913,7 @@ var SchemaWatcher = class {
729
2913
  debounceTimer = null;
730
2914
  start() {
731
2915
  if (this.watcher) return;
732
- this.watcher = (0, import_node_fs3.watch)(this.config.schemaPath, () => {
2916
+ this.watcher = (0, import_node_fs4.watch)(this.config.schemaPath, () => {
733
2917
  this.scheduleRegeneration();
734
2918
  });
735
2919
  this.watcher.on("error", (error) => {
@@ -745,7 +2929,7 @@ var SchemaWatcher = class {
745
2929
  this.watcher = null;
746
2930
  }
747
2931
  async regenerate() {
748
- const koraBinJs = (0, import_node_path5.join)(this.config.projectRoot, "node_modules", "@korajs", "cli", "dist", "bin.js");
2932
+ const koraBinJs = (0, import_node_path16.join)(this.config.projectRoot, "node_modules", "@korajs", "cli", "dist", "bin.js");
749
2933
  const hasTsx = await hasTsxInstalled(this.config.projectRoot);
750
2934
  const command = process.execPath;
751
2935
  const args = hasTsx ? ["--import", "tsx", koraBinJs, "generate", "types", "--schema", this.config.schemaPath] : [koraBinJs, "generate", "types", "--schema", this.config.schemaPath];
@@ -765,8 +2949,8 @@ var SchemaWatcher = class {
765
2949
  }
766
2950
  };
767
2951
  async function spawnCommand(command, args, cwd) {
768
- await new Promise((resolve7, reject) => {
769
- const child = (0, import_node_child_process5.spawn)(command, args, {
2952
+ await new Promise((resolve9, reject) => {
2953
+ const child = (0, import_node_child_process8.spawn)(command, args, {
770
2954
  cwd,
771
2955
  stdio: ["ignore", "pipe", "pipe"],
772
2956
  env: process.env
@@ -782,7 +2966,7 @@ async function spawnCommand(command, args, cwd) {
782
2966
  });
783
2967
  child.on("exit", (code) => {
784
2968
  if (code === 0) {
785
- resolve7();
2969
+ resolve9();
786
2970
  return;
787
2971
  }
788
2972
  reject(new Error(`Type generation exited with code ${code ?? "unknown"}.`));
@@ -804,7 +2988,7 @@ function toError(error) {
804
2988
  }
805
2989
 
806
2990
  // src/commands/dev/dev-command.ts
807
- var devCommand = (0, import_citty2.defineCommand)({
2991
+ var devCommand = (0, import_citty3.defineCommand)({
808
2992
  meta: {
809
2993
  name: "dev",
810
2994
  description: "Start the Kora development environment"
@@ -844,7 +3028,7 @@ var devCommand = (0, import_citty2.defineCommand)({
844
3028
  const watchDebounceMs = typeof config?.dev?.watch === "object" && typeof config.dev.watch.debounceMs === "number" ? config.dev.watch.debounceMs : 300;
845
3029
  const viteEntryPoint = await resolveProjectBinaryEntryPoint(projectRoot, "vite", "vite");
846
3030
  if (!viteEntryPoint) {
847
- throw new DevServerError("vite", (0, import_node_path6.join)(projectRoot, "node_modules", ".bin", "vite"));
3031
+ throw new DevServerError("vite", (0, import_node_path17.join)(projectRoot, "node_modules", ".bin", "vite"));
848
3032
  }
849
3033
  const syncServerFile = await findSyncServerFile(projectRoot);
850
3034
  let managedSyncStore = normalizeManagedSyncStore(config, projectRoot);
@@ -859,8 +3043,8 @@ var devCommand = (0, import_citty2.defineCommand)({
859
3043
  }
860
3044
  }
861
3045
  if (shouldStartSync && syncServerFile === null && managedSyncStore) {
862
- const hasServerPackage = await fileExists(
863
- (0, import_node_path6.join)(projectRoot, "node_modules", "@korajs", "server", "package.json")
3046
+ const hasServerPackage = await fileExists2(
3047
+ (0, import_node_path17.join)(projectRoot, "node_modules", "@korajs", "server", "package.json")
864
3048
  );
865
3049
  if (!hasServerPackage) {
866
3050
  logger.warn(
@@ -877,8 +3061,8 @@ var devCommand = (0, import_citty2.defineCommand)({
877
3061
  }
878
3062
  let configuredSchemaPath = null;
879
3063
  if (typeof config?.schema === "string") {
880
- const candidate = (0, import_node_path7.resolve)(projectRoot, config.schema);
881
- if (await fileExists(candidate)) {
3064
+ const candidate = (0, import_node_path18.resolve)(projectRoot, config.schema);
3065
+ if (await fileExists2(candidate)) {
882
3066
  configuredSchemaPath = candidate;
883
3067
  } else {
884
3068
  logger.warn(`Configured schema file not found: ${candidate}. Falling back to auto-detection.`);
@@ -890,8 +3074,8 @@ var devCommand = (0, import_citty2.defineCommand)({
890
3074
  let schemaWatcher = null;
891
3075
  let shuttingDown = false;
892
3076
  let resolveFinished;
893
- const finished = new Promise((resolve7) => {
894
- resolveFinished = resolve7;
3077
+ const finished = new Promise((resolve9) => {
3078
+ resolveFinished = resolve9;
895
3079
  });
896
3080
  const onManagedProcessExit = () => {
897
3081
  if (!processManager.hasRunning() && !shuttingDown) {
@@ -989,19 +3173,19 @@ var devCommand = (0, import_citty2.defineCommand)({
989
3173
  }
990
3174
  });
991
3175
  async function findSyncServerFile(projectRoot) {
992
- const candidates = [(0, import_node_path6.join)(projectRoot, "server.ts"), (0, import_node_path6.join)(projectRoot, "server.js")];
3176
+ const candidates = [(0, import_node_path17.join)(projectRoot, "server.ts"), (0, import_node_path17.join)(projectRoot, "server.js")];
993
3177
  for (const candidate of candidates) {
994
3178
  try {
995
- await (0, import_promises4.access)(candidate);
3179
+ await (0, import_promises13.access)(candidate);
996
3180
  return candidate;
997
3181
  } catch {
998
3182
  }
999
3183
  }
1000
3184
  return null;
1001
3185
  }
1002
- async function fileExists(path) {
3186
+ async function fileExists2(path) {
1003
3187
  try {
1004
- await (0, import_promises4.access)(path);
3188
+ await (0, import_promises13.access)(path);
1005
3189
  return true;
1006
3190
  } catch {
1007
3191
  return false;
@@ -1013,7 +3197,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
1013
3197
  const store = sync.store;
1014
3198
  if (store === void 0) return { type: "memory" };
1015
3199
  if (store === "memory") return { type: "memory" };
1016
- if (store === "sqlite") return { type: "sqlite", filename: (0, import_node_path6.join)(projectRoot, "kora-sync.db") };
3200
+ if (store === "sqlite") return { type: "sqlite", filename: (0, import_node_path17.join)(projectRoot, "kora-sync.db") };
1017
3201
  if (store === "postgres") {
1018
3202
  const connectionString = process.env.DATABASE_URL;
1019
3203
  if (!connectionString) return null;
@@ -1022,7 +3206,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
1022
3206
  if (typeof store === "object" && store !== null) {
1023
3207
  if (store.type === "memory") return { type: "memory" };
1024
3208
  if (store.type === "sqlite") {
1025
- const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0, import_node_path7.resolve)(projectRoot, store.filename) : (0, import_node_path6.join)(projectRoot, "kora-sync.db");
3209
+ const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0, import_node_path18.resolve)(projectRoot, store.filename) : (0, import_node_path17.join)(projectRoot, "kora-sync.db");
1026
3210
  return { type: "sqlite", filename };
1027
3211
  }
1028
3212
  if (store.type === "postgres" && typeof store.connectionString === "string") {
@@ -1085,9 +3269,9 @@ await new Promise(() => {});
1085
3269
  `;
1086
3270
 
1087
3271
  // src/commands/generate/generate-command.ts
1088
- var import_promises5 = require("fs/promises");
1089
- var import_node_path8 = require("path");
1090
- var import_citty3 = require("citty");
3272
+ var import_promises14 = require("fs/promises");
3273
+ var import_node_path19 = require("path");
3274
+ var import_citty4 = require("citty");
1091
3275
 
1092
3276
  // src/commands/generate/type-generator.ts
1093
3277
  function generateTypes(schema) {
@@ -1187,13 +3371,13 @@ function toPascalCase(name) {
1187
3371
  }
1188
3372
 
1189
3373
  // src/commands/generate/generate-command.ts
1190
- var generateCommand = (0, import_citty3.defineCommand)({
3374
+ var generateCommand = (0, import_citty4.defineCommand)({
1191
3375
  meta: {
1192
3376
  name: "generate",
1193
3377
  description: "Generate code from your Kora schema"
1194
3378
  },
1195
3379
  subCommands: {
1196
- types: (0, import_citty3.defineCommand)({
3380
+ types: (0, import_citty4.defineCommand)({
1197
3381
  meta: {
1198
3382
  name: "types",
1199
3383
  description: "Generate TypeScript types from your schema"
@@ -1217,7 +3401,7 @@ var generateCommand = (0, import_citty3.defineCommand)({
1217
3401
  }
1218
3402
  let schemaPath;
1219
3403
  if (args.schema && typeof args.schema === "string") {
1220
- schemaPath = (0, import_node_path8.resolve)(args.schema);
3404
+ schemaPath = (0, import_node_path19.resolve)(args.schema);
1221
3405
  } else {
1222
3406
  const found = await findSchemaFile(projectRoot);
1223
3407
  if (!found) {
@@ -1240,9 +3424,9 @@ var generateCommand = (0, import_citty3.defineCommand)({
1240
3424
  }
1241
3425
  const output = generateTypes(schema);
1242
3426
  const outputFile = typeof args.output === "string" ? args.output : "kora/generated/types.ts";
1243
- const outputPath = (0, import_node_path8.resolve)(projectRoot, outputFile);
1244
- await (0, import_promises5.mkdir)((0, import_node_path8.dirname)(outputPath), { recursive: true });
1245
- await (0, import_promises5.writeFile)(outputPath, output, "utf-8");
3427
+ const outputPath = (0, import_node_path19.resolve)(projectRoot, outputFile);
3428
+ await (0, import_promises14.mkdir)((0, import_node_path19.dirname)(outputPath), { recursive: true });
3429
+ await (0, import_promises14.writeFile)(outputPath, output, "utf-8");
1246
3430
  logger.success(`Generated types at ${outputPath}`);
1247
3431
  }
1248
3432
  })
@@ -1262,9 +3446,9 @@ function isSchemaDefinition(value) {
1262
3446
  }
1263
3447
 
1264
3448
  // src/commands/migrate/migrate-command.ts
1265
- var import_promises6 = require("fs/promises");
1266
- var import_node_path10 = require("path");
1267
- var import_citty4 = require("citty");
3449
+ var import_promises15 = require("fs/promises");
3450
+ var import_node_path21 = require("path");
3451
+ var import_citty5 = require("citty");
1268
3452
 
1269
3453
  // src/commands/migrate/migration-generator.ts
1270
3454
  var import_core2 = require("@korajs/core");
@@ -1832,11 +4016,11 @@ async function loadPostgresModule() {
1832
4016
  }
1833
4017
 
1834
4018
  // src/commands/migrate/schema-loader.ts
1835
- var import_node_child_process6 = require("child_process");
1836
- var import_node_path9 = require("path");
4019
+ var import_node_child_process9 = require("child_process");
4020
+ var import_node_path20 = require("path");
1837
4021
  var import_node_url4 = require("url");
1838
4022
  async function loadSchemaDefinition(schemaPath, projectRoot) {
1839
- const ext = (0, import_node_path9.extname)(schemaPath);
4023
+ const ext = (0, import_node_path20.extname)(schemaPath);
1840
4024
  const moduleValue = ext === ".ts" || ext === ".mts" || ext === ".cts" ? await loadTypeScriptModule(schemaPath, projectRoot) : await import(`${(0, import_node_url4.pathToFileURL)(schemaPath).href}?t=${Date.now()}-${Math.random()}`);
1841
4025
  return extractSchema2(moduleValue);
1842
4026
  }
@@ -1875,8 +4059,8 @@ async function loadTypeScriptModule(schemaPath, projectRoot) {
1875
4059
  }
1876
4060
  }
1877
4061
  async function runCommand2(command, args, cwd) {
1878
- return await new Promise((resolve7, reject) => {
1879
- const child = (0, import_node_child_process6.spawn)(command, args, {
4062
+ return await new Promise((resolve9, reject) => {
4063
+ const child = (0, import_node_child_process9.spawn)(command, args, {
1880
4064
  cwd,
1881
4065
  stdio: ["ignore", "pipe", "pipe"],
1882
4066
  env: process.env
@@ -1894,7 +4078,7 @@ async function runCommand2(command, args, cwd) {
1894
4078
  });
1895
4079
  child.on("exit", (code) => {
1896
4080
  if (code === 0) {
1897
- resolve7(stdout.trim());
4081
+ resolve9(stdout.trim());
1898
4082
  return;
1899
4083
  }
1900
4084
  reject(
@@ -1907,7 +4091,7 @@ async function runCommand2(command, args, cwd) {
1907
4091
  // src/commands/migrate/migrate-command.ts
1908
4092
  var SNAPSHOT_PATH = "kora/schema.snapshot.json";
1909
4093
  var MIGRATIONS_DIR = "kora/migrations";
1910
- var migrateCommand = (0, import_citty4.defineCommand)({
4094
+ var migrateCommand = (0, import_citty5.defineCommand)({
1911
4095
  meta: {
1912
4096
  name: "migrate",
1913
4097
  description: "Detect schema changes and generate/apply migrations"
@@ -1949,12 +4133,12 @@ var migrateCommand = (0, import_citty4.defineCommand)({
1949
4133
  throw new InvalidProjectError(process.cwd());
1950
4134
  }
1951
4135
  const config = await loadKoraConfig(projectRoot);
1952
- const resolvedSchemaPath = typeof args.schema === "string" ? (0, import_node_path10.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path10.resolve)(projectRoot, config.schema) : await findSchemaFile(projectRoot);
4136
+ const resolvedSchemaPath = typeof args.schema === "string" ? (0, import_node_path21.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path21.resolve)(projectRoot, config.schema) : await findSchemaFile(projectRoot);
1953
4137
  if (!resolvedSchemaPath) {
1954
4138
  throw new SchemaNotFoundError(["src/schema.ts", "schema.ts", "src/schema.js", "schema.js"]);
1955
4139
  }
1956
4140
  const currentSchema = await loadSchemaDefinition(resolvedSchemaPath, projectRoot);
1957
- const snapshotFile = (0, import_node_path10.join)(projectRoot, SNAPSHOT_PATH);
4141
+ const snapshotFile = (0, import_node_path21.join)(projectRoot, SNAPSHOT_PATH);
1958
4142
  const previousSchema = await readSchemaSnapshot(snapshotFile);
1959
4143
  if (!previousSchema) {
1960
4144
  if (args["dry-run"] === true) {
@@ -1967,7 +4151,7 @@ var migrateCommand = (0, import_citty4.defineCommand)({
1967
4151
  return;
1968
4152
  }
1969
4153
  const diff = diffSchemas(previousSchema, currentSchema);
1970
- const outputDir = typeof args["output-dir"] === "string" ? (0, import_node_path10.resolve)(projectRoot, args["output-dir"]) : (0, import_node_path10.resolve)(projectRoot, MIGRATIONS_DIR);
4154
+ const outputDir = typeof args["output-dir"] === "string" ? (0, import_node_path21.resolve)(projectRoot, args["output-dir"]) : (0, import_node_path21.resolve)(projectRoot, MIGRATIONS_DIR);
1971
4155
  if (!diff.hasChanges) {
1972
4156
  logger.success("No schema changes detected.");
1973
4157
  if (args.apply === true) {
@@ -2019,7 +4203,7 @@ var migrateCommand = (0, import_citty4.defineCommand)({
2019
4203
  logger.warn("Dry run enabled: no files written, no migrations applied.");
2020
4204
  return;
2021
4205
  }
2022
- await (0, import_promises6.mkdir)(outputDir, { recursive: true });
4206
+ await (0, import_promises15.mkdir)(outputDir, { recursive: true });
2023
4207
  const migrationPath = await writeMigrationFile(outputDir, diff.fromVersion, diff.toVersion, generated);
2024
4208
  await writeSchemaSnapshot(snapshotFile, currentSchema);
2025
4209
  logger.blank();
@@ -2050,7 +4234,7 @@ var migrateCommand = (0, import_citty4.defineCommand)({
2050
4234
  });
2051
4235
  async function readSchemaSnapshot(path) {
2052
4236
  try {
2053
- const content = await (0, import_promises6.readFile)(path, "utf-8");
4237
+ const content = await (0, import_promises15.readFile)(path, "utf-8");
2054
4238
  return JSON.parse(content);
2055
4239
  } catch {
2056
4240
  return null;
@@ -2060,26 +4244,26 @@ async function confirmBreakingChanges(force) {
2060
4244
  if (force) {
2061
4245
  return true;
2062
4246
  }
2063
- if (!isInteractiveTerminal()) {
4247
+ if (!isInteractiveTerminal2()) {
2064
4248
  throw new Error(
2065
4249
  "Breaking schema changes require confirmation. Re-run with --force to continue or --dry-run to preview."
2066
4250
  );
2067
4251
  }
2068
4252
  return await promptConfirm("Continue and generate a breaking migration?", false);
2069
4253
  }
2070
- function isInteractiveTerminal() {
4254
+ function isInteractiveTerminal2() {
2071
4255
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
2072
4256
  }
2073
4257
  async function writeSchemaSnapshot(path, schema) {
2074
- await (0, import_promises6.mkdir)((0, import_node_path10.dirname)(path), { recursive: true });
2075
- await (0, import_promises6.writeFile)(path, `${JSON.stringify(schema, null, 2)}
4258
+ await (0, import_promises15.mkdir)((0, import_node_path21.dirname)(path), { recursive: true });
4259
+ await (0, import_promises15.writeFile)(path, `${JSON.stringify(schema, null, 2)}
2076
4260
  `, "utf-8");
2077
4261
  }
2078
4262
  async function writeMigrationFile(outputDir, fromVersion, toVersion, generated) {
2079
- const existing = await (0, import_promises6.readdir)(outputDir).catch(() => []);
4263
+ const existing = await (0, import_promises15.readdir)(outputDir).catch(() => []);
2080
4264
  const sequence = existing.filter((file) => /^\d{3}-/.test(file)).length + 1;
2081
4265
  const filename = `${String(sequence).padStart(3, "0")}-v${fromVersion}-to-v${toVersion}.ts`;
2082
- const path = (0, import_node_path10.join)(outputDir, filename);
4266
+ const path = (0, import_node_path21.join)(outputDir, filename);
2083
4267
  const migrationId = filename.replace(/\.ts$/, "");
2084
4268
  const fileContent = [
2085
4269
  `export const up = ${JSON.stringify(generated.up, null, 2)} as const`,
@@ -2091,8 +4275,8 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
2091
4275
  `export const containsBreakingChanges = ${generated.containsBreakingChanges}`,
2092
4276
  ""
2093
4277
  ].join("\n");
2094
- await (0, import_promises6.writeFile)(path, fileContent, "utf-8");
2095
- await writeMigrationManifest((0, import_node_path10.join)(outputDir, `${migrationId}.json`), {
4278
+ await (0, import_promises15.writeFile)(path, fileContent, "utf-8");
4279
+ await writeMigrationManifest((0, import_node_path21.join)(outputDir, `${migrationId}.json`), {
2096
4280
  id: migrationId,
2097
4281
  fromVersion,
2098
4282
  toVersion,
@@ -2104,22 +4288,22 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
2104
4288
  return path;
2105
4289
  }
2106
4290
  async function writeMigrationManifest(path, manifest) {
2107
- await (0, import_promises6.writeFile)(path, `${JSON.stringify(manifest, null, 2)}
4291
+ await (0, import_promises15.writeFile)(path, `${JSON.stringify(manifest, null, 2)}
2108
4292
  `, "utf-8");
2109
4293
  }
2110
4294
  async function listMigrationManifests(outputDir) {
2111
- const files = await (0, import_promises6.readdir)(outputDir).catch(() => []);
4295
+ const files = await (0, import_promises15.readdir)(outputDir).catch(() => []);
2112
4296
  const migrationFiles = files.filter((file) => /^\d{3}-.*\.ts$/.test(file)).sort((left, right) => left.localeCompare(right));
2113
4297
  const manifests = [];
2114
4298
  for (const file of migrationFiles) {
2115
4299
  const id = file.replace(/\.ts$/, "");
2116
- const manifestPath = (0, import_node_path10.join)(outputDir, `${id}.json`);
4300
+ const manifestPath = (0, import_node_path21.join)(outputDir, `${id}.json`);
2117
4301
  const jsonManifest = await readMigrationManifest(manifestPath);
2118
4302
  if (jsonManifest) {
2119
4303
  manifests.push({ ...jsonManifest, id });
2120
4304
  continue;
2121
4305
  }
2122
- const migrationPath = (0, import_node_path10.join)(outputDir, file);
4306
+ const migrationPath = (0, import_node_path21.join)(outputDir, file);
2123
4307
  const sourceManifest = await readMigrationManifestFromSource(migrationPath, id);
2124
4308
  manifests.push(sourceManifest);
2125
4309
  }
@@ -2127,7 +4311,7 @@ async function listMigrationManifests(outputDir) {
2127
4311
  }
2128
4312
  async function readMigrationManifest(path) {
2129
4313
  try {
2130
- const content = await (0, import_promises6.readFile)(path, "utf-8");
4314
+ const content = await (0, import_promises15.readFile)(path, "utf-8");
2131
4315
  return JSON.parse(content);
2132
4316
  } catch (error) {
2133
4317
  const code = error.code;
@@ -2138,7 +4322,7 @@ async function readMigrationManifest(path) {
2138
4322
  }
2139
4323
  }
2140
4324
  async function readMigrationManifestFromSource(path, id) {
2141
- const content = await (0, import_promises6.readFile)(path, "utf-8");
4325
+ const content = await (0, import_promises15.readFile)(path, "utf-8");
2142
4326
  const versions = parseVersionsFromMigrationId(id);
2143
4327
  return {
2144
4328
  id,
@@ -2195,18 +4379,18 @@ function parseLiteralExport(source, exportName) {
2195
4379
  }
2196
4380
  function resolveSqliteApplyPath(dbArg, projectRoot, config) {
2197
4381
  if (typeof dbArg === "string") {
2198
- return (0, import_node_path10.resolve)(projectRoot, dbArg);
4382
+ return (0, import_node_path21.resolve)(projectRoot, dbArg);
2199
4383
  }
2200
4384
  const sync = config?.dev?.sync;
2201
4385
  if (typeof sync === "object" && sync !== null) {
2202
4386
  if (sync.store === "sqlite") {
2203
- return (0, import_node_path10.join)(projectRoot, "kora-sync.db");
4387
+ return (0, import_node_path21.join)(projectRoot, "kora-sync.db");
2204
4388
  }
2205
4389
  if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "sqlite") {
2206
4390
  if (typeof sync.store.filename === "string" && sync.store.filename.length > 0) {
2207
- return (0, import_node_path10.resolve)(projectRoot, sync.store.filename);
4391
+ return (0, import_node_path21.resolve)(projectRoot, sync.store.filename);
2208
4392
  }
2209
- return (0, import_node_path10.join)(projectRoot, "kora-sync.db");
4393
+ return (0, import_node_path21.join)(projectRoot, "kora-sync.db");
2210
4394
  }
2211
4395
  }
2212
4396
  return void 0;
@@ -2224,7 +4408,7 @@ function resolvePostgresConnectionString(config) {
2224
4408
  }
2225
4409
 
2226
4410
  // src/bin.ts
2227
- var main = (0, import_citty5.defineCommand)({
4411
+ var main = (0, import_citty6.defineCommand)({
2228
4412
  meta: {
2229
4413
  name: "kora",
2230
4414
  description: "Kora.js \u2014 Offline-first application framework"
@@ -2232,9 +4416,10 @@ var main = (0, import_citty5.defineCommand)({
2232
4416
  subCommands: {
2233
4417
  create: createCommand,
2234
4418
  dev: devCommand,
4419
+ deploy: deployCommand,
2235
4420
  generate: generateCommand,
2236
4421
  migrate: migrateCommand
2237
4422
  }
2238
4423
  });
2239
- (0, import_citty5.runMain)(main);
4424
+ (0, import_citty6.runMain)(main);
2240
4425
  //# sourceMappingURL=bin.cjs.map