@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.
@@ -0,0 +1,752 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/errors.ts
4
+ import { KoraError } from "@korajs/core";
5
+ var CliError = class extends KoraError {
6
+ constructor(message, context) {
7
+ super(message, "CLI_ERROR", context);
8
+ this.name = "CliError";
9
+ }
10
+ };
11
+ var ProjectExistsError = class extends KoraError {
12
+ constructor(directory) {
13
+ super(
14
+ `Directory "${directory}" already exists. Choose a different name or remove the existing directory.`,
15
+ "PROJECT_EXISTS",
16
+ { directory }
17
+ );
18
+ this.directory = directory;
19
+ this.name = "ProjectExistsError";
20
+ }
21
+ directory;
22
+ };
23
+ var SchemaNotFoundError = class extends KoraError {
24
+ constructor(searchedPaths) {
25
+ super(
26
+ `Could not find a schema file. Searched: ${searchedPaths.join(", ")}. Create a schema file using defineSchema() from @korajs/core.`,
27
+ "SCHEMA_NOT_FOUND",
28
+ { searchedPaths }
29
+ );
30
+ this.searchedPaths = searchedPaths;
31
+ this.name = "SchemaNotFoundError";
32
+ }
33
+ searchedPaths;
34
+ };
35
+ var InvalidProjectError = class extends KoraError {
36
+ constructor(directory) {
37
+ super(
38
+ `"${directory}" is not a valid Kora project. No package.json with a kora dependency found. Run this command from inside a Kora project.`,
39
+ "INVALID_PROJECT",
40
+ { directory }
41
+ );
42
+ this.directory = directory;
43
+ this.name = "InvalidProjectError";
44
+ }
45
+ directory;
46
+ };
47
+ var DevServerError = class extends KoraError {
48
+ constructor(binary, searchPath) {
49
+ super(
50
+ `Could not find required binary "${binary}" at ${searchPath}. Install project dependencies and try again.`,
51
+ "DEV_SERVER_ERROR",
52
+ { binary, searchPath }
53
+ );
54
+ this.binary = binary;
55
+ this.searchPath = searchPath;
56
+ this.name = "DevServerError";
57
+ }
58
+ binary;
59
+ searchPath;
60
+ };
61
+
62
+ // src/prompts/preferences.ts
63
+ import Conf from "conf";
64
+ var DEFAULT_PREFERENCES = {
65
+ framework: "react",
66
+ tailwind: true,
67
+ sync: true,
68
+ db: "sqlite",
69
+ dbProvider: "none",
70
+ auth: "none",
71
+ packageManager: "pnpm"
72
+ };
73
+ var PREFERENCES_KEY = "create.defaults";
74
+ var PreferenceStore = class {
75
+ store;
76
+ constructor() {
77
+ this.store = new Conf({
78
+ projectName: "korajs-cli"
79
+ });
80
+ }
81
+ getCreatePreferences() {
82
+ return this.store.get(PREFERENCES_KEY) ?? null;
83
+ }
84
+ saveCreatePreferences(preferences) {
85
+ this.store.set(PREFERENCES_KEY, preferences);
86
+ }
87
+ clearCreatePreferences() {
88
+ this.store.delete(PREFERENCES_KEY);
89
+ }
90
+ };
91
+ function getCreatePreferencesOrDefault(store) {
92
+ return store.getCreatePreferences() ?? DEFAULT_PREFERENCES;
93
+ }
94
+ function getDefaultCreatePreferences() {
95
+ return { ...DEFAULT_PREFERENCES };
96
+ }
97
+
98
+ // src/prompts/prompt-client.ts
99
+ import {
100
+ cancel as clackCancel,
101
+ confirm as clackConfirm,
102
+ intro as clackIntro,
103
+ isCancel as clackIsCancel,
104
+ outro as clackOutro,
105
+ select as clackSelect,
106
+ text as clackText
107
+ } from "@clack/prompts";
108
+
109
+ // src/utils/prompt.ts
110
+ import { createInterface } from "readline";
111
+ function promptText(message, defaultValue, options) {
112
+ return new Promise((resolve2) => {
113
+ const rl = createReadline(options);
114
+ const suffix = defaultValue !== void 0 ? ` (${defaultValue})` : "";
115
+ rl.question(` ? ${message}${suffix}: `, (answer) => {
116
+ rl.close();
117
+ const trimmed = answer.trim();
118
+ resolve2(trimmed || defaultValue || "");
119
+ });
120
+ });
121
+ }
122
+ function promptSelect(message, choices, options) {
123
+ return new Promise((resolve2) => {
124
+ const rl = createReadline(options);
125
+ const out = options?.output ?? process.stdout;
126
+ out.write(` ? ${message}
127
+ `);
128
+ for (let i = 0; i < choices.length; i++) {
129
+ const choice = choices[i];
130
+ if (choice) {
131
+ out.write(` ${i + 1}) ${choice.label}
132
+ `);
133
+ }
134
+ }
135
+ const ask = () => {
136
+ rl.question(" > ", (answer) => {
137
+ const index = Number.parseInt(answer.trim(), 10) - 1;
138
+ const selected = choices[index];
139
+ if (selected) {
140
+ rl.close();
141
+ resolve2(selected.value);
142
+ } else {
143
+ out.write(` Please enter a number between 1 and ${choices.length}
144
+ `);
145
+ ask();
146
+ }
147
+ });
148
+ };
149
+ ask();
150
+ });
151
+ }
152
+ function promptConfirm(message, defaultValue = false, options) {
153
+ return new Promise((resolve2) => {
154
+ const rl = createReadline(options);
155
+ const suffix = defaultValue ? "Y/n" : "y/N";
156
+ const ask = () => {
157
+ rl.question(` ? ${message} (${suffix}): `, (answer) => {
158
+ const normalized = answer.trim().toLowerCase();
159
+ if (normalized.length === 0) {
160
+ rl.close();
161
+ resolve2(defaultValue);
162
+ return;
163
+ }
164
+ if (normalized === "y" || normalized === "yes") {
165
+ rl.close();
166
+ resolve2(true);
167
+ return;
168
+ }
169
+ if (normalized === "n" || normalized === "no") {
170
+ rl.close();
171
+ resolve2(false);
172
+ return;
173
+ }
174
+ (options?.output ?? process.stdout).write(" Please answer with y or n\n");
175
+ ask();
176
+ });
177
+ };
178
+ ask();
179
+ });
180
+ }
181
+ function createReadline(options) {
182
+ return createInterface({
183
+ input: options?.input ?? process.stdin,
184
+ output: options?.output ?? process.stdout
185
+ });
186
+ }
187
+
188
+ // src/prompts/prompt-client.ts
189
+ var PromptCancelledError = class extends Error {
190
+ constructor(message = "Prompt cancelled by user") {
191
+ super(message);
192
+ this.name = "PromptCancelledError";
193
+ }
194
+ };
195
+ var ReadlinePromptClient = class {
196
+ async text(message, defaultValue) {
197
+ return promptText(message, defaultValue);
198
+ }
199
+ async select(message, options) {
200
+ return promptSelect(
201
+ message,
202
+ options.filter((option) => option.disabled !== true).map((option) => ({ label: option.label, value: option.value }))
203
+ );
204
+ }
205
+ async confirm(message, defaultValue = false) {
206
+ return promptConfirm(message, defaultValue);
207
+ }
208
+ intro(message) {
209
+ void message;
210
+ }
211
+ outro(message) {
212
+ void message;
213
+ }
214
+ };
215
+ function createPromptClient() {
216
+ const canUseInteractiveClack = typeof process !== "undefined" && process.stdin.isTTY && process.stdout.isTTY;
217
+ if (canUseInteractiveClack) {
218
+ return new ClackPromptClient();
219
+ }
220
+ return new ReadlinePromptClient();
221
+ }
222
+ var ClackPromptClient = class {
223
+ async text(message, defaultValue) {
224
+ const result = await clackText({
225
+ message,
226
+ placeholder: defaultValue,
227
+ defaultValue
228
+ });
229
+ if (clackIsCancel(result)) {
230
+ clackCancel("Operation cancelled.");
231
+ throw new PromptCancelledError();
232
+ }
233
+ const value = result.trim();
234
+ if (value.length > 0) return value;
235
+ return defaultValue ?? "";
236
+ }
237
+ async select(message, options) {
238
+ const mappedOptions = options.map((option) => ({
239
+ label: option.label,
240
+ value: option.value,
241
+ hint: option.hint,
242
+ disabled: option.disabled
243
+ }));
244
+ const result = await clackSelect({
245
+ message,
246
+ options: mappedOptions
247
+ });
248
+ if (clackIsCancel(result)) {
249
+ clackCancel("Operation cancelled.");
250
+ throw new PromptCancelledError();
251
+ }
252
+ return result;
253
+ }
254
+ async confirm(message, defaultValue = false) {
255
+ const result = await clackConfirm({
256
+ message,
257
+ initialValue: defaultValue
258
+ });
259
+ if (clackIsCancel(result)) {
260
+ clackCancel("Operation cancelled.");
261
+ throw new PromptCancelledError();
262
+ }
263
+ return result;
264
+ }
265
+ intro(message) {
266
+ clackIntro(message);
267
+ }
268
+ outro(message) {
269
+ clackOutro(message);
270
+ }
271
+ };
272
+
273
+ // src/types.ts
274
+ var PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
275
+ var TEMPLATES = [
276
+ "react-tailwind-sync",
277
+ "react-tailwind",
278
+ "react-sync",
279
+ "react-basic"
280
+ ];
281
+ var TEMPLATE_INFO = [
282
+ {
283
+ name: "react-tailwind-sync",
284
+ label: "React + Tailwind (with sync)",
285
+ description: "Polished dark-themed app with Tailwind CSS and sync server (Recommended)"
286
+ },
287
+ {
288
+ name: "react-tailwind",
289
+ label: "React + Tailwind (local-only)",
290
+ description: "Polished dark-themed app with Tailwind CSS \u2014 no sync server"
291
+ },
292
+ {
293
+ name: "react-sync",
294
+ label: "React + CSS (with sync)",
295
+ description: "Clean CSS app with sync server included"
296
+ },
297
+ {
298
+ name: "react-basic",
299
+ label: "React + CSS (local-only)",
300
+ description: "Clean CSS app \u2014 no sync server"
301
+ }
302
+ ];
303
+
304
+ // src/commands/create/options.ts
305
+ function determineTemplateFromSelections(input) {
306
+ const shouldSync = input.sync && input.db !== "none";
307
+ if (input.tailwind && shouldSync) return "react-tailwind-sync";
308
+ if (input.tailwind && !shouldSync) return "react-tailwind";
309
+ if (!input.tailwind && shouldSync) return "react-sync";
310
+ return "react-basic";
311
+ }
312
+ function isFrameworkValue(value) {
313
+ return value === "react" || value === "vue" || value === "svelte" || value === "solid";
314
+ }
315
+ function isAuthValue(value) {
316
+ return value === "none" || value === "email-password" || value === "oauth";
317
+ }
318
+ function isDatabaseValue(value) {
319
+ return value === "none" || value === "sqlite" || value === "postgres";
320
+ }
321
+ function isDatabaseProviderValue(value) {
322
+ return value === "none" || value === "local" || value === "supabase" || value === "neon" || value === "railway" || value === "vercel-postgres" || value === "custom";
323
+ }
324
+
325
+ // src/commands/create/preferences-flow.ts
326
+ async function resolveCreatePreferencesFlow(params) {
327
+ const { flags, prompts, store } = params;
328
+ const stored = store.getCreatePreferences();
329
+ const base = getCreatePreferencesOrDefault(store);
330
+ const hasExplicitFlags = hasExplicitPreferenceFlags(flags);
331
+ const canOfferStored = !flags.useDefaults && !hasExplicitFlags && stored !== null && promptSupportsRichOptions();
332
+ let effective = { ...base };
333
+ let usedStoredPreferences = false;
334
+ if (flags.useDefaults) {
335
+ effective = getDefaultCreatePreferences();
336
+ } else if (canOfferStored && stored !== null) {
337
+ const reuseStored = await prompts.select("Welcome back! Choose setup mode:", [
338
+ { label: formatStoredPreferenceLabel(stored), value: "reuse" },
339
+ { label: "Customize", value: "customize" }
340
+ ]);
341
+ if (reuseStored === "reuse") {
342
+ effective = { ...stored };
343
+ usedStoredPreferences = true;
344
+ }
345
+ }
346
+ if (flags.framework !== void 0) {
347
+ if (!isFrameworkValue(flags.framework)) {
348
+ throw new Error(
349
+ `Invalid --framework value "${flags.framework}". Expected one of: react, vue, svelte, solid.`
350
+ );
351
+ }
352
+ effective.framework = flags.framework;
353
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
354
+ effective.framework = await prompts.select("UI framework:", [
355
+ { label: "React", value: "react" },
356
+ { label: "Vue (coming soon)", value: "vue", disabled: true },
357
+ { label: "Svelte (coming soon)", value: "svelte", disabled: true },
358
+ { label: "Solid (coming soon)", value: "solid", disabled: true }
359
+ ]);
360
+ }
361
+ if (flags.auth !== void 0) {
362
+ if (!isAuthValue(flags.auth)) {
363
+ throw new Error(
364
+ `Invalid --auth value "${flags.auth}". Expected one of: none, email-password, oauth.`
365
+ );
366
+ }
367
+ effective.auth = flags.auth;
368
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
369
+ effective.auth = await prompts.select("Authentication:", [
370
+ { label: "None", value: "none" },
371
+ { label: "Email + Password (coming soon)", value: "email-password", disabled: true },
372
+ { label: "OAuth (coming soon)", value: "oauth", disabled: true }
373
+ ]);
374
+ }
375
+ if (flags.tailwind !== void 0) {
376
+ effective.tailwind = flags.tailwind;
377
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
378
+ effective.tailwind = await prompts.confirm("Use Tailwind CSS?", true);
379
+ }
380
+ if (flags.sync !== void 0) {
381
+ effective.sync = flags.sync;
382
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
383
+ effective.sync = await prompts.confirm("Enable multi-device sync?", true);
384
+ }
385
+ if (flags.db !== void 0) {
386
+ if (!isDatabaseValue(flags.db)) {
387
+ throw new Error(`Invalid --db value "${flags.db}". Expected one of: none, sqlite, postgres.`);
388
+ }
389
+ effective.db = flags.db;
390
+ } else if (!effective.sync) {
391
+ effective.db = "none";
392
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
393
+ effective.db = await prompts.select("Server-side database:", [
394
+ { label: "SQLite (zero-config)", value: "sqlite" },
395
+ { label: "PostgreSQL (production-scale)", value: "postgres" }
396
+ ]);
397
+ }
398
+ if (effective.db !== "postgres") {
399
+ effective.dbProvider = "none";
400
+ } else if (flags.dbProvider !== void 0) {
401
+ if (!isDatabaseProviderValue(flags.dbProvider)) {
402
+ throw new Error(
403
+ `Invalid --db-provider value "${flags.dbProvider}". Expected one of: none, local, supabase, neon, railway, vercel-postgres, custom.`
404
+ );
405
+ }
406
+ effective.dbProvider = flags.dbProvider;
407
+ } else if (!flags.useDefaults && !usedStoredPreferences) {
408
+ effective.dbProvider = await prompts.select("Database provider:", [
409
+ { label: "Local Postgres", value: "local" },
410
+ { label: "Supabase", value: "supabase" },
411
+ { label: "Neon", value: "neon" },
412
+ { label: "Railway", value: "railway" },
413
+ { label: "Vercel Postgres", value: "vercel-postgres" },
414
+ { label: "Custom connection string", value: "custom" }
415
+ ]);
416
+ }
417
+ const template = determineTemplateFromSelections({
418
+ tailwind: effective.tailwind,
419
+ sync: effective.sync,
420
+ db: effective.db
421
+ });
422
+ return {
423
+ framework: effective.framework,
424
+ auth: effective.auth,
425
+ db: effective.db,
426
+ dbProvider: effective.dbProvider,
427
+ tailwind: effective.tailwind,
428
+ sync: effective.sync,
429
+ template,
430
+ usedStoredPreferences
431
+ };
432
+ }
433
+ function shouldSavePreferences(flags) {
434
+ return !flags.useDefaults;
435
+ }
436
+ function saveResolvedPreferences(store, resolution) {
437
+ store.saveCreatePreferences({
438
+ framework: resolution.framework,
439
+ tailwind: resolution.tailwind,
440
+ sync: resolution.sync,
441
+ db: resolution.db,
442
+ dbProvider: resolution.dbProvider,
443
+ auth: resolution.auth,
444
+ packageManager: resolution.packageManager
445
+ });
446
+ }
447
+ function hasExplicitPreferenceFlags(flags) {
448
+ 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;
449
+ }
450
+ function promptSupportsRichOptions() {
451
+ return typeof process !== "undefined" && process.stdin.isTTY && process.stdout.isTTY;
452
+ }
453
+ function formatStoredPreferenceLabel(preferences) {
454
+ const syncLabel = preferences.sync ? `sync/${preferences.db}` : "local-only";
455
+ const styleLabel = preferences.tailwind ? "tailwind" : "css";
456
+ return `Use previous settings (${preferences.framework} + ${styleLabel} + ${syncLabel} + ${preferences.packageManager})`;
457
+ }
458
+
459
+ // src/commands/create/project-name.ts
460
+ import validateNpmPackageName from "validate-npm-package-name";
461
+ function validateProjectName(name) {
462
+ const trimmedName = name.trim();
463
+ if (trimmedName.length === 0) {
464
+ return {
465
+ valid: false,
466
+ issues: ["Project name cannot be empty."]
467
+ };
468
+ }
469
+ const validation = validateNpmPackageName(trimmedName);
470
+ const issues = [...validation.errors ?? [], ...validation.warnings ?? []];
471
+ if (!validation.validForNewPackages && issues.length === 0) {
472
+ return {
473
+ valid: false,
474
+ issues: ["Project name is not a valid npm package name."]
475
+ };
476
+ }
477
+ return {
478
+ valid: validation.validForNewPackages,
479
+ issues
480
+ };
481
+ }
482
+
483
+ // src/commands/create/sync-provider-preset.ts
484
+ import { readFile, writeFile } from "fs/promises";
485
+ import { join } from "path";
486
+ async function applySyncProviderPreset(options) {
487
+ if (!isSyncTemplate(options.template)) {
488
+ return;
489
+ }
490
+ if (options.db !== "postgres") {
491
+ return;
492
+ }
493
+ const providerName = getProviderDisplayName(options.dbProvider);
494
+ const providerConnectionString = getProviderConnectionStringExample(options.dbProvider);
495
+ const serverPath = join(options.targetDir, "server.ts");
496
+ const envPath = join(options.targetDir, ".env.example");
497
+ const readmePath = join(options.targetDir, "README.md");
498
+ const serverTemplate = [
499
+ "import { createPostgresServerStore, createProductionServer } from '@korajs/server'",
500
+ "",
501
+ `// PostgreSQL provider preset: ${providerName}`,
502
+ "// Ensure DATABASE_URL is set in your environment.",
503
+ "",
504
+ "async function start(): Promise<void> {",
505
+ " const connectionString = process.env.DATABASE_URL || ''",
506
+ " if (connectionString.length === 0) {",
507
+ " throw new Error('DATABASE_URL is required for PostgreSQL sync server store.')",
508
+ " }",
509
+ "",
510
+ " const store = await createPostgresServerStore({ connectionString })",
511
+ " const server = createProductionServer({",
512
+ " store,",
513
+ " port: Number(process.env.PORT) || 3001,",
514
+ " staticDir: './dist',",
515
+ " syncPath: '/kora-sync',",
516
+ " })",
517
+ "",
518
+ " const url = await server.start()",
519
+ " console.log(`Kora app running at ${url}`)",
520
+ "}",
521
+ "",
522
+ "void start()",
523
+ ""
524
+ ].join("\n");
525
+ const envTemplate = [
526
+ "# Kora Sync Server",
527
+ "# WebSocket URL for the sync server (used by the client)",
528
+ "VITE_SYNC_URL=ws://localhost:3001",
529
+ "",
530
+ "# Sync server port",
531
+ "PORT=3001",
532
+ "",
533
+ `# PostgreSQL connection string (${providerName})`,
534
+ `# Example: ${providerConnectionString}`,
535
+ "DATABASE_URL=",
536
+ ""
537
+ ].join("\n");
538
+ const existingReadme = await readFile(readmePath, "utf-8");
539
+ const trimmedReadme = existingReadme.trimEnd();
540
+ const readmeSuffix = [
541
+ "",
542
+ "## PostgreSQL Provider Preset",
543
+ "",
544
+ `Selected DB provider: ${options.dbProvider}`,
545
+ "",
546
+ "This scaffold uses `createPostgresServerStore` in `server.ts` and reads `DATABASE_URL` from the environment. See `.env.example` for provider-specific examples.",
547
+ ""
548
+ ].join("\n");
549
+ const readmeTemplate = `${trimmedReadme}${readmeSuffix}`;
550
+ await writeFile(serverPath, serverTemplate, "utf-8");
551
+ await writeFile(envPath, envTemplate, "utf-8");
552
+ await writeFile(readmePath, readmeTemplate, "utf-8");
553
+ }
554
+ function isSyncTemplate(template) {
555
+ return template === "react-sync" || template === "react-tailwind-sync";
556
+ }
557
+ function getProviderDisplayName(provider) {
558
+ switch (provider) {
559
+ case "supabase":
560
+ return "Supabase";
561
+ case "neon":
562
+ return "Neon";
563
+ case "railway":
564
+ return "Railway";
565
+ case "vercel-postgres":
566
+ return "Vercel Postgres";
567
+ case "custom":
568
+ return "Custom";
569
+ case "local":
570
+ return "Local Postgres";
571
+ case "none":
572
+ return "PostgreSQL";
573
+ }
574
+ }
575
+ function getProviderConnectionStringExample(provider) {
576
+ switch (provider) {
577
+ case "supabase":
578
+ return "postgresql://postgres:<password>@db.<project-ref>.supabase.co:5432/postgres?sslmode=require";
579
+ case "neon":
580
+ return "postgresql://<user>:<password>@<branch>.<project>.neon.tech/neondb?sslmode=require";
581
+ case "railway":
582
+ return "postgresql://postgres:<password>@<host>.railway.app:<port>/railway?sslmode=require";
583
+ case "vercel-postgres":
584
+ return "postgresql://<user>:<password>@<host>.pooler.vercel-storage.com:5432/verceldb?sslmode=require";
585
+ case "custom":
586
+ return "postgresql://<user>:<password>@<host>:5432/<database>";
587
+ case "local":
588
+ return "postgresql://postgres:postgres@localhost:5432/kora";
589
+ case "none":
590
+ return "postgresql://postgres:postgres@localhost:5432/kora";
591
+ }
592
+ }
593
+
594
+ // src/utils/fs-helpers.ts
595
+ import { access, readFile as readFile2 } from "fs/promises";
596
+ import { dirname, join as join2, resolve } from "path";
597
+ async function directoryExists(path) {
598
+ try {
599
+ await access(path);
600
+ return true;
601
+ } catch {
602
+ return false;
603
+ }
604
+ }
605
+ async function findProjectRoot(startDir) {
606
+ let current = resolve(startDir ?? process.cwd());
607
+ for (; ; ) {
608
+ const pkgPath = join2(current, "package.json");
609
+ try {
610
+ const content = await readFile2(pkgPath, "utf-8");
611
+ const pkg = JSON.parse(content);
612
+ if (isKoraProject(pkg)) {
613
+ return current;
614
+ }
615
+ } catch {
616
+ }
617
+ const parent = dirname(current);
618
+ if (parent === current) break;
619
+ current = parent;
620
+ }
621
+ return null;
622
+ }
623
+ async function findSchemaFile(projectRoot) {
624
+ const candidates = [
625
+ join2(projectRoot, "src", "schema.ts"),
626
+ join2(projectRoot, "schema.ts"),
627
+ join2(projectRoot, "src", "schema.js"),
628
+ join2(projectRoot, "schema.js")
629
+ ];
630
+ for (const candidate of candidates) {
631
+ try {
632
+ await access(candidate);
633
+ return candidate;
634
+ } catch {
635
+ }
636
+ }
637
+ return null;
638
+ }
639
+ async function hasTsxInstalled(projectRoot) {
640
+ try {
641
+ await access(join2(projectRoot, "node_modules", "tsx", "package.json"));
642
+ return true;
643
+ } catch {
644
+ return false;
645
+ }
646
+ }
647
+ async function resolveProjectBinaryEntryPoint(projectRoot, packageName, binaryName) {
648
+ const pkgJsonPath = join2(projectRoot, "node_modules", packageName, "package.json");
649
+ try {
650
+ const content = await readFile2(pkgJsonPath, "utf-8");
651
+ const pkg = JSON.parse(content);
652
+ let binPath;
653
+ if (typeof pkg.bin === "string") {
654
+ binPath = pkg.bin;
655
+ } else if (typeof pkg.bin === "object" && pkg.bin !== null) {
656
+ binPath = pkg.bin[binaryName];
657
+ }
658
+ if (!binPath) return null;
659
+ const fullPath = join2(projectRoot, "node_modules", packageName, binPath);
660
+ await access(fullPath);
661
+ return fullPath;
662
+ } catch {
663
+ return null;
664
+ }
665
+ }
666
+ function isKoraProject(pkg) {
667
+ if (typeof pkg !== "object" || pkg === null) return false;
668
+ const record = pkg;
669
+ return hasKoraDep(record.dependencies) || hasKoraDep(record.devDependencies);
670
+ }
671
+ function hasKoraDep(deps) {
672
+ if (typeof deps !== "object" || deps === null) return false;
673
+ return Object.keys(deps).some((key) => key === "kora" || key.startsWith("@korajs/"));
674
+ }
675
+
676
+ // src/utils/logger.ts
677
+ var RESET = "\x1B[0m";
678
+ var BOLD = "\x1B[1m";
679
+ var DIM = "\x1B[2m";
680
+ var GREEN = "\x1B[32m";
681
+ var YELLOW = "\x1B[33m";
682
+ var RED = "\x1B[31m";
683
+ var CYAN = "\x1B[36m";
684
+ function createLogger(options) {
685
+ const colorDisabled = options?.noColor === true || process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
686
+ function color(code, text) {
687
+ return colorDisabled ? text : `${code}${text}${RESET}`;
688
+ }
689
+ return {
690
+ info(message) {
691
+ console.log(color(CYAN, message));
692
+ },
693
+ success(message) {
694
+ console.log(color(GREEN, ` \u2713 ${message}`));
695
+ },
696
+ warn(message) {
697
+ console.warn(color(YELLOW, ` \u26A0 ${message}`));
698
+ },
699
+ error(message) {
700
+ console.error(color(RED, ` \u2717 ${message}`));
701
+ },
702
+ step(message) {
703
+ console.log(color(DIM, ` ${message}`));
704
+ },
705
+ blank() {
706
+ console.log();
707
+ },
708
+ banner() {
709
+ console.log();
710
+ console.log(
711
+ color(BOLD + CYAN, " Kora.js") + color(DIM, " \u2014 Offline-first application framework")
712
+ );
713
+ console.log();
714
+ }
715
+ };
716
+ }
717
+
718
+ export {
719
+ CliError,
720
+ ProjectExistsError,
721
+ SchemaNotFoundError,
722
+ InvalidProjectError,
723
+ DevServerError,
724
+ PreferenceStore,
725
+ getCreatePreferencesOrDefault,
726
+ getDefaultCreatePreferences,
727
+ promptConfirm,
728
+ PromptCancelledError,
729
+ ReadlinePromptClient,
730
+ createPromptClient,
731
+ ClackPromptClient,
732
+ PACKAGE_MANAGERS,
733
+ TEMPLATES,
734
+ TEMPLATE_INFO,
735
+ directoryExists,
736
+ findProjectRoot,
737
+ findSchemaFile,
738
+ hasTsxInstalled,
739
+ resolveProjectBinaryEntryPoint,
740
+ createLogger,
741
+ determineTemplateFromSelections,
742
+ isFrameworkValue,
743
+ isAuthValue,
744
+ isDatabaseValue,
745
+ isDatabaseProviderValue,
746
+ resolveCreatePreferencesFlow,
747
+ shouldSavePreferences,
748
+ saveResolvedPreferences,
749
+ validateProjectName,
750
+ applySyncProviderPreset
751
+ };
752
+ //# sourceMappingURL=chunk-KTSRAPSE.js.map