@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/create.cjs CHANGED
@@ -1,4 +1,26 @@
1
1
  "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
2
24
 
3
25
  // src/create.ts
4
26
  var import_citty2 = require("citty");
@@ -6,7 +28,7 @@ var import_citty2 = require("citty");
6
28
  // src/commands/create/create-command.ts
7
29
  var import_node_child_process2 = require("child_process");
8
30
  var import_node_fs2 = require("fs");
9
- var import_node_path3 = require("path");
31
+ var import_node_path5 = require("path");
10
32
  var import_node_url2 = require("url");
11
33
  var import_citty = require("citty");
12
34
 
@@ -25,6 +47,209 @@ var ProjectExistsError = class extends import_core.KoraError {
25
47
  directory;
26
48
  };
27
49
 
50
+ // src/prompts/preferences.ts
51
+ var import_conf = __toESM(require("conf"), 1);
52
+ var DEFAULT_PREFERENCES = {
53
+ framework: "react",
54
+ tailwind: true,
55
+ sync: true,
56
+ db: "sqlite",
57
+ dbProvider: "none",
58
+ auth: "none",
59
+ packageManager: "pnpm"
60
+ };
61
+ var PREFERENCES_KEY = "create.defaults";
62
+ var PreferenceStore = class {
63
+ store;
64
+ constructor() {
65
+ this.store = new import_conf.default({
66
+ projectName: "korajs-cli"
67
+ });
68
+ }
69
+ getCreatePreferences() {
70
+ return this.store.get(PREFERENCES_KEY) ?? null;
71
+ }
72
+ saveCreatePreferences(preferences) {
73
+ this.store.set(PREFERENCES_KEY, preferences);
74
+ }
75
+ clearCreatePreferences() {
76
+ this.store.delete(PREFERENCES_KEY);
77
+ }
78
+ };
79
+ function getCreatePreferencesOrDefault(store) {
80
+ return store.getCreatePreferences() ?? DEFAULT_PREFERENCES;
81
+ }
82
+ function getDefaultCreatePreferences() {
83
+ return { ...DEFAULT_PREFERENCES };
84
+ }
85
+
86
+ // src/prompts/prompt-client.ts
87
+ var import_prompts = require("@clack/prompts");
88
+
89
+ // src/utils/prompt.ts
90
+ var import_node_readline = require("readline");
91
+ function promptText(message, defaultValue, options) {
92
+ return new Promise((resolve5) => {
93
+ const rl = createReadline(options);
94
+ const suffix = defaultValue !== void 0 ? ` (${defaultValue})` : "";
95
+ rl.question(` ? ${message}${suffix}: `, (answer) => {
96
+ rl.close();
97
+ const trimmed = answer.trim();
98
+ resolve5(trimmed || defaultValue || "");
99
+ });
100
+ });
101
+ }
102
+ function promptSelect(message, choices, options) {
103
+ return new Promise((resolve5) => {
104
+ const rl = createReadline(options);
105
+ const out = options?.output ?? process.stdout;
106
+ out.write(` ? ${message}
107
+ `);
108
+ for (let i = 0; i < choices.length; i++) {
109
+ const choice = choices[i];
110
+ if (choice) {
111
+ out.write(` ${i + 1}) ${choice.label}
112
+ `);
113
+ }
114
+ }
115
+ const ask = () => {
116
+ rl.question(" > ", (answer) => {
117
+ const index = Number.parseInt(answer.trim(), 10) - 1;
118
+ const selected = choices[index];
119
+ if (selected) {
120
+ rl.close();
121
+ resolve5(selected.value);
122
+ } else {
123
+ out.write(` Please enter a number between 1 and ${choices.length}
124
+ `);
125
+ ask();
126
+ }
127
+ });
128
+ };
129
+ ask();
130
+ });
131
+ }
132
+ function promptConfirm(message, defaultValue = false, options) {
133
+ return new Promise((resolve5) => {
134
+ const rl = createReadline(options);
135
+ const suffix = defaultValue ? "Y/n" : "y/N";
136
+ const ask = () => {
137
+ rl.question(` ? ${message} (${suffix}): `, (answer) => {
138
+ const normalized = answer.trim().toLowerCase();
139
+ if (normalized.length === 0) {
140
+ rl.close();
141
+ resolve5(defaultValue);
142
+ return;
143
+ }
144
+ if (normalized === "y" || normalized === "yes") {
145
+ rl.close();
146
+ resolve5(true);
147
+ return;
148
+ }
149
+ if (normalized === "n" || normalized === "no") {
150
+ rl.close();
151
+ resolve5(false);
152
+ return;
153
+ }
154
+ (options?.output ?? process.stdout).write(" Please answer with y or n\n");
155
+ ask();
156
+ });
157
+ };
158
+ ask();
159
+ });
160
+ }
161
+ function createReadline(options) {
162
+ return (0, import_node_readline.createInterface)({
163
+ input: options?.input ?? process.stdin,
164
+ output: options?.output ?? process.stdout
165
+ });
166
+ }
167
+
168
+ // src/prompts/prompt-client.ts
169
+ var PromptCancelledError = class extends Error {
170
+ constructor(message = "Prompt cancelled by user") {
171
+ super(message);
172
+ this.name = "PromptCancelledError";
173
+ }
174
+ };
175
+ var ReadlinePromptClient = class {
176
+ async text(message, defaultValue) {
177
+ return promptText(message, defaultValue);
178
+ }
179
+ async select(message, options) {
180
+ return promptSelect(
181
+ message,
182
+ options.filter((option) => option.disabled !== true).map((option) => ({ label: option.label, value: option.value }))
183
+ );
184
+ }
185
+ async confirm(message, defaultValue = false) {
186
+ return promptConfirm(message, defaultValue);
187
+ }
188
+ intro(message) {
189
+ void message;
190
+ }
191
+ outro(message) {
192
+ void message;
193
+ }
194
+ };
195
+ function createPromptClient() {
196
+ const canUseInteractiveClack = typeof process !== "undefined" && process.stdin.isTTY && process.stdout.isTTY;
197
+ if (canUseInteractiveClack) {
198
+ return new ClackPromptClient();
199
+ }
200
+ return new ReadlinePromptClient();
201
+ }
202
+ var ClackPromptClient = class {
203
+ async text(message, defaultValue) {
204
+ const result = await (0, import_prompts.text)({
205
+ message,
206
+ placeholder: defaultValue,
207
+ defaultValue
208
+ });
209
+ if ((0, import_prompts.isCancel)(result)) {
210
+ (0, import_prompts.cancel)("Operation cancelled.");
211
+ throw new PromptCancelledError();
212
+ }
213
+ const value = result.trim();
214
+ if (value.length > 0) return value;
215
+ return defaultValue ?? "";
216
+ }
217
+ async select(message, options) {
218
+ const mappedOptions = options.map((option) => ({
219
+ label: option.label,
220
+ value: option.value,
221
+ hint: option.hint,
222
+ disabled: option.disabled
223
+ }));
224
+ const result = await (0, import_prompts.select)({
225
+ message,
226
+ options: mappedOptions
227
+ });
228
+ if ((0, import_prompts.isCancel)(result)) {
229
+ (0, import_prompts.cancel)("Operation cancelled.");
230
+ throw new PromptCancelledError();
231
+ }
232
+ return result;
233
+ }
234
+ async confirm(message, defaultValue = false) {
235
+ const result = await (0, import_prompts.confirm)({
236
+ message,
237
+ initialValue: defaultValue
238
+ });
239
+ if ((0, import_prompts.isCancel)(result)) {
240
+ (0, import_prompts.cancel)("Operation cancelled.");
241
+ throw new PromptCancelledError();
242
+ }
243
+ return result;
244
+ }
245
+ intro(message) {
246
+ (0, import_prompts.intro)(message);
247
+ }
248
+ outro(message) {
249
+ (0, import_prompts.outro)(message);
250
+ }
251
+ };
252
+
28
253
  // src/types.ts
29
254
  var PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
30
255
  var TEMPLATES = [
@@ -33,28 +258,6 @@ var TEMPLATES = [
33
258
  "react-sync",
34
259
  "react-basic"
35
260
  ];
36
- var TEMPLATE_INFO = [
37
- {
38
- name: "react-tailwind-sync",
39
- label: "React + Tailwind (with sync)",
40
- description: "Polished dark-themed app with Tailwind CSS and sync server (Recommended)"
41
- },
42
- {
43
- name: "react-tailwind",
44
- label: "React + Tailwind (local-only)",
45
- description: "Polished dark-themed app with Tailwind CSS \u2014 no sync server"
46
- },
47
- {
48
- name: "react-sync",
49
- label: "React + CSS (with sync)",
50
- description: "Clean CSS app with sync server included"
51
- },
52
- {
53
- name: "react-basic",
54
- label: "React + CSS (local-only)",
55
- description: "Clean CSS app \u2014 no sync server"
56
- }
57
- ];
58
261
 
59
262
  // src/utils/fs-helpers.ts
60
263
  var import_promises = require("fs/promises");
@@ -128,60 +331,421 @@ function getRunDevCommand(pm) {
128
331
  return `${pm} dev`;
129
332
  }
130
333
 
131
- // src/utils/prompt.ts
132
- var import_node_readline = require("readline");
133
- function promptText(message, defaultValue, options) {
134
- return new Promise((resolve4) => {
135
- const rl = createReadline(options);
136
- const suffix = defaultValue !== void 0 ? ` (${defaultValue})` : "";
137
- rl.question(` ? ${message}${suffix}: `, (answer) => {
138
- rl.close();
139
- const trimmed = answer.trim();
140
- resolve4(trimmed || defaultValue || "");
141
- });
142
- });
334
+ // src/commands/create/environment-detection.ts
335
+ var import_promises2 = require("fs/promises");
336
+ var import_node_path2 = require("path");
337
+ function detectEditorFromEnvironment(env = process.env) {
338
+ const termProgram = String(env.TERM_PROGRAM ?? "").toLowerCase();
339
+ const editorValue = `${String(env.EDITOR ?? "")} ${String(env.VISUAL ?? "")}`.toLowerCase();
340
+ if (termProgram.includes("cursor") || env.CURSOR_TRACE_ID !== void 0 || env.CURSOR_SESSION_ID !== void 0 || editorValue.includes("cursor")) {
341
+ return { editor: "cursor", source: "env" };
342
+ }
343
+ if (termProgram.includes("windsurf") || env.WINDSURF_SESSION_ID !== void 0 || editorValue.includes("windsurf")) {
344
+ return { editor: "windsurf", source: "env" };
345
+ }
346
+ 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")) {
347
+ return { editor: "vscode", source: "env" };
348
+ }
349
+ if (termProgram.includes("zed") || env.ZED_TERM !== void 0 || editorValue.includes("zed")) {
350
+ return { editor: "zed", source: "env" };
351
+ }
352
+ return { editor: "unknown", source: "none" };
143
353
  }
144
- function promptSelect(message, choices, options) {
145
- return new Promise((resolve4) => {
146
- const rl = createReadline(options);
147
- const out = options?.output ?? process.stdout;
148
- out.write(` ? ${message}
149
- `);
150
- for (let i = 0; i < choices.length; i++) {
151
- const choice = choices[i];
152
- if (choice) {
153
- out.write(` ${i + 1}) ${choice.label}
154
- `);
155
- }
354
+ async function detectGitContext(startDir) {
355
+ const root = await findNearestAncestorWithEntry(startDir, ".git");
356
+ return {
357
+ hasRepository: root !== null,
358
+ repositoryRoot: root
359
+ };
360
+ }
361
+ async function detectMonorepoContext(startDir) {
362
+ let current = (0, import_node_path2.resolve)(startDir);
363
+ for (; ; ) {
364
+ if (await fileExists((0, import_node_path2.join)(current, "pnpm-workspace.yaml"))) {
365
+ return { isMonorepo: true, root: current, kind: "pnpm-workspace" };
156
366
  }
157
- const ask = () => {
158
- rl.question(" > ", (answer) => {
159
- const index = Number.parseInt(answer.trim(), 10) - 1;
160
- const selected = choices[index];
161
- if (selected) {
162
- rl.close();
163
- resolve4(selected.value);
164
- } else {
165
- out.write(` Please enter a number between 1 and ${choices.length}
166
- `);
167
- ask();
367
+ if (await fileExists((0, import_node_path2.join)(current, "turbo.json"))) {
368
+ return { isMonorepo: true, root: current, kind: "turborepo" };
369
+ }
370
+ const packageJsonPath = (0, import_node_path2.join)(current, "package.json");
371
+ if (await fileExists(packageJsonPath)) {
372
+ try {
373
+ const packageJsonRaw = await (0, import_promises2.readFile)(packageJsonPath, "utf-8");
374
+ const parsed = JSON.parse(packageJsonRaw);
375
+ if (Array.isArray(parsed.workspaces) || isNpmWorkspaceObject(parsed.workspaces)) {
376
+ return { isMonorepo: true, root: current, kind: "npm-workspaces" };
168
377
  }
169
- });
170
- };
171
- ask();
378
+ } catch {
379
+ }
380
+ }
381
+ const parent = (0, import_node_path2.dirname)(current);
382
+ if (parent === current) {
383
+ return { isMonorepo: false, root: null, kind: "none" };
384
+ }
385
+ current = parent;
386
+ }
387
+ }
388
+ async function applyEditorWorkspacePreset(params) {
389
+ const { targetDir, editor } = params;
390
+ if (editor !== "vscode" && editor !== "cursor" && editor !== "windsurf") {
391
+ return { applied: false, filePath: null };
392
+ }
393
+ const vscodeDir = (0, import_node_path2.join)(targetDir, ".vscode");
394
+ const extensionsPath = (0, import_node_path2.join)(vscodeDir, "extensions.json");
395
+ await (0, import_promises2.mkdir)(vscodeDir, { recursive: true });
396
+ const recommendations = ["korajs.kora-devtools"];
397
+ const existing = await readJsonObject(extensionsPath);
398
+ const existingRecommendations = Array.isArray(existing?.recommendations) ? existing.recommendations.filter((item) => typeof item === "string") : [];
399
+ const mergedRecommendations = dedupeStrings([...existingRecommendations, ...recommendations]);
400
+ const next = {
401
+ recommendations: mergedRecommendations
402
+ };
403
+ await (0, import_promises2.writeFile)(extensionsPath, `${JSON.stringify(next, null, 2)}
404
+ `, "utf-8");
405
+ return { applied: true, filePath: extensionsPath };
406
+ }
407
+ function resolveMonorepoTargetDirectory(monorepoRoot, projectName) {
408
+ return (0, import_node_path2.join)(monorepoRoot, "packages", projectName);
409
+ }
410
+ function dedupeStrings(values) {
411
+ return Array.from(new Set(values));
412
+ }
413
+ async function readJsonObject(path) {
414
+ try {
415
+ const content = await (0, import_promises2.readFile)(path, "utf-8");
416
+ const parsed = JSON.parse(content);
417
+ if (typeof parsed === "object" && parsed !== null) {
418
+ return parsed;
419
+ }
420
+ return null;
421
+ } catch {
422
+ return null;
423
+ }
424
+ }
425
+ function isNpmWorkspaceObject(value) {
426
+ if (typeof value !== "object" || value === null) return false;
427
+ const record = value;
428
+ return Array.isArray(record.packages);
429
+ }
430
+ async function fileExists(path) {
431
+ try {
432
+ await (0, import_promises2.access)(path);
433
+ return true;
434
+ } catch {
435
+ return false;
436
+ }
437
+ }
438
+ async function findNearestAncestorWithEntry(startDir, entryName) {
439
+ let current = (0, import_node_path2.resolve)(startDir);
440
+ for (; ; ) {
441
+ const candidate = (0, import_node_path2.join)(current, entryName);
442
+ try {
443
+ await (0, import_promises2.stat)(candidate);
444
+ return current;
445
+ } catch {
446
+ }
447
+ const parent = (0, import_node_path2.dirname)(current);
448
+ if (parent === current) {
449
+ return null;
450
+ }
451
+ current = parent;
452
+ }
453
+ }
454
+
455
+ // src/commands/create/options.ts
456
+ function determineTemplateFromSelections(input) {
457
+ const shouldSync = input.sync && input.db !== "none";
458
+ if (input.tailwind && shouldSync) return "react-tailwind-sync";
459
+ if (input.tailwind && !shouldSync) return "react-tailwind";
460
+ if (!input.tailwind && shouldSync) return "react-sync";
461
+ return "react-basic";
462
+ }
463
+ function isFrameworkValue(value) {
464
+ return value === "react" || value === "vue" || value === "svelte" || value === "solid";
465
+ }
466
+ function isAuthValue(value) {
467
+ return value === "none" || value === "email-password" || value === "oauth";
468
+ }
469
+ function isDatabaseValue(value) {
470
+ return value === "none" || value === "sqlite" || value === "postgres";
471
+ }
472
+ function isDatabaseProviderValue(value) {
473
+ return value === "none" || value === "local" || value === "supabase" || value === "neon" || value === "railway" || value === "vercel-postgres" || value === "custom";
474
+ }
475
+
476
+ // src/commands/create/preferences-flow.ts
477
+ async function resolveCreatePreferencesFlow(params) {
478
+ const { flags, prompts, store } = params;
479
+ const stored = store.getCreatePreferences();
480
+ const base = getCreatePreferencesOrDefault(store);
481
+ const hasExplicitFlags = hasExplicitPreferenceFlags(flags);
482
+ const canOfferStored = !flags.useDefaults && !hasExplicitFlags && stored !== null && promptSupportsRichOptions();
483
+ let effective = { ...base };
484
+ let usedStoredPreferences = false;
485
+ if (flags.useDefaults) {
486
+ effective = getDefaultCreatePreferences();
487
+ } else if (canOfferStored && stored !== null) {
488
+ const reuseStored = await prompts.select("Welcome back! Choose setup mode:", [
489
+ { label: formatStoredPreferenceLabel(stored), value: "reuse" },
490
+ { label: "Customize", value: "customize" }
491
+ ]);
492
+ if (reuseStored === "reuse") {
493
+ effective = { ...stored };
494
+ usedStoredPreferences = true;
495
+ }
496
+ }
497
+ if (flags.framework !== void 0) {
498
+ if (!isFrameworkValue(flags.framework)) {
499
+ throw new Error(
500
+ `Invalid --framework value "${flags.framework}". Expected one of: react, vue, svelte, solid.`
501
+ );
502
+ }
503
+ effective.framework = flags.framework;
504
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
505
+ effective.framework = await prompts.select("UI framework:", [
506
+ { label: "React", value: "react" },
507
+ { label: "Vue (coming soon)", value: "vue", disabled: true },
508
+ { label: "Svelte (coming soon)", value: "svelte", disabled: true },
509
+ { label: "Solid (coming soon)", value: "solid", disabled: true }
510
+ ]);
511
+ }
512
+ if (flags.auth !== void 0) {
513
+ if (!isAuthValue(flags.auth)) {
514
+ throw new Error(
515
+ `Invalid --auth value "${flags.auth}". Expected one of: none, email-password, oauth.`
516
+ );
517
+ }
518
+ effective.auth = flags.auth;
519
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
520
+ effective.auth = await prompts.select("Authentication:", [
521
+ { label: "None", value: "none" },
522
+ { label: "Email + Password (coming soon)", value: "email-password", disabled: true },
523
+ { label: "OAuth (coming soon)", value: "oauth", disabled: true }
524
+ ]);
525
+ }
526
+ if (flags.tailwind !== void 0) {
527
+ effective.tailwind = flags.tailwind;
528
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
529
+ effective.tailwind = await prompts.confirm("Use Tailwind CSS?", true);
530
+ }
531
+ if (flags.sync !== void 0) {
532
+ effective.sync = flags.sync;
533
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
534
+ effective.sync = await prompts.confirm("Enable multi-device sync?", true);
535
+ }
536
+ if (flags.db !== void 0) {
537
+ if (!isDatabaseValue(flags.db)) {
538
+ throw new Error(`Invalid --db value "${flags.db}". Expected one of: none, sqlite, postgres.`);
539
+ }
540
+ effective.db = flags.db;
541
+ } else if (!effective.sync) {
542
+ effective.db = "none";
543
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
544
+ effective.db = await prompts.select("Server-side database:", [
545
+ { label: "SQLite (zero-config)", value: "sqlite" },
546
+ { label: "PostgreSQL (production-scale)", value: "postgres" }
547
+ ]);
548
+ }
549
+ if (effective.db !== "postgres") {
550
+ effective.dbProvider = "none";
551
+ } else if (flags.dbProvider !== void 0) {
552
+ if (!isDatabaseProviderValue(flags.dbProvider)) {
553
+ throw new Error(
554
+ `Invalid --db-provider value "${flags.dbProvider}". Expected one of: none, local, supabase, neon, railway, vercel-postgres, custom.`
555
+ );
556
+ }
557
+ effective.dbProvider = flags.dbProvider;
558
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
559
+ effective.dbProvider = await prompts.select("Database provider:", [
560
+ { label: "Local Postgres", value: "local" },
561
+ { label: "Supabase", value: "supabase" },
562
+ { label: "Neon", value: "neon" },
563
+ { label: "Railway", value: "railway" },
564
+ { label: "Vercel Postgres", value: "vercel-postgres" },
565
+ { label: "Custom connection string", value: "custom" }
566
+ ]);
567
+ }
568
+ const template = determineTemplateFromSelections({
569
+ tailwind: effective.tailwind,
570
+ sync: effective.sync,
571
+ db: effective.db
172
572
  });
573
+ return {
574
+ framework: effective.framework,
575
+ auth: effective.auth,
576
+ db: effective.db,
577
+ dbProvider: effective.dbProvider,
578
+ tailwind: effective.tailwind,
579
+ sync: effective.sync,
580
+ template,
581
+ usedStoredPreferences
582
+ };
173
583
  }
174
- function createReadline(options) {
175
- return (0, import_node_readline.createInterface)({
176
- input: options?.input ?? process.stdin,
177
- output: options?.output ?? process.stdout
584
+ function shouldSavePreferences(flags) {
585
+ return !flags.useDefaults;
586
+ }
587
+ function saveResolvedPreferences(store, resolution) {
588
+ store.saveCreatePreferences({
589
+ framework: resolution.framework,
590
+ tailwind: resolution.tailwind,
591
+ sync: resolution.sync,
592
+ db: resolution.db,
593
+ dbProvider: resolution.dbProvider,
594
+ auth: resolution.auth,
595
+ packageManager: resolution.packageManager
178
596
  });
179
597
  }
598
+ function hasExplicitPreferenceFlags(flags) {
599
+ 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;
600
+ }
601
+ function promptSupportsRichOptions() {
602
+ return typeof process !== "undefined" && process.stdin.isTTY && process.stdout.isTTY;
603
+ }
604
+ function formatStoredPreferenceLabel(preferences) {
605
+ const syncLabel = preferences.sync ? `sync/${preferences.db}` : "local-only";
606
+ const styleLabel = preferences.tailwind ? "tailwind" : "css";
607
+ return `Use previous settings (${preferences.framework} + ${styleLabel} + ${syncLabel} + ${preferences.packageManager})`;
608
+ }
180
609
 
181
- // src/commands/create/template-engine.ts
610
+ // src/commands/create/project-name.ts
611
+ var import_validate_npm_package_name = __toESM(require("validate-npm-package-name"), 1);
612
+ function validateProjectName(name) {
613
+ const trimmedName = name.trim();
614
+ if (trimmedName.length === 0) {
615
+ return {
616
+ valid: false,
617
+ issues: ["Project name cannot be empty."]
618
+ };
619
+ }
620
+ const validation = (0, import_validate_npm_package_name.default)(trimmedName);
621
+ const issues = [...validation.errors ?? [], ...validation.warnings ?? []];
622
+ if (!validation.validForNewPackages && issues.length === 0) {
623
+ return {
624
+ valid: false,
625
+ issues: ["Project name is not a valid npm package name."]
626
+ };
627
+ }
628
+ return {
629
+ valid: validation.validForNewPackages,
630
+ issues
631
+ };
632
+ }
633
+
634
+ // src/commands/create/sync-provider-preset.ts
635
+ var import_promises3 = require("fs/promises");
636
+ var import_node_path3 = require("path");
637
+ async function applySyncProviderPreset(options) {
638
+ if (!isSyncTemplate(options.template)) {
639
+ return;
640
+ }
641
+ if (options.db !== "postgres") {
642
+ return;
643
+ }
644
+ const providerName = getProviderDisplayName(options.dbProvider);
645
+ const providerConnectionString = getProviderConnectionStringExample(options.dbProvider);
646
+ const serverPath = (0, import_node_path3.join)(options.targetDir, "server.ts");
647
+ const envPath = (0, import_node_path3.join)(options.targetDir, ".env.example");
648
+ const readmePath = (0, import_node_path3.join)(options.targetDir, "README.md");
649
+ const serverTemplate = [
650
+ "import { createPostgresServerStore, createProductionServer } from '@korajs/server'",
651
+ "",
652
+ `// PostgreSQL provider preset: ${providerName}`,
653
+ "// Ensure DATABASE_URL is set in your environment.",
654
+ "",
655
+ "async function start(): Promise<void> {",
656
+ " const connectionString = process.env.DATABASE_URL || ''",
657
+ " if (connectionString.length === 0) {",
658
+ " throw new Error('DATABASE_URL is required for PostgreSQL sync server store.')",
659
+ " }",
660
+ "",
661
+ " const store = await createPostgresServerStore({ connectionString })",
662
+ " const server = createProductionServer({",
663
+ " store,",
664
+ " port: Number(process.env.PORT) || 3001,",
665
+ " staticDir: './dist',",
666
+ " syncPath: '/kora-sync',",
667
+ " })",
668
+ "",
669
+ " const url = await server.start()",
670
+ " console.log(`Kora app running at ${url}`)",
671
+ "}",
672
+ "",
673
+ "void start()",
674
+ ""
675
+ ].join("\n");
676
+ const envTemplate = [
677
+ "# Kora Sync Server",
678
+ "# WebSocket URL for the sync server (used by the client)",
679
+ "VITE_SYNC_URL=ws://localhost:3001",
680
+ "",
681
+ "# Sync server port",
682
+ "PORT=3001",
683
+ "",
684
+ `# PostgreSQL connection string (${providerName})`,
685
+ `# Example: ${providerConnectionString}`,
686
+ "DATABASE_URL=",
687
+ ""
688
+ ].join("\n");
689
+ const existingReadme = await (0, import_promises3.readFile)(readmePath, "utf-8");
690
+ const trimmedReadme = existingReadme.trimEnd();
691
+ const readmeSuffix = [
692
+ "",
693
+ "## PostgreSQL Provider Preset",
694
+ "",
695
+ `Selected DB provider: ${options.dbProvider}`,
696
+ "",
697
+ "This scaffold uses `createPostgresServerStore` in `server.ts` and reads `DATABASE_URL` from the environment. See `.env.example` for provider-specific examples.",
698
+ ""
699
+ ].join("\n");
700
+ const readmeTemplate = `${trimmedReadme}${readmeSuffix}`;
701
+ await (0, import_promises3.writeFile)(serverPath, serverTemplate, "utf-8");
702
+ await (0, import_promises3.writeFile)(envPath, envTemplate, "utf-8");
703
+ await (0, import_promises3.writeFile)(readmePath, readmeTemplate, "utf-8");
704
+ }
705
+ function isSyncTemplate(template) {
706
+ return template === "react-sync" || template === "react-tailwind-sync";
707
+ }
708
+ function getProviderDisplayName(provider) {
709
+ switch (provider) {
710
+ case "supabase":
711
+ return "Supabase";
712
+ case "neon":
713
+ return "Neon";
714
+ case "railway":
715
+ return "Railway";
716
+ case "vercel-postgres":
717
+ return "Vercel Postgres";
718
+ case "custom":
719
+ return "Custom";
720
+ case "local":
721
+ return "Local Postgres";
722
+ case "none":
723
+ return "PostgreSQL";
724
+ }
725
+ }
726
+ function getProviderConnectionStringExample(provider) {
727
+ switch (provider) {
728
+ case "supabase":
729
+ return "postgresql://postgres:<password>@db.<project-ref>.supabase.co:5432/postgres?sslmode=require";
730
+ case "neon":
731
+ return "postgresql://<user>:<password>@<branch>.<project>.neon.tech/neondb?sslmode=require";
732
+ case "railway":
733
+ return "postgresql://postgres:<password>@<host>.railway.app:<port>/railway?sslmode=require";
734
+ case "vercel-postgres":
735
+ return "postgresql://<user>:<password>@<host>.pooler.vercel-storage.com:5432/verceldb?sslmode=require";
736
+ case "custom":
737
+ return "postgresql://<user>:<password>@<host>:5432/<database>";
738
+ case "local":
739
+ return "postgresql://postgres:postgres@localhost:5432/kora";
740
+ case "none":
741
+ return "postgresql://postgres:postgres@localhost:5432/kora";
742
+ }
743
+ }
744
+
745
+ // src/templates/composer.ts
182
746
  var import_node_fs = require("fs");
183
- var import_promises2 = require("fs/promises");
184
- var import_node_path2 = require("path");
747
+ var import_promises4 = require("fs/promises");
748
+ var import_node_path4 = require("path");
185
749
  var import_node_url = require("url");
186
750
  var import_meta = {};
187
751
  function substituteVariables(template, context) {
@@ -191,43 +755,113 @@ function substituteVariables(template, context) {
191
755
  });
192
756
  }
193
757
  function getTemplatePath(templateName) {
194
- let dir = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
195
- for (let i = 0; i < 5; i++) {
196
- if ((0, import_node_fs.existsSync)((0, import_node_path2.resolve)(dir, "templates"))) {
197
- return (0, import_node_path2.resolve)(dir, "templates", templateName);
758
+ let dir = (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
759
+ for (let i = 0; i < 7; i++) {
760
+ const candidate = (0, import_node_path4.resolve)(dir, "templates", templateName);
761
+ if ((0, import_node_fs.existsSync)(candidate)) {
762
+ return candidate;
198
763
  }
199
- dir = (0, import_node_path2.dirname)(dir);
764
+ dir = (0, import_node_path4.dirname)(dir);
200
765
  }
201
- const currentDir = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
202
- return (0, import_node_path2.resolve)(currentDir, "..", "templates", templateName);
766
+ const currentDir = (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
767
+ return (0, import_node_path4.resolve)(currentDir, "..", "..", "..", "templates", templateName);
203
768
  }
204
- async function scaffoldTemplate(templateName, targetDir, context) {
205
- const templateDir = getTemplatePath(templateName);
769
+ function createCompatibilityLayerPlan(templateName) {
770
+ const baseLayer = { category: "base", name: "base", sourceTemplate: "react-basic" };
771
+ const uiLayer = { category: "ui", name: "react", sourceTemplate: null };
772
+ const authLayer = { category: "auth", name: "none", sourceTemplate: null };
773
+ switch (templateName) {
774
+ case "react-basic":
775
+ return {
776
+ compatibilityTarget: templateName,
777
+ layers: [
778
+ baseLayer,
779
+ uiLayer,
780
+ { category: "style", name: "plain", sourceTemplate: null },
781
+ { category: "sync", name: "disabled", sourceTemplate: null },
782
+ { category: "db", name: "none", sourceTemplate: null },
783
+ authLayer
784
+ ]
785
+ };
786
+ case "react-tailwind":
787
+ return {
788
+ compatibilityTarget: templateName,
789
+ layers: [
790
+ baseLayer,
791
+ uiLayer,
792
+ { category: "style", name: "tailwind", sourceTemplate: "react-tailwind" },
793
+ { category: "sync", name: "disabled", sourceTemplate: null },
794
+ { category: "db", name: "none", sourceTemplate: null },
795
+ authLayer
796
+ ]
797
+ };
798
+ case "react-sync":
799
+ return {
800
+ compatibilityTarget: templateName,
801
+ layers: [
802
+ baseLayer,
803
+ uiLayer,
804
+ { category: "style", name: "plain", sourceTemplate: null },
805
+ { category: "sync", name: "enabled", sourceTemplate: "react-sync" },
806
+ { category: "db", name: "sqlite", sourceTemplate: null },
807
+ authLayer
808
+ ]
809
+ };
810
+ case "react-tailwind-sync":
811
+ return {
812
+ compatibilityTarget: templateName,
813
+ layers: [
814
+ baseLayer,
815
+ uiLayer,
816
+ { category: "style", name: "tailwind", sourceTemplate: "react-tailwind" },
817
+ { category: "sync", name: "enabled", sourceTemplate: "react-tailwind-sync" },
818
+ { category: "db", name: "sqlite", sourceTemplate: null },
819
+ authLayer
820
+ ]
821
+ };
822
+ }
823
+ }
824
+ async function composeTemplateLayers(plan, targetDir, context) {
206
825
  const vars = {
207
826
  projectName: context.projectName,
208
827
  packageManager: context.packageManager,
209
- koraVersion: context.koraVersion
828
+ koraVersion: context.koraVersion,
829
+ dbProvider: context.dbProvider ?? "none"
210
830
  };
211
- await copyDirectory(templateDir, targetDir, vars);
831
+ for (const layer of plan.layers) {
832
+ if (layer.sourceTemplate === null) {
833
+ continue;
834
+ }
835
+ const sourceDir = getTemplatePath(layer.sourceTemplate);
836
+ await copyDirectory(sourceDir, targetDir, vars);
837
+ }
212
838
  }
213
839
  async function copyDirectory(src, dest, vars) {
214
- await (0, import_promises2.mkdir)(dest, { recursive: true });
215
- const entries = await (0, import_promises2.readdir)(src);
840
+ await (0, import_promises4.mkdir)(dest, { recursive: true });
841
+ const entries = await (0, import_promises4.readdir)(src);
216
842
  for (const entry of entries) {
217
- const srcPath = (0, import_node_path2.join)(src, entry);
218
- const srcStat = await (0, import_promises2.stat)(srcPath);
843
+ const srcPath = (0, import_node_path4.join)(src, entry);
844
+ const srcStat = await (0, import_promises4.stat)(srcPath);
219
845
  if (srcStat.isDirectory()) {
220
- await copyDirectory(srcPath, (0, import_node_path2.join)(dest, entry), vars);
221
- } else if (entry.endsWith(".hbs")) {
222
- const content = await (0, import_promises2.readFile)(srcPath, "utf-8");
846
+ await copyDirectory(srcPath, (0, import_node_path4.join)(dest, entry), vars);
847
+ continue;
848
+ }
849
+ if (entry.endsWith(".hbs")) {
850
+ const content = await (0, import_promises4.readFile)(srcPath, "utf-8");
223
851
  const outputName = entry.slice(0, -4);
224
- await (0, import_promises2.writeFile)((0, import_node_path2.join)(dest, outputName), substituteVariables(content, vars), "utf-8");
225
- } else {
226
- await (0, import_promises2.copyFile)(srcPath, (0, import_node_path2.join)(dest, entry));
852
+ await (0, import_promises4.writeFile)((0, import_node_path4.join)(dest, outputName), substituteVariables(content, vars), "utf-8");
853
+ continue;
227
854
  }
855
+ await (0, import_promises4.copyFile)(srcPath, (0, import_node_path4.join)(dest, entry));
228
856
  }
229
857
  }
230
858
 
859
+ // src/commands/create/template-engine.ts
860
+ async function scaffoldTemplate(templateName, targetDir, context) {
861
+ const plan = createCompatibilityLayerPlan(templateName);
862
+ await composeTemplateLayers(plan, targetDir, context);
863
+ }
864
+
231
865
  // src/commands/create/create-command.ts
232
866
  var import_meta2 = {};
233
867
  var createCommand = (0, import_citty.defineCommand)({
@@ -249,6 +883,22 @@ var createCommand = (0, import_citty.defineCommand)({
249
883
  type: "string",
250
884
  description: "Package manager (pnpm, npm, yarn, bun)"
251
885
  },
886
+ framework: {
887
+ type: "string",
888
+ description: "UI framework (react, vue, svelte, solid)"
889
+ },
890
+ db: {
891
+ type: "string",
892
+ description: "Database backend for sync templates (sqlite, postgres)"
893
+ },
894
+ "db-provider": {
895
+ type: "string",
896
+ description: "Database provider for postgres (local, supabase, neon, railway, vercel-postgres, custom)"
897
+ },
898
+ auth: {
899
+ type: "string",
900
+ description: "Authentication mode (none, email-password, oauth)"
901
+ },
252
902
  "skip-install": {
253
903
  type: "boolean",
254
904
  description: "Skip installing dependencies",
@@ -271,69 +921,182 @@ var createCommand = (0, import_citty.defineCommand)({
271
921
  },
272
922
  async run({ args }) {
273
923
  const logger = createLogger();
924
+ const prompts = createPromptClient();
925
+ const preferenceStore = new PreferenceStore();
274
926
  logger.banner();
275
- const useDefaults = args.yes === true;
276
- const projectName = args.name || (useDefaults ? "my-kora-app" : await promptText("Project name", "my-kora-app"));
277
- if (!projectName) {
278
- logger.error("Project name is required");
279
- process.exitCode = 1;
280
- return;
281
- }
282
- let template;
283
- if (args.template && isValidTemplate(args.template)) {
284
- template = args.template;
285
- } else if (useDefaults) {
286
- template = "react-tailwind-sync";
287
- } else if (args.tailwind !== void 0 || args.sync !== void 0) {
288
- template = resolveTemplateFromFlags(args.tailwind, args.sync);
289
- } else {
290
- template = await promptSelect(
291
- "Select a template:",
292
- TEMPLATE_INFO.map((t) => ({ label: `${t.label} \u2014 ${t.description}`, value: t.name }))
293
- );
294
- }
295
- let pm;
296
- if (args.pm && isValidPackageManager(args.pm)) {
297
- pm = args.pm;
298
- } else if (useDefaults) {
299
- pm = detectPackageManager();
300
- } else {
301
- const detected = detectPackageManager();
302
- pm = await promptSelect(
303
- "Package manager:",
304
- PACKAGE_MANAGERS.map((p) => ({
305
- label: p === detected ? `${p} (detected)` : p,
306
- value: p
307
- }))
308
- );
309
- }
310
- const targetDir = (0, import_node_path3.resolve)(process.cwd(), projectName);
311
- if (await directoryExists(targetDir)) {
312
- throw new ProjectExistsError(projectName);
313
- }
314
- const koraVersion = resolveKoraVersion();
315
- logger.step(`Creating ${projectName} with ${template} template...`);
316
- await scaffoldTemplate(template, targetDir, {
317
- projectName,
318
- packageManager: pm,
319
- koraVersion
320
- });
321
- logger.success("Project scaffolded");
322
- if (!args["skip-install"]) {
323
- logger.step("Installing dependencies...");
324
- try {
325
- (0, import_node_child_process2.execSync)(getInstallCommand(pm), { cwd: targetDir, stdio: "inherit" });
326
- logger.success("Dependencies installed");
327
- } catch {
328
- logger.warn("Failed to install dependencies. Run install manually.");
927
+ try {
928
+ const useDefaults = args.yes === true;
929
+ if (!useDefaults) {
930
+ prompts.intro("Kora.js \u2014 Offline-first application framework");
329
931
  }
932
+ const projectName = args.name || (useDefaults ? "my-kora-app" : await prompts.text("Project name", "my-kora-app"));
933
+ if (!projectName) {
934
+ logger.error("Project name is required");
935
+ process.exitCode = 1;
936
+ return;
937
+ }
938
+ const nameValidation = validateProjectName(projectName);
939
+ if (!nameValidation.valid) {
940
+ logger.error("Invalid project name.");
941
+ for (const issue of nameValidation.issues) {
942
+ logger.step(`- ${issue}`);
943
+ }
944
+ if (!useDefaults) {
945
+ prompts.outro("Project creation aborted.");
946
+ }
947
+ process.exitCode = 1;
948
+ return;
949
+ }
950
+ const preferenceFlags = {
951
+ framework: typeof args.framework === "string" ? args.framework : void 0,
952
+ auth: typeof args.auth === "string" ? args.auth : void 0,
953
+ db: typeof args.db === "string" ? args.db : void 0,
954
+ dbProvider: typeof args["db-provider"] === "string" ? args["db-provider"] : void 0,
955
+ tailwind: args.tailwind,
956
+ sync: args.sync,
957
+ useDefaults
958
+ };
959
+ const selection = await resolveCreatePreferencesFlow({
960
+ flags: preferenceFlags,
961
+ prompts,
962
+ store: preferenceStore
963
+ });
964
+ if (selection.framework !== "react") {
965
+ logger.error(`Framework "${selection.framework}" is not available yet. Use "react".`);
966
+ if (!useDefaults) {
967
+ prompts.outro("Project creation aborted.");
968
+ }
969
+ process.exitCode = 1;
970
+ return;
971
+ }
972
+ if (selection.auth !== "none") {
973
+ logger.error(`Auth mode "${selection.auth}" is not available yet. Use "none".`);
974
+ if (!useDefaults) {
975
+ prompts.outro("Project creation aborted.");
976
+ }
977
+ process.exitCode = 1;
978
+ return;
979
+ }
980
+ const template = args.template && isValidTemplate(args.template) ? args.template : selection.template;
981
+ let pm;
982
+ if (args.pm && isValidPackageManager(args.pm)) {
983
+ pm = args.pm;
984
+ } else if (useDefaults) {
985
+ pm = detectPackageManager();
986
+ } else if (selection.usedStoredPreferences) {
987
+ pm = preferenceStore.getCreatePreferences()?.packageManager ?? detectPackageManager();
988
+ } else {
989
+ const detected = detectPackageManager();
990
+ pm = await prompts.select(
991
+ "Package manager:",
992
+ PACKAGE_MANAGERS.map((p) => ({
993
+ label: p === detected ? `${p} (detected)` : p,
994
+ value: p
995
+ }))
996
+ );
997
+ }
998
+ const editorDetection = detectEditorFromEnvironment();
999
+ const gitContext = await detectGitContext(process.cwd());
1000
+ const monorepoContext = await detectMonorepoContext(process.cwd());
1001
+ let targetDir = (0, import_node_path5.resolve)(process.cwd(), projectName);
1002
+ if (!useDefaults && monorepoContext.isMonorepo && monorepoContext.root !== null) {
1003
+ const useWorkspaceTarget = await prompts.confirm(
1004
+ `Detected ${formatMonorepoKind(monorepoContext.kind)} at ${monorepoContext.root}. Create app under packages/${projectName}?`,
1005
+ true
1006
+ );
1007
+ if (useWorkspaceTarget) {
1008
+ targetDir = resolveMonorepoTargetDirectory(monorepoContext.root, projectName);
1009
+ }
1010
+ }
1011
+ if (await directoryExists(targetDir)) {
1012
+ throw new ProjectExistsError(projectName);
1013
+ }
1014
+ const koraVersion = resolveKoraVersion();
1015
+ logger.step(`Creating ${projectName} with ${template} template...`);
1016
+ await scaffoldTemplate(template, targetDir, {
1017
+ projectName,
1018
+ packageManager: pm,
1019
+ koraVersion,
1020
+ dbProvider: selection.dbProvider
1021
+ });
1022
+ await applySyncProviderPreset({
1023
+ targetDir,
1024
+ template,
1025
+ db: selection.db,
1026
+ dbProvider: selection.dbProvider
1027
+ });
1028
+ if (!useDefaults && editorDetection.editor !== "unknown") {
1029
+ const shouldApplyEditorPreset = await prompts.confirm(
1030
+ `Detected ${formatEditor(editorDetection.editor)}. Add workspace recommendations for ${formatEditor(editorDetection.editor)}?`,
1031
+ true
1032
+ );
1033
+ if (shouldApplyEditorPreset) {
1034
+ const appliedPreset = await applyEditorWorkspacePreset({
1035
+ targetDir,
1036
+ editor: editorDetection.editor
1037
+ });
1038
+ if (appliedPreset.applied) {
1039
+ logger.step(
1040
+ `Added editor recommendations at ${relativeToTarget(targetDir, appliedPreset.filePath)}`
1041
+ );
1042
+ }
1043
+ }
1044
+ }
1045
+ if (selection.db === "postgres" && isSyncTemplate2(template)) {
1046
+ logger.info(
1047
+ `Applied PostgreSQL sync preset (${formatDbProviderForLog(selection.dbProvider)}). Update DATABASE_URL in .env.example before running the sync server.`
1048
+ );
1049
+ }
1050
+ if (gitContext.hasRepository) {
1051
+ logger.step(
1052
+ `Detected existing git repository at ${gitContext.repositoryRoot}. Skipping git initialization.`
1053
+ );
1054
+ }
1055
+ logger.success("Project scaffolded");
1056
+ if (shouldSavePreferences(preferenceFlags)) {
1057
+ saveResolvedPreferences(preferenceStore, {
1058
+ framework: selection.framework,
1059
+ auth: selection.auth,
1060
+ db: selection.db,
1061
+ dbProvider: selection.dbProvider,
1062
+ tailwind: selection.tailwind,
1063
+ sync: selection.sync,
1064
+ packageManager: pm
1065
+ });
1066
+ }
1067
+ if (!args["skip-install"]) {
1068
+ logger.step("Installing dependencies...");
1069
+ try {
1070
+ (0, import_node_child_process2.execSync)(getInstallCommand(pm), { cwd: targetDir, stdio: "inherit" });
1071
+ logger.success("Dependencies installed");
1072
+ } catch {
1073
+ logger.warn("Failed to install dependencies. Run install manually.");
1074
+ }
1075
+ }
1076
+ logger.blank();
1077
+ logger.info("Done! Next steps:");
1078
+ logger.blank();
1079
+ logger.step(` cd ${targetDir}`);
1080
+ logger.step(` ${getRunDevCommand(pm)}`);
1081
+ logger.blank();
1082
+ if (!useDefaults) {
1083
+ prompts.outro("Project ready. Happy building with Kora!");
1084
+ }
1085
+ } catch (error) {
1086
+ if (error instanceof PromptCancelledError) {
1087
+ process.exitCode = 1;
1088
+ return;
1089
+ }
1090
+ if (error instanceof Error && error.message.startsWith("Invalid --")) {
1091
+ logger.error(error.message);
1092
+ if (!args.yes) {
1093
+ prompts.outro("Project creation aborted.");
1094
+ }
1095
+ process.exitCode = 1;
1096
+ return;
1097
+ }
1098
+ throw error;
330
1099
  }
331
- logger.blank();
332
- logger.info("Done! Next steps:");
333
- logger.blank();
334
- logger.step(` cd ${projectName}`);
335
- logger.step(` ${getRunDevCommand(pm)}`);
336
- logger.blank();
337
1100
  }
338
1101
  });
339
1102
  function isValidTemplate(value) {
@@ -342,19 +1105,71 @@ function isValidTemplate(value) {
342
1105
  function isValidPackageManager(value) {
343
1106
  return PACKAGE_MANAGERS.includes(value);
344
1107
  }
345
- function resolveTemplateFromFlags(tailwind, sync) {
346
- const useTailwind = tailwind !== false;
347
- const useSync = sync !== false;
348
- if (useTailwind && useSync) return "react-tailwind-sync";
349
- if (useTailwind && !useSync) return "react-tailwind";
350
- if (!useTailwind && useSync) return "react-sync";
351
- return "react-basic";
1108
+ function isSyncTemplate2(template) {
1109
+ return template === "react-sync" || template === "react-tailwind-sync";
1110
+ }
1111
+ function formatDbProviderForLog(dbProvider) {
1112
+ switch (dbProvider) {
1113
+ case "supabase":
1114
+ return "Supabase";
1115
+ case "neon":
1116
+ return "Neon";
1117
+ case "railway":
1118
+ return "Railway";
1119
+ case "vercel-postgres":
1120
+ return "Vercel Postgres";
1121
+ case "custom":
1122
+ return "Custom";
1123
+ case "local":
1124
+ return "Local Postgres";
1125
+ case "none":
1126
+ return "PostgreSQL";
1127
+ default:
1128
+ return dbProvider;
1129
+ }
1130
+ }
1131
+ function formatEditor(editor) {
1132
+ switch (editor) {
1133
+ case "vscode":
1134
+ return "VS Code";
1135
+ case "cursor":
1136
+ return "Cursor";
1137
+ case "windsurf":
1138
+ return "Windsurf";
1139
+ case "zed":
1140
+ return "Zed";
1141
+ default:
1142
+ return editor;
1143
+ }
1144
+ }
1145
+ function formatMonorepoKind(kind) {
1146
+ switch (kind) {
1147
+ case "pnpm-workspace":
1148
+ return "pnpm workspace";
1149
+ case "npm-workspaces":
1150
+ return "npm workspace";
1151
+ case "turborepo":
1152
+ return "Turborepo";
1153
+ default:
1154
+ return "monorepo";
1155
+ }
1156
+ }
1157
+ function relativeToTarget(targetDir, filePath) {
1158
+ if (filePath === null) return ".";
1159
+ const normalizedTarget = targetDir.endsWith("/") ? targetDir : `${targetDir}/`;
1160
+ if (filePath.startsWith(normalizedTarget)) {
1161
+ return filePath.slice(normalizedTarget.length);
1162
+ }
1163
+ if (filePath === targetDir) {
1164
+ return ".";
1165
+ }
1166
+ return filePath;
352
1167
  }
353
1168
  function resolveKoraVersion() {
354
1169
  try {
355
- let dir = (0, import_node_path3.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url));
1170
+ let dir = (0, import_node_path5.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url));
356
1171
  for (let i = 0; i < 5; i++) {
357
- const pkgPath = (0, import_node_path3.resolve)(dir, "package.json");
1172
+ const pkgPath = (0, import_node_path5.resolve)(dir, "package.json");
358
1173
  if ((0, import_node_fs2.existsSync)(pkgPath)) {
359
1174
  const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
360
1175
  if (pkg.name === "@korajs/cli") {
@@ -363,7 +1178,7 @@ function resolveKoraVersion() {
363
1178
  return `^${parts[0]}.${parts[1]}.0`;
364
1179
  }
365
1180
  }
366
- dir = (0, import_node_path3.dirname)(dir);
1181
+ dir = (0, import_node_path5.dirname)(dir);
367
1182
  }
368
1183
  return "latest";
369
1184
  } catch {