@korajs/cli 0.1.8 → 0.1.11

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.
Files changed (50) hide show
  1. package/dist/bin.cjs +52 -8
  2. package/dist/bin.cjs.map +1 -1
  3. package/dist/bin.js +2 -2
  4. package/dist/chunk-N36PFOSA.js +0 -0
  5. package/dist/{chunk-AUDUNRYA.js → chunk-SYOFLJLB.js} +33 -4
  6. package/dist/chunk-SYOFLJLB.js.map +1 -0
  7. package/dist/{chunk-ZVB4HAB3.js → chunk-VGOOQ56H.js} +22 -7
  8. package/dist/chunk-VGOOQ56H.js.map +1 -0
  9. package/dist/create.cjs +52 -8
  10. package/dist/create.cjs.map +1 -1
  11. package/dist/create.js +2 -2
  12. package/dist/index.cjs +21 -6
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +1 -1
  17. package/package.json +13 -13
  18. package/templates/react-basic/src/App.tsx +129 -31
  19. package/templates/react-basic/src/index.css +281 -0
  20. package/templates/react-basic/src/main.tsx +2 -0
  21. package/templates/react-basic/vite.config.ts +31 -1
  22. package/templates/react-sync/kora.config.ts +1 -1
  23. package/templates/react-sync/server.ts +13 -2
  24. package/templates/react-sync/src/App.tsx +132 -32
  25. package/templates/react-sync/src/index.css +292 -0
  26. package/templates/react-sync/src/main.tsx +2 -0
  27. package/templates/react-sync/vite.config.ts +31 -1
  28. package/templates/react-tailwind/index.html.hbs +12 -0
  29. package/templates/react-tailwind/kora.config.ts +13 -0
  30. package/templates/react-tailwind/package.json.hbs +31 -0
  31. package/templates/react-tailwind/src/App.tsx +212 -0
  32. package/templates/react-tailwind/src/index.css +7 -0
  33. package/templates/react-tailwind/src/kora-worker.ts +3 -0
  34. package/templates/react-tailwind/src/main.tsx +23 -0
  35. package/templates/react-tailwind/src/schema.ts +15 -0
  36. package/templates/react-tailwind/tsconfig.json +14 -0
  37. package/templates/react-tailwind/vite.config.ts +40 -0
  38. package/templates/react-tailwind-sync/index.html.hbs +12 -0
  39. package/templates/react-tailwind-sync/kora.config.ts +17 -0
  40. package/templates/react-tailwind-sync/package.json.hbs +33 -0
  41. package/templates/react-tailwind-sync/server.ts +22 -0
  42. package/templates/react-tailwind-sync/src/App.tsx +241 -0
  43. package/templates/react-tailwind-sync/src/index.css +7 -0
  44. package/templates/react-tailwind-sync/src/kora-worker.ts +3 -0
  45. package/templates/react-tailwind-sync/src/main.tsx +29 -0
  46. package/templates/react-tailwind-sync/src/schema.ts +15 -0
  47. package/templates/react-tailwind-sync/tsconfig.json +14 -0
  48. package/templates/react-tailwind-sync/vite.config.ts +40 -0
  49. package/dist/chunk-AUDUNRYA.js.map +0 -1
  50. package/dist/chunk-ZVB4HAB3.js.map +0 -1
package/dist/bin.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  hasTsxInstalled,
8
8
  promptConfirm,
9
9
  resolveProjectBinaryEntryPoint
10
- } from "./chunk-AUDUNRYA.js";
10
+ } from "./chunk-SYOFLJLB.js";
11
11
  import {
12
12
  generateTypes
13
13
  } from "./chunk-N36PFOSA.js";
@@ -15,7 +15,7 @@ import {
15
15
  DevServerError,
16
16
  InvalidProjectError,
17
17
  SchemaNotFoundError
18
- } from "./chunk-ZVB4HAB3.js";
18
+ } from "./chunk-VGOOQ56H.js";
19
19
 
20
20
  // src/bin.ts
21
21
  import { defineCommand as defineCommand4, runMain } from "citty";
File without changes
@@ -4,7 +4,7 @@ import {
4
4
  ProjectExistsError,
5
5
  TEMPLATES,
6
6
  TEMPLATE_INFO
7
- } from "./chunk-ZVB4HAB3.js";
7
+ } from "./chunk-VGOOQ56H.js";
8
8
 
9
9
  // src/commands/create/create-command.ts
10
10
  import { execSync as execSync2 } from "child_process";
@@ -297,7 +297,7 @@ var createCommand = defineCommand({
297
297
  },
298
298
  template: {
299
299
  type: "string",
300
- description: "Project template (react-basic, react-sync)"
300
+ description: "Project template (react-tailwind-sync, react-tailwind, react-sync, react-basic)"
301
301
  },
302
302
  pm: {
303
303
  type: "string",
@@ -307,12 +307,27 @@ var createCommand = defineCommand({
307
307
  type: "boolean",
308
308
  description: "Skip installing dependencies",
309
309
  default: false
310
+ },
311
+ yes: {
312
+ type: "boolean",
313
+ alias: "y",
314
+ description: "Accept all defaults (react-tailwind-sync + detected package manager)",
315
+ default: false
316
+ },
317
+ tailwind: {
318
+ type: "boolean",
319
+ description: "Use Tailwind CSS (use --no-tailwind to skip)"
320
+ },
321
+ sync: {
322
+ type: "boolean",
323
+ description: "Include sync server (use --no-sync to skip)"
310
324
  }
311
325
  },
312
326
  async run({ args }) {
313
327
  const logger = createLogger();
314
328
  logger.banner();
315
- const projectName = args.name || await promptText("Project name", "my-kora-app");
329
+ const useDefaults = args.yes === true;
330
+ const projectName = args.name || (useDefaults ? "my-kora-app" : await promptText("Project name", "my-kora-app"));
316
331
  if (!projectName) {
317
332
  logger.error("Project name is required");
318
333
  process.exitCode = 1;
@@ -321,6 +336,10 @@ var createCommand = defineCommand({
321
336
  let template;
322
337
  if (args.template && isValidTemplate(args.template)) {
323
338
  template = args.template;
339
+ } else if (useDefaults) {
340
+ template = "react-tailwind-sync";
341
+ } else if (args.tailwind !== void 0 || args.sync !== void 0) {
342
+ template = resolveTemplateFromFlags(args.tailwind, args.sync);
324
343
  } else {
325
344
  template = await promptSelect(
326
345
  "Select a template:",
@@ -330,6 +349,8 @@ var createCommand = defineCommand({
330
349
  let pm;
331
350
  if (args.pm && isValidPackageManager(args.pm)) {
332
351
  pm = args.pm;
352
+ } else if (useDefaults) {
353
+ pm = detectPackageManager();
333
354
  } else {
334
355
  const detected = detectPackageManager();
335
356
  pm = await promptSelect(
@@ -375,6 +396,14 @@ function isValidTemplate(value) {
375
396
  function isValidPackageManager(value) {
376
397
  return PACKAGE_MANAGERS.includes(value);
377
398
  }
399
+ function resolveTemplateFromFlags(tailwind, sync) {
400
+ const useTailwind = tailwind !== false;
401
+ const useSync = sync !== false;
402
+ if (useTailwind && useSync) return "react-tailwind-sync";
403
+ if (useTailwind && !useSync) return "react-tailwind";
404
+ if (!useTailwind && useSync) return "react-sync";
405
+ return "react-basic";
406
+ }
378
407
  function resolveKoraVersion() {
379
408
  try {
380
409
  let dir = dirname3(fileURLToPath2(import.meta.url));
@@ -405,4 +434,4 @@ export {
405
434
  promptConfirm,
406
435
  createCommand
407
436
  };
408
- //# sourceMappingURL=chunk-AUDUNRYA.js.map
437
+ //# sourceMappingURL=chunk-SYOFLJLB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/create/create-command.ts","../src/utils/fs-helpers.ts","../src/utils/logger.ts","../src/utils/package-manager.ts","../src/utils/prompt.ts","../src/commands/create/template-engine.ts"],"sourcesContent":["import { execSync } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineCommand } from 'citty'\nimport { ProjectExistsError } from '../../errors'\nimport { PACKAGE_MANAGERS, TEMPLATES, TEMPLATE_INFO } from '../../types'\nimport type { PackageManager, TemplateName } from '../../types'\nimport { directoryExists } from '../../utils/fs-helpers'\nimport { createLogger } from '../../utils/logger'\nimport {\n\tdetectPackageManager,\n\tgetInstallCommand,\n\tgetRunDevCommand,\n} from '../../utils/package-manager'\nimport { promptSelect, promptText } from '../../utils/prompt'\nimport { scaffoldTemplate } from './template-engine'\n\n/**\n * The `create` command — scaffolds a new Kora project.\n * Used as `kora create <name>` or `create-kora-app <name>`.\n */\nexport const createCommand = defineCommand({\n\tmeta: {\n\t\tname: 'create',\n\t\tdescription: 'Create a new Kora application',\n\t},\n\targs: {\n\t\tname: {\n\t\t\ttype: 'positional',\n\t\t\tdescription: 'Project directory name',\n\t\t\trequired: false,\n\t\t},\n\t\ttemplate: {\n\t\t\ttype: 'string',\n\t\t\tdescription:\n\t\t\t\t'Project template (react-tailwind-sync, react-tailwind, react-sync, react-basic)',\n\t\t},\n\t\tpm: {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'Package manager (pnpm, npm, yarn, bun)',\n\t\t},\n\t\t'skip-install': {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Skip installing dependencies',\n\t\t\tdefault: false,\n\t\t},\n\t\tyes: {\n\t\t\ttype: 'boolean',\n\t\t\talias: 'y',\n\t\t\tdescription: 'Accept all defaults (react-tailwind-sync + detected package manager)',\n\t\t\tdefault: false,\n\t\t},\n\t\ttailwind: {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Use Tailwind CSS (use --no-tailwind to skip)',\n\t\t},\n\t\tsync: {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Include sync server (use --no-sync to skip)',\n\t\t},\n\t},\n\tasync run({ args }) {\n\t\tconst logger = createLogger()\n\t\tlogger.banner()\n\n\t\tconst useDefaults = args.yes === true\n\n\t\t// Resolve project name\n\t\tconst projectName =\n\t\t\targs.name || (useDefaults ? 'my-kora-app' : await promptText('Project name', 'my-kora-app'))\n\t\tif (!projectName) {\n\t\t\tlogger.error('Project name is required')\n\t\t\tprocess.exitCode = 1\n\t\t\treturn\n\t\t}\n\n\t\t// Resolve template\n\t\tlet template: TemplateName\n\t\tif (args.template && isValidTemplate(args.template)) {\n\t\t\t// Explicit --template flag takes priority\n\t\t\ttemplate = args.template\n\t\t} else if (useDefaults) {\n\t\t\t// --yes defaults to recommended template\n\t\t\ttemplate = 'react-tailwind-sync'\n\t\t} else if (args.tailwind !== undefined || args.sync !== undefined) {\n\t\t\t// Derive template from --tailwind and --sync flags\n\t\t\ttemplate = resolveTemplateFromFlags(args.tailwind, args.sync)\n\t\t} else {\n\t\t\ttemplate = await promptSelect(\n\t\t\t\t'Select a template:',\n\t\t\t\tTEMPLATE_INFO.map((t) => ({ label: `${t.label} — ${t.description}`, value: t.name })),\n\t\t\t)\n\t\t}\n\n\t\t// Resolve package manager\n\t\tlet pm: PackageManager\n\t\tif (args.pm && isValidPackageManager(args.pm)) {\n\t\t\tpm = args.pm\n\t\t} else if (useDefaults) {\n\t\t\tpm = detectPackageManager()\n\t\t} else {\n\t\t\tconst detected = detectPackageManager()\n\t\t\tpm = await promptSelect(\n\t\t\t\t'Package manager:',\n\t\t\t\tPACKAGE_MANAGERS.map((p) => ({\n\t\t\t\t\tlabel: p === detected ? `${p} (detected)` : p,\n\t\t\t\t\tvalue: p,\n\t\t\t\t})),\n\t\t\t)\n\t\t}\n\n\t\t// Validate target directory\n\t\tconst targetDir = resolve(process.cwd(), projectName)\n\t\tif (await directoryExists(targetDir)) {\n\t\t\tthrow new ProjectExistsError(projectName)\n\t\t}\n\n\t\t// Resolve kora version from this package's own package.json\n\t\tconst koraVersion = resolveKoraVersion()\n\n\t\t// Scaffold\n\t\tlogger.step(`Creating ${projectName} with ${template} template...`)\n\t\tawait scaffoldTemplate(template, targetDir, {\n\t\t\tprojectName,\n\t\t\tpackageManager: pm,\n\t\t\tkoraVersion,\n\t\t})\n\t\tlogger.success('Project scaffolded')\n\n\t\t// Install dependencies\n\t\tif (!args['skip-install']) {\n\t\t\tlogger.step('Installing dependencies...')\n\t\t\ttry {\n\t\t\t\texecSync(getInstallCommand(pm), { cwd: targetDir, stdio: 'inherit' })\n\t\t\t\tlogger.success('Dependencies installed')\n\t\t\t} catch {\n\t\t\t\tlogger.warn('Failed to install dependencies. Run install manually.')\n\t\t\t}\n\t\t}\n\n\t\t// Print next steps\n\t\tlogger.blank()\n\t\tlogger.info('Done! Next steps:')\n\t\tlogger.blank()\n\t\tlogger.step(` cd ${projectName}`)\n\t\tlogger.step(` ${getRunDevCommand(pm)}`)\n\t\tlogger.blank()\n\t},\n})\n\nfunction isValidTemplate(value: string): value is TemplateName {\n\treturn (TEMPLATES as readonly string[]).includes(value)\n}\n\nfunction isValidPackageManager(value: string): value is PackageManager {\n\treturn (PACKAGE_MANAGERS as readonly string[]).includes(value)\n}\n\n/**\n * Derives the template name from --tailwind and --sync boolean flags.\n * Unspecified flags default to true (tailwind and sync are the recommended defaults).\n */\nfunction resolveTemplateFromFlags(\n\ttailwind: boolean | undefined,\n\tsync: boolean | undefined,\n): TemplateName {\n\tconst useTailwind = tailwind !== false\n\tconst useSync = sync !== false\n\tif (useTailwind && useSync) return 'react-tailwind-sync'\n\tif (useTailwind && !useSync) return 'react-tailwind'\n\tif (!useTailwind && useSync) return 'react-sync'\n\treturn 'react-basic'\n}\n\n/**\n * Reads the version from @korajs/cli's own package.json and derives a\n * compatible version range for all @korajs packages.\n *\n * The CLI may be a patch ahead of other packages (e.g. CLI-only fixes),\n * so we use the major.minor range (^major.minor.0) which matches all\n * packages in the same release series.\n */\nfunction resolveKoraVersion(): string {\n\ttry {\n\t\tlet dir = dirname(fileURLToPath(import.meta.url))\n\t\tfor (let i = 0; i < 5; i++) {\n\t\t\tconst pkgPath = resolve(dir, 'package.json')\n\t\t\tif (existsSync(pkgPath)) {\n\t\t\t\tconst pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { name?: string; version: string }\n\t\t\t\tif (pkg.name === '@korajs/cli') {\n\t\t\t\t\tif (pkg.version === '0.0.0') return 'latest'\n\t\t\t\t\t// Use ^major.minor.0 so all packages in the series match\n\t\t\t\t\tconst parts = pkg.version.split('.')\n\t\t\t\t\treturn `^${parts[0]}.${parts[1]}.0`\n\t\t\t\t}\n\t\t\t}\n\t\t\tdir = dirname(dir)\n\t\t}\n\t\treturn 'latest'\n\t} catch {\n\t\treturn 'latest'\n\t}\n}\n","import { access, readFile } from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\n\n/** Checks if a directory exists at the given path */\nexport async function directoryExists(path: string): Promise<boolean> {\n\ttry {\n\t\tawait access(path)\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Walks up the directory tree from startDir looking for a package.json\n * that contains a kora or @korajs/* dependency.\n *\n * @param startDir - Directory to start searching from (defaults to cwd)\n * @returns Absolute path to the project root, or null if not found\n */\nexport async function findProjectRoot(startDir?: string): Promise<string | null> {\n\tlet current = resolve(startDir ?? process.cwd())\n\n\t// Walk up until the filesystem root (where dirname(x) === x)\n\tfor (;;) {\n\t\tconst pkgPath = join(current, 'package.json')\n\t\ttry {\n\t\t\tconst content = await readFile(pkgPath, 'utf-8')\n\t\t\tconst pkg: unknown = JSON.parse(content)\n\t\t\tif (isKoraProject(pkg)) {\n\t\t\t\treturn current\n\t\t\t}\n\t\t} catch {\n\t\t\t// No package.json at this level, keep walking up\n\t\t}\n\t\tconst parent = dirname(current)\n\t\tif (parent === current) break\n\t\tcurrent = parent\n\t}\n\n\treturn null\n}\n\n/**\n * Searches for a schema file in common locations within a project.\n *\n * @param projectRoot - The project root directory\n * @returns Absolute path to the schema file, or null if not found\n */\nexport async function findSchemaFile(projectRoot: string): Promise<string | null> {\n\tconst candidates = [\n\t\tjoin(projectRoot, 'src', 'schema.ts'),\n\t\tjoin(projectRoot, 'schema.ts'),\n\t\tjoin(projectRoot, 'src', 'schema.js'),\n\t\tjoin(projectRoot, 'schema.js'),\n\t]\n\n\tfor (const candidate of candidates) {\n\t\ttry {\n\t\t\tawait access(candidate)\n\t\t\treturn candidate\n\t\t} catch {\n\t\t\t// Not found, try next\n\t\t}\n\t}\n\n\treturn null\n}\n\n/**\n * Resolves a binary from a project's local node_modules/.bin directory.\n * On Windows, npm creates .cmd shims instead of extensionless files.\n *\n * @param projectRoot - The project root directory\n * @param binaryName - Binary filename (for example: vite, tsx, kora)\n * @returns Absolute path to the binary, or null if not found\n */\nexport async function resolveProjectBinary(\n\tprojectRoot: string,\n\tbinaryName: string,\n): Promise<string | null> {\n\tconst binDir = join(projectRoot, 'node_modules', '.bin')\n\t// On Windows, try .cmd first (npm/pnpm create .cmd shims)\n\tconst candidates =\n\t\tprocess.platform === 'win32'\n\t\t\t? [join(binDir, `${binaryName}.cmd`), join(binDir, binaryName)]\n\t\t\t: [join(binDir, binaryName)]\n\n\tfor (const candidate of candidates) {\n\t\ttry {\n\t\t\tawait access(candidate)\n\t\t\treturn candidate\n\t\t} catch {\n\t\t\t// continue\n\t\t}\n\t}\n\treturn null\n}\n\n/**\n * Checks whether the `tsx` package is installed in the project's node_modules.\n * Used to determine if we can use `--import tsx` with process.execPath.\n */\nexport async function hasTsxInstalled(projectRoot: string): Promise<boolean> {\n\ttry {\n\t\tawait access(join(projectRoot, 'node_modules', 'tsx', 'package.json'))\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Resolves the JS entry point for a package binary.\n * Reads the package.json bin field to find the actual JS file,\n * avoiding .cmd shims and shell:true on Windows.\n *\n * @returns Absolute path to the JS entry point, or null if not found\n */\nexport async function resolveProjectBinaryEntryPoint(\n\tprojectRoot: string,\n\tpackageName: string,\n\tbinaryName: string,\n): Promise<string | null> {\n\tconst pkgJsonPath = join(projectRoot, 'node_modules', packageName, 'package.json')\n\ttry {\n\t\tconst content = await readFile(pkgJsonPath, 'utf-8')\n\t\tconst pkg = JSON.parse(content) as { bin?: string | Record<string, string> }\n\t\tlet binPath: string | undefined\n\t\tif (typeof pkg.bin === 'string') {\n\t\t\tbinPath = pkg.bin\n\t\t} else if (typeof pkg.bin === 'object' && pkg.bin !== null) {\n\t\t\tbinPath = pkg.bin[binaryName]\n\t\t}\n\t\tif (!binPath) return null\n\t\tconst fullPath = join(projectRoot, 'node_modules', packageName, binPath)\n\t\tawait access(fullPath)\n\t\treturn fullPath\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction isKoraProject(pkg: unknown): boolean {\n\tif (typeof pkg !== 'object' || pkg === null) return false\n\tconst record = pkg as Record<string, unknown>\n\treturn hasKoraDep(record.dependencies) || hasKoraDep(record.devDependencies)\n}\n\nfunction hasKoraDep(deps: unknown): boolean {\n\tif (typeof deps !== 'object' || deps === null) return false\n\treturn Object.keys(deps).some((key) => key === 'kora' || key.startsWith('@korajs/'))\n}\n","/** ANSI escape codes for terminal colors */\nconst RESET = '\\x1b[0m'\nconst BOLD = '\\x1b[1m'\nconst DIM = '\\x1b[2m'\nconst GREEN = '\\x1b[32m'\nconst YELLOW = '\\x1b[33m'\nconst RED = '\\x1b[31m'\nconst CYAN = '\\x1b[36m'\n\nexport interface LoggerOptions {\n\t/** Disable ANSI colors */\n\tnoColor?: boolean\n}\n\nexport interface Logger {\n\tinfo(message: string): void\n\tsuccess(message: string): void\n\twarn(message: string): void\n\terror(message: string): void\n\tstep(message: string): void\n\tblank(): void\n\tbanner(): void\n}\n\n/**\n * Creates a logger with optional ANSI color support.\n * Respects the NO_COLOR environment variable and TTY detection.\n */\nexport function createLogger(options?: LoggerOptions): Logger {\n\tconst colorDisabled =\n\t\toptions?.noColor === true || process.env.NO_COLOR !== undefined || !process.stdout.isTTY\n\n\tfunction color(code: string, text: string): string {\n\t\treturn colorDisabled ? text : `${code}${text}${RESET}`\n\t}\n\n\treturn {\n\t\tinfo(message: string): void {\n\t\t\tconsole.log(color(CYAN, message))\n\t\t},\n\t\tsuccess(message: string): void {\n\t\t\tconsole.log(color(GREEN, ` ✓ ${message}`))\n\t\t},\n\t\twarn(message: string): void {\n\t\t\tconsole.warn(color(YELLOW, ` ⚠ ${message}`))\n\t\t},\n\t\terror(message: string): void {\n\t\t\tconsole.error(color(RED, ` ✗ ${message}`))\n\t\t},\n\t\tstep(message: string): void {\n\t\t\tconsole.log(color(DIM, ` ${message}`))\n\t\t},\n\t\tblank(): void {\n\t\t\tconsole.log()\n\t\t},\n\t\tbanner(): void {\n\t\t\tconsole.log()\n\t\t\tconsole.log(\n\t\t\t\tcolor(BOLD + CYAN, ' Kora.js') + color(DIM, ' — Offline-first application framework'),\n\t\t\t)\n\t\t\tconsole.log()\n\t\t},\n\t}\n}\n","import { execSync } from 'node:child_process'\nimport type { PackageManager } from '../types'\n\n/**\n * Detects the package manager used to invoke the current process\n * by reading the npm_config_user_agent environment variable.\n * Falls back to 'npm' if detection fails.\n */\nexport function detectPackageManager(): PackageManager {\n\tconst userAgent = process.env.npm_config_user_agent\n\tif (!userAgent) return 'npm'\n\n\tif (userAgent.startsWith('pnpm/')) return 'pnpm'\n\tif (userAgent.startsWith('yarn/')) return 'yarn'\n\tif (userAgent.startsWith('bun/')) return 'bun'\n\treturn 'npm'\n}\n\n/** Returns the install command for the given package manager */\nexport function getInstallCommand(pm: PackageManager): string {\n\treturn pm === 'yarn' ? 'yarn' : `${pm} install`\n}\n\n/** Returns the dev server run command for the given package manager */\nexport function getRunDevCommand(pm: PackageManager): string {\n\tif (pm === 'npm') return 'npm run dev'\n\treturn `${pm} dev`\n}\n\n/** Checks if a package manager is available on PATH */\nexport function isPackageManagerAvailable(pm: PackageManager): boolean {\n\ttry {\n\t\texecSync(`${pm} --version`, { stdio: 'ignore' })\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n","import { type Interface as ReadlineInterface, createInterface } from 'node:readline'\n\nexport interface PromptOptions {\n\t/** Input stream (defaults to process.stdin) */\n\tinput?: NodeJS.ReadableStream\n\t/** Output stream (defaults to process.stdout) */\n\toutput?: NodeJS.WritableStream\n}\n\n/**\n * Prompts the user for text input.\n *\n * @param message - The prompt message to display\n * @param defaultValue - Optional default value shown in brackets\n * @param options - Optional input/output streams for testing\n */\nexport function promptText(\n\tmessage: string,\n\tdefaultValue?: string,\n\toptions?: PromptOptions,\n): Promise<string> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createReadline(options)\n\t\tconst suffix = defaultValue !== undefined ? ` (${defaultValue})` : ''\n\t\trl.question(` ? ${message}${suffix}: `, (answer) => {\n\t\t\trl.close()\n\t\t\tconst trimmed = answer.trim()\n\t\t\tresolve(trimmed || defaultValue || '')\n\t\t})\n\t})\n}\n\n/**\n * Prompts the user to select from a numbered list of options.\n *\n * @param message - The prompt message to display\n * @param choices - Array of { label, value } options\n * @param options - Optional input/output streams for testing\n */\nexport function promptSelect<T extends string>(\n\tmessage: string,\n\tchoices: readonly { label: string; value: T }[],\n\toptions?: PromptOptions,\n): Promise<T> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createReadline(options)\n\t\tconst out = options?.output ?? process.stdout\n\n\t\tout.write(` ? ${message}\\n`)\n\t\tfor (let i = 0; i < choices.length; i++) {\n\t\t\tconst choice = choices[i]\n\t\t\tif (choice) {\n\t\t\t\tout.write(` ${i + 1}) ${choice.label}\\n`)\n\t\t\t}\n\t\t}\n\n\t\tconst ask = (): void => {\n\t\t\trl.question(' > ', (answer) => {\n\t\t\t\tconst index = Number.parseInt(answer.trim(), 10) - 1\n\t\t\t\tconst selected = choices[index]\n\t\t\t\tif (selected) {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(selected.value)\n\t\t\t\t} else {\n\t\t\t\t\tout.write(` Please enter a number between 1 and ${choices.length}\\n`)\n\t\t\t\t\task()\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\task()\n\t})\n}\n\n/**\n * Prompts the user with a yes/no confirmation.\n *\n * @param message - The prompt message to display\n * @param defaultValue - Default when input is empty (true => yes)\n * @param options - Optional input/output streams for testing\n */\nexport function promptConfirm(\n\tmessage: string,\n\tdefaultValue = false,\n\toptions?: PromptOptions,\n): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createReadline(options)\n\t\tconst suffix = defaultValue ? 'Y/n' : 'y/N'\n\n\t\tconst ask = (): void => {\n\t\t\trl.question(` ? ${message} (${suffix}): `, (answer) => {\n\t\t\t\tconst normalized = answer.trim().toLowerCase()\n\t\t\t\tif (normalized.length === 0) {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(defaultValue)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (normalized === 'y' || normalized === 'yes') {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(true)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (normalized === 'n' || normalized === 'no') {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(false)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t(options?.output ?? process.stdout).write(' Please answer with y or n\\n')\n\t\t\t\task()\n\t\t\t})\n\t\t}\n\n\t\task()\n\t})\n}\n\nfunction createReadline(options?: PromptOptions): ReadlineInterface {\n\treturn createInterface({\n\t\tinput: options?.input ?? process.stdin,\n\t\toutput: options?.output ?? process.stdout,\n\t})\n}\n","import { existsSync } from 'node:fs'\nimport { copyFile, mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { TemplateContext, TemplateName } from '../../types'\n\n/**\n * Replaces {{variable}} placeholders in a template string with context values.\n *\n * @param template - The template string containing {{variable}} placeholders\n * @param context - Key-value pairs to substitute\n * @returns The template with all placeholders replaced\n */\nexport function substituteVariables(template: string, context: Record<string, string>): string {\n\treturn template.replace(/\\{\\{(\\w+)\\}\\}/g, (_match, key: string) => {\n\t\tconst value = context[key]\n\t\treturn value !== undefined ? value : `{{${key}}}`\n\t})\n}\n\n/**\n * Resolves the absolute path to a bundled template directory.\n *\n * After tsup bundling, import.meta.url points to dist/<file>.js (1 level from root).\n * In source, it's src/commands/create/template-engine.ts (3 levels from root).\n * We walk up from the current file to find the package root containing templates/.\n *\n * @param templateName - Name of the template (e.g. 'react-basic')\n * @returns Absolute path to the template directory\n */\nexport function getTemplatePath(templateName: TemplateName): string {\n\tlet dir = dirname(fileURLToPath(import.meta.url))\n\tfor (let i = 0; i < 5; i++) {\n\t\tif (existsSync(resolve(dir, 'templates'))) {\n\t\t\treturn resolve(dir, 'templates', templateName)\n\t\t}\n\t\tdir = dirname(dir)\n\t}\n\t// Fallback: assume bundled (1 level from package root)\n\tconst currentDir = dirname(fileURLToPath(import.meta.url))\n\treturn resolve(currentDir, '..', 'templates', templateName)\n}\n\n/**\n * Scaffolds a project from a bundled template.\n * Copies all files from the template directory to the target, applying\n * variable substitution to .hbs files and stripping the .hbs extension.\n *\n * @param templateName - Which template to use\n * @param targetDir - Destination directory (must not exist yet)\n * @param context - Variables for template substitution\n */\nexport async function scaffoldTemplate(\n\ttemplateName: TemplateName,\n\ttargetDir: string,\n\tcontext: TemplateContext,\n): Promise<void> {\n\tconst templateDir = getTemplatePath(templateName)\n\tconst vars: Record<string, string> = {\n\t\tprojectName: context.projectName,\n\t\tpackageManager: context.packageManager,\n\t\tkoraVersion: context.koraVersion,\n\t}\n\tawait copyDirectory(templateDir, targetDir, vars)\n}\n\nasync function copyDirectory(\n\tsrc: string,\n\tdest: string,\n\tvars: Record<string, string>,\n): Promise<void> {\n\tawait mkdir(dest, { recursive: true })\n\tconst entries = await readdir(src)\n\n\tfor (const entry of entries) {\n\t\tconst srcPath = join(src, entry)\n\t\tconst srcStat = await stat(srcPath)\n\n\t\tif (srcStat.isDirectory()) {\n\t\t\tawait copyDirectory(srcPath, join(dest, entry), vars)\n\t\t} else if (entry.endsWith('.hbs')) {\n\t\t\t// Template file: substitute variables and strip .hbs extension\n\t\t\tconst content = await readFile(srcPath, 'utf-8')\n\t\t\tconst outputName = entry.slice(0, -4) // Remove .hbs\n\t\t\tawait writeFile(join(dest, outputName), substituteVariables(content, vars), 'utf-8')\n\t\t} else {\n\t\t\t// Regular file: copy as-is\n\t\t\tawait copyFile(srcPath, join(dest, entry))\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["execSync","existsSync","dirname","resolve","fileURLToPath","resolve","readFile","dirname","join","resolve","resolve","execSync","dirname","fileURLToPath","existsSync"]}
@@ -61,17 +61,32 @@ var DevServerError = class extends KoraError {
61
61
 
62
62
  // src/types.ts
63
63
  var PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
64
- var TEMPLATES = ["react-basic", "react-sync"];
64
+ var TEMPLATES = [
65
+ "react-tailwind-sync",
66
+ "react-tailwind",
67
+ "react-sync",
68
+ "react-basic"
69
+ ];
65
70
  var TEMPLATE_INFO = [
66
71
  {
67
- name: "react-basic",
68
- label: "React (basic)",
69
- description: "Local-only React app with Kora \u2014 no sync server"
72
+ name: "react-tailwind-sync",
73
+ label: "React + Tailwind (with sync)",
74
+ description: "Polished dark-themed app with Tailwind CSS and sync server (Recommended)"
75
+ },
76
+ {
77
+ name: "react-tailwind",
78
+ label: "React + Tailwind (local-only)",
79
+ description: "Polished dark-themed app with Tailwind CSS \u2014 no sync server"
70
80
  },
71
81
  {
72
82
  name: "react-sync",
73
- label: "React (with sync)",
74
- description: "React app with Kora sync server included"
83
+ label: "React + CSS (with sync)",
84
+ description: "Clean CSS app with sync server included"
85
+ },
86
+ {
87
+ name: "react-basic",
88
+ label: "React + CSS (local-only)",
89
+ description: "Clean CSS app \u2014 no sync server"
75
90
  }
76
91
  ];
77
92
 
@@ -85,4 +100,4 @@ export {
85
100
  TEMPLATES,
86
101
  TEMPLATE_INFO
87
102
  };
88
- //# sourceMappingURL=chunk-ZVB4HAB3.js.map
103
+ //# sourceMappingURL=chunk-VGOOQ56H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/types.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\n\n/**\n * Base error class for all CLI errors.\n */\nexport class CliError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'CLI_ERROR', context)\n\t\tthis.name = 'CliError'\n\t}\n}\n\n/**\n * Thrown when the target project directory already exists.\n */\nexport class ProjectExistsError extends KoraError {\n\tconstructor(public readonly directory: string) {\n\t\tsuper(\n\t\t\t`Directory \"${directory}\" already exists. Choose a different name or remove the existing directory.`,\n\t\t\t'PROJECT_EXISTS',\n\t\t\t{ directory },\n\t\t)\n\t\tthis.name = 'ProjectExistsError'\n\t}\n}\n\n/**\n * Thrown when a schema file cannot be found in the project.\n */\nexport class SchemaNotFoundError extends KoraError {\n\tconstructor(public readonly searchedPaths: string[]) {\n\t\tsuper(\n\t\t\t`Could not find a schema file. Searched: ${searchedPaths.join(', ')}. Create a schema file using defineSchema() from @korajs/core.`,\n\t\t\t'SCHEMA_NOT_FOUND',\n\t\t\t{ searchedPaths },\n\t\t)\n\t\tthis.name = 'SchemaNotFoundError'\n\t}\n}\n\n/**\n * Thrown when a command is run outside a valid Kora project.\n */\nexport class InvalidProjectError extends KoraError {\n\tconstructor(public readonly directory: string) {\n\t\tsuper(\n\t\t\t`\"${directory}\" is not a valid Kora project. No package.json with a kora dependency found. Run this command from inside a Kora project.`,\n\t\t\t'INVALID_PROJECT',\n\t\t\t{ directory },\n\t\t)\n\t\tthis.name = 'InvalidProjectError'\n\t}\n}\n\n/**\n * Thrown when a required local dev server binary cannot be found.\n */\nexport class DevServerError extends KoraError {\n\tconstructor(\n\t\tpublic readonly binary: string,\n\t\tpublic readonly searchPath: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not find required binary \"${binary}\" at ${searchPath}. Install project dependencies and try again.`,\n\t\t\t'DEV_SERVER_ERROR',\n\t\t\t{ binary, searchPath },\n\t\t)\n\t\tthis.name = 'DevServerError'\n\t}\n}\n","/** Supported package managers */\nexport const PACKAGE_MANAGERS = ['pnpm', 'npm', 'yarn', 'bun'] as const\nexport type PackageManager = (typeof PACKAGE_MANAGERS)[number]\n\n/** Available project templates */\nexport const TEMPLATES = [\n\t'react-tailwind-sync',\n\t'react-tailwind',\n\t'react-sync',\n\t'react-basic',\n] as const\nexport type TemplateName = (typeof TEMPLATES)[number]\n\n/** Metadata for a project template */\nexport interface TemplateInfo {\n\tname: TemplateName\n\tlabel: string\n\tdescription: string\n}\n\n/** Available templates with their descriptions */\nexport const TEMPLATE_INFO: readonly TemplateInfo[] = [\n\t{\n\t\tname: 'react-tailwind-sync',\n\t\tlabel: 'React + Tailwind (with sync)',\n\t\tdescription: 'Polished dark-themed app with Tailwind CSS and sync server (Recommended)',\n\t},\n\t{\n\t\tname: 'react-tailwind',\n\t\tlabel: 'React + Tailwind (local-only)',\n\t\tdescription: 'Polished dark-themed app with Tailwind CSS — no sync server',\n\t},\n\t{\n\t\tname: 'react-sync',\n\t\tlabel: 'React + CSS (with sync)',\n\t\tdescription: 'Clean CSS app with sync server included',\n\t},\n\t{\n\t\tname: 'react-basic',\n\t\tlabel: 'React + CSS (local-only)',\n\t\tdescription: 'Clean CSS app — no sync server',\n\t},\n] as const\n\n/** Variables available for template substitution */\nexport interface TemplateContext {\n\tprojectName: string\n\tpackageManager: PackageManager\n\tkoraVersion: string\n}\n"],"mappings":";;;AAAA,SAAS,iBAAiB;AAKnB,IAAM,WAAN,cAAuB,UAAU;AAAA,EACvC,YAAY,SAAiB,SAAmC;AAC/D,UAAM,SAAS,aAAa,OAAO;AACnC,SAAK,OAAO;AAAA,EACb;AACD;AAKO,IAAM,qBAAN,cAAiC,UAAU;AAAA,EACjD,YAA4B,WAAmB;AAC9C;AAAA,MACC,cAAc,SAAS;AAAA,MACvB;AAAA,MACA,EAAE,UAAU;AAAA,IACb;AAL2B;AAM3B,SAAK,OAAO;AAAA,EACb;AAAA,EAP4B;AAQ7B;AAKO,IAAM,sBAAN,cAAkC,UAAU;AAAA,EAClD,YAA4B,eAAyB;AACpD;AAAA,MACC,2CAA2C,cAAc,KAAK,IAAI,CAAC;AAAA,MACnE;AAAA,MACA,EAAE,cAAc;AAAA,IACjB;AAL2B;AAM3B,SAAK,OAAO;AAAA,EACb;AAAA,EAP4B;AAQ7B;AAKO,IAAM,sBAAN,cAAkC,UAAU;AAAA,EAClD,YAA4B,WAAmB;AAC9C;AAAA,MACC,IAAI,SAAS;AAAA,MACb;AAAA,MACA,EAAE,UAAU;AAAA,IACb;AAL2B;AAM3B,SAAK,OAAO;AAAA,EACb;AAAA,EAP4B;AAQ7B;AAKO,IAAM,iBAAN,cAA6B,UAAU;AAAA,EAC7C,YACiB,QACA,YACf;AACD;AAAA,MACC,mCAAmC,MAAM,QAAQ,UAAU;AAAA,MAC3D;AAAA,MACA,EAAE,QAAQ,WAAW;AAAA,IACtB;AAPgB;AACA;AAOhB,SAAK,OAAO;AAAA,EACb;AAAA,EATiB;AAAA,EACA;AASlB;;;ACpEO,IAAM,mBAAmB,CAAC,QAAQ,OAAO,QAAQ,KAAK;AAItD,IAAM,YAAY;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD;AAWO,IAAM,gBAAyC;AAAA,EACrD;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACd;AAAA,EACA;AAAA,IACC,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aAAa;AAAA,EACd;AACD;","names":[]}
package/dist/create.cjs CHANGED
@@ -27,17 +27,32 @@ var ProjectExistsError = class extends import_core.KoraError {
27
27
 
28
28
  // src/types.ts
29
29
  var PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
30
- var TEMPLATES = ["react-basic", "react-sync"];
30
+ var TEMPLATES = [
31
+ "react-tailwind-sync",
32
+ "react-tailwind",
33
+ "react-sync",
34
+ "react-basic"
35
+ ];
31
36
  var TEMPLATE_INFO = [
32
37
  {
33
- name: "react-basic",
34
- label: "React (basic)",
35
- description: "Local-only React app with Kora \u2014 no sync server"
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"
36
46
  },
37
47
  {
38
48
  name: "react-sync",
39
- label: "React (with sync)",
40
- description: "React app with Kora sync server included"
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"
41
56
  }
42
57
  ];
43
58
 
@@ -228,7 +243,7 @@ var createCommand = (0, import_citty.defineCommand)({
228
243
  },
229
244
  template: {
230
245
  type: "string",
231
- description: "Project template (react-basic, react-sync)"
246
+ description: "Project template (react-tailwind-sync, react-tailwind, react-sync, react-basic)"
232
247
  },
233
248
  pm: {
234
249
  type: "string",
@@ -238,12 +253,27 @@ var createCommand = (0, import_citty.defineCommand)({
238
253
  type: "boolean",
239
254
  description: "Skip installing dependencies",
240
255
  default: false
256
+ },
257
+ yes: {
258
+ type: "boolean",
259
+ alias: "y",
260
+ description: "Accept all defaults (react-tailwind-sync + detected package manager)",
261
+ default: false
262
+ },
263
+ tailwind: {
264
+ type: "boolean",
265
+ description: "Use Tailwind CSS (use --no-tailwind to skip)"
266
+ },
267
+ sync: {
268
+ type: "boolean",
269
+ description: "Include sync server (use --no-sync to skip)"
241
270
  }
242
271
  },
243
272
  async run({ args }) {
244
273
  const logger = createLogger();
245
274
  logger.banner();
246
- const projectName = args.name || await promptText("Project name", "my-kora-app");
275
+ const useDefaults = args.yes === true;
276
+ const projectName = args.name || (useDefaults ? "my-kora-app" : await promptText("Project name", "my-kora-app"));
247
277
  if (!projectName) {
248
278
  logger.error("Project name is required");
249
279
  process.exitCode = 1;
@@ -252,6 +282,10 @@ var createCommand = (0, import_citty.defineCommand)({
252
282
  let template;
253
283
  if (args.template && isValidTemplate(args.template)) {
254
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);
255
289
  } else {
256
290
  template = await promptSelect(
257
291
  "Select a template:",
@@ -261,6 +295,8 @@ var createCommand = (0, import_citty.defineCommand)({
261
295
  let pm;
262
296
  if (args.pm && isValidPackageManager(args.pm)) {
263
297
  pm = args.pm;
298
+ } else if (useDefaults) {
299
+ pm = detectPackageManager();
264
300
  } else {
265
301
  const detected = detectPackageManager();
266
302
  pm = await promptSelect(
@@ -306,6 +342,14 @@ function isValidTemplate(value) {
306
342
  function isValidPackageManager(value) {
307
343
  return PACKAGE_MANAGERS.includes(value);
308
344
  }
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";
352
+ }
309
353
  function resolveKoraVersion() {
310
354
  try {
311
355
  let dir = (0, import_node_path3.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/create.ts","../src/commands/create/create-command.ts","../src/errors.ts","../src/types.ts","../src/utils/fs-helpers.ts","../src/utils/logger.ts","../src/utils/package-manager.ts","../src/utils/prompt.ts","../src/commands/create/template-engine.ts"],"sourcesContent":["import { runMain } from 'citty'\nimport { createCommand } from './commands/create/create-command'\n\nrunMain(createCommand)\n","import { execSync } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineCommand } from 'citty'\nimport { ProjectExistsError } from '../../errors'\nimport { PACKAGE_MANAGERS, TEMPLATES, TEMPLATE_INFO } from '../../types'\nimport type { PackageManager, TemplateName } from '../../types'\nimport { directoryExists } from '../../utils/fs-helpers'\nimport { createLogger } from '../../utils/logger'\nimport {\n\tdetectPackageManager,\n\tgetInstallCommand,\n\tgetRunDevCommand,\n} from '../../utils/package-manager'\nimport { promptSelect, promptText } from '../../utils/prompt'\nimport { scaffoldTemplate } from './template-engine'\n\n/**\n * The `create` command — scaffolds a new Kora project.\n * Used as `kora create <name>` or `create-kora-app <name>`.\n */\nexport const createCommand = defineCommand({\n\tmeta: {\n\t\tname: 'create',\n\t\tdescription: 'Create a new Kora application',\n\t},\n\targs: {\n\t\tname: {\n\t\t\ttype: 'positional',\n\t\t\tdescription: 'Project directory name',\n\t\t\trequired: false,\n\t\t},\n\t\ttemplate: {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'Project template (react-basic, react-sync)',\n\t\t},\n\t\tpm: {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'Package manager (pnpm, npm, yarn, bun)',\n\t\t},\n\t\t'skip-install': {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Skip installing dependencies',\n\t\t\tdefault: false,\n\t\t},\n\t},\n\tasync run({ args }) {\n\t\tconst logger = createLogger()\n\t\tlogger.banner()\n\n\t\t// Resolve project name\n\t\tconst projectName = args.name || (await promptText('Project name', 'my-kora-app'))\n\t\tif (!projectName) {\n\t\t\tlogger.error('Project name is required')\n\t\t\tprocess.exitCode = 1\n\t\t\treturn\n\t\t}\n\n\t\t// Resolve template\n\t\tlet template: TemplateName\n\t\tif (args.template && isValidTemplate(args.template)) {\n\t\t\ttemplate = args.template\n\t\t} else {\n\t\t\ttemplate = await promptSelect(\n\t\t\t\t'Select a template:',\n\t\t\t\tTEMPLATE_INFO.map((t) => ({ label: `${t.label} — ${t.description}`, value: t.name })),\n\t\t\t)\n\t\t}\n\n\t\t// Resolve package manager\n\t\tlet pm: PackageManager\n\t\tif (args.pm && isValidPackageManager(args.pm)) {\n\t\t\tpm = args.pm\n\t\t} else {\n\t\t\tconst detected = detectPackageManager()\n\t\t\tpm = await promptSelect(\n\t\t\t\t'Package manager:',\n\t\t\t\tPACKAGE_MANAGERS.map((p) => ({\n\t\t\t\t\tlabel: p === detected ? `${p} (detected)` : p,\n\t\t\t\t\tvalue: p,\n\t\t\t\t})),\n\t\t\t)\n\t\t}\n\n\t\t// Validate target directory\n\t\tconst targetDir = resolve(process.cwd(), projectName)\n\t\tif (await directoryExists(targetDir)) {\n\t\t\tthrow new ProjectExistsError(projectName)\n\t\t}\n\n\t\t// Resolve kora version from this package's own package.json\n\t\tconst koraVersion = resolveKoraVersion()\n\n\t\t// Scaffold\n\t\tlogger.step(`Creating ${projectName} with ${template} template...`)\n\t\tawait scaffoldTemplate(template, targetDir, {\n\t\t\tprojectName,\n\t\t\tpackageManager: pm,\n\t\t\tkoraVersion,\n\t\t})\n\t\tlogger.success('Project scaffolded')\n\n\t\t// Install dependencies\n\t\tif (!args['skip-install']) {\n\t\t\tlogger.step('Installing dependencies...')\n\t\t\ttry {\n\t\t\t\texecSync(getInstallCommand(pm), { cwd: targetDir, stdio: 'inherit' })\n\t\t\t\tlogger.success('Dependencies installed')\n\t\t\t} catch {\n\t\t\t\tlogger.warn('Failed to install dependencies. Run install manually.')\n\t\t\t}\n\t\t}\n\n\t\t// Print next steps\n\t\tlogger.blank()\n\t\tlogger.info('Done! Next steps:')\n\t\tlogger.blank()\n\t\tlogger.step(` cd ${projectName}`)\n\t\tlogger.step(` ${getRunDevCommand(pm)}`)\n\t\tlogger.blank()\n\t},\n})\n\nfunction isValidTemplate(value: string): value is TemplateName {\n\treturn (TEMPLATES as readonly string[]).includes(value)\n}\n\nfunction isValidPackageManager(value: string): value is PackageManager {\n\treturn (PACKAGE_MANAGERS as readonly string[]).includes(value)\n}\n\n/**\n * Reads the version from @korajs/cli's own package.json and derives a\n * compatible version range for all @korajs packages.\n *\n * The CLI may be a patch ahead of other packages (e.g. CLI-only fixes),\n * so we use the major.minor range (^major.minor.0) which matches all\n * packages in the same release series.\n */\nfunction resolveKoraVersion(): string {\n\ttry {\n\t\tlet dir = dirname(fileURLToPath(import.meta.url))\n\t\tfor (let i = 0; i < 5; i++) {\n\t\t\tconst pkgPath = resolve(dir, 'package.json')\n\t\t\tif (existsSync(pkgPath)) {\n\t\t\t\tconst pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { name?: string; version: string }\n\t\t\t\tif (pkg.name === '@korajs/cli') {\n\t\t\t\t\tif (pkg.version === '0.0.0') return 'latest'\n\t\t\t\t\t// Use ^major.minor.0 so all packages in the series match\n\t\t\t\t\tconst parts = pkg.version.split('.')\n\t\t\t\t\treturn `^${parts[0]}.${parts[1]}.0`\n\t\t\t\t}\n\t\t\t}\n\t\t\tdir = dirname(dir)\n\t\t}\n\t\treturn 'latest'\n\t} catch {\n\t\treturn 'latest'\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n/**\n * Base error class for all CLI errors.\n */\nexport class CliError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'CLI_ERROR', context)\n\t\tthis.name = 'CliError'\n\t}\n}\n\n/**\n * Thrown when the target project directory already exists.\n */\nexport class ProjectExistsError extends KoraError {\n\tconstructor(public readonly directory: string) {\n\t\tsuper(\n\t\t\t`Directory \"${directory}\" already exists. Choose a different name or remove the existing directory.`,\n\t\t\t'PROJECT_EXISTS',\n\t\t\t{ directory },\n\t\t)\n\t\tthis.name = 'ProjectExistsError'\n\t}\n}\n\n/**\n * Thrown when a schema file cannot be found in the project.\n */\nexport class SchemaNotFoundError extends KoraError {\n\tconstructor(public readonly searchedPaths: string[]) {\n\t\tsuper(\n\t\t\t`Could not find a schema file. Searched: ${searchedPaths.join(', ')}. Create a schema file using defineSchema() from @korajs/core.`,\n\t\t\t'SCHEMA_NOT_FOUND',\n\t\t\t{ searchedPaths },\n\t\t)\n\t\tthis.name = 'SchemaNotFoundError'\n\t}\n}\n\n/**\n * Thrown when a command is run outside a valid Kora project.\n */\nexport class InvalidProjectError extends KoraError {\n\tconstructor(public readonly directory: string) {\n\t\tsuper(\n\t\t\t`\"${directory}\" is not a valid Kora project. No package.json with a kora dependency found. Run this command from inside a Kora project.`,\n\t\t\t'INVALID_PROJECT',\n\t\t\t{ directory },\n\t\t)\n\t\tthis.name = 'InvalidProjectError'\n\t}\n}\n\n/**\n * Thrown when a required local dev server binary cannot be found.\n */\nexport class DevServerError extends KoraError {\n\tconstructor(\n\t\tpublic readonly binary: string,\n\t\tpublic readonly searchPath: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not find required binary \"${binary}\" at ${searchPath}. Install project dependencies and try again.`,\n\t\t\t'DEV_SERVER_ERROR',\n\t\t\t{ binary, searchPath },\n\t\t)\n\t\tthis.name = 'DevServerError'\n\t}\n}\n","/** Supported package managers */\nexport const PACKAGE_MANAGERS = ['pnpm', 'npm', 'yarn', 'bun'] as const\nexport type PackageManager = (typeof PACKAGE_MANAGERS)[number]\n\n/** Available project templates */\nexport const TEMPLATES = ['react-basic', 'react-sync'] as const\nexport type TemplateName = (typeof TEMPLATES)[number]\n\n/** Metadata for a project template */\nexport interface TemplateInfo {\n\tname: TemplateName\n\tlabel: string\n\tdescription: string\n}\n\n/** Available templates with their descriptions */\nexport const TEMPLATE_INFO: readonly TemplateInfo[] = [\n\t{\n\t\tname: 'react-basic',\n\t\tlabel: 'React (basic)',\n\t\tdescription: 'Local-only React app with Kora — no sync server',\n\t},\n\t{\n\t\tname: 'react-sync',\n\t\tlabel: 'React (with sync)',\n\t\tdescription: 'React app with Kora sync server included',\n\t},\n] as const\n\n/** Variables available for template substitution */\nexport interface TemplateContext {\n\tprojectName: string\n\tpackageManager: PackageManager\n\tkoraVersion: string\n}\n","import { access, readFile } from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\n\n/** Checks if a directory exists at the given path */\nexport async function directoryExists(path: string): Promise<boolean> {\n\ttry {\n\t\tawait access(path)\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Walks up the directory tree from startDir looking for a package.json\n * that contains a kora or @korajs/* dependency.\n *\n * @param startDir - Directory to start searching from (defaults to cwd)\n * @returns Absolute path to the project root, or null if not found\n */\nexport async function findProjectRoot(startDir?: string): Promise<string | null> {\n\tlet current = resolve(startDir ?? process.cwd())\n\n\t// Walk up until the filesystem root (where dirname(x) === x)\n\tfor (;;) {\n\t\tconst pkgPath = join(current, 'package.json')\n\t\ttry {\n\t\t\tconst content = await readFile(pkgPath, 'utf-8')\n\t\t\tconst pkg: unknown = JSON.parse(content)\n\t\t\tif (isKoraProject(pkg)) {\n\t\t\t\treturn current\n\t\t\t}\n\t\t} catch {\n\t\t\t// No package.json at this level, keep walking up\n\t\t}\n\t\tconst parent = dirname(current)\n\t\tif (parent === current) break\n\t\tcurrent = parent\n\t}\n\n\treturn null\n}\n\n/**\n * Searches for a schema file in common locations within a project.\n *\n * @param projectRoot - The project root directory\n * @returns Absolute path to the schema file, or null if not found\n */\nexport async function findSchemaFile(projectRoot: string): Promise<string | null> {\n\tconst candidates = [\n\t\tjoin(projectRoot, 'src', 'schema.ts'),\n\t\tjoin(projectRoot, 'schema.ts'),\n\t\tjoin(projectRoot, 'src', 'schema.js'),\n\t\tjoin(projectRoot, 'schema.js'),\n\t]\n\n\tfor (const candidate of candidates) {\n\t\ttry {\n\t\t\tawait access(candidate)\n\t\t\treturn candidate\n\t\t} catch {\n\t\t\t// Not found, try next\n\t\t}\n\t}\n\n\treturn null\n}\n\n/**\n * Resolves a binary from a project's local node_modules/.bin directory.\n * On Windows, npm creates .cmd shims instead of extensionless files.\n *\n * @param projectRoot - The project root directory\n * @param binaryName - Binary filename (for example: vite, tsx, kora)\n * @returns Absolute path to the binary, or null if not found\n */\nexport async function resolveProjectBinary(\n\tprojectRoot: string,\n\tbinaryName: string,\n): Promise<string | null> {\n\tconst binDir = join(projectRoot, 'node_modules', '.bin')\n\t// On Windows, try .cmd first (npm/pnpm create .cmd shims)\n\tconst candidates =\n\t\tprocess.platform === 'win32'\n\t\t\t? [join(binDir, `${binaryName}.cmd`), join(binDir, binaryName)]\n\t\t\t: [join(binDir, binaryName)]\n\n\tfor (const candidate of candidates) {\n\t\ttry {\n\t\t\tawait access(candidate)\n\t\t\treturn candidate\n\t\t} catch {\n\t\t\t// continue\n\t\t}\n\t}\n\treturn null\n}\n\n/**\n * Checks whether the `tsx` package is installed in the project's node_modules.\n * Used to determine if we can use `--import tsx` with process.execPath.\n */\nexport async function hasTsxInstalled(projectRoot: string): Promise<boolean> {\n\ttry {\n\t\tawait access(join(projectRoot, 'node_modules', 'tsx', 'package.json'))\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Resolves the JS entry point for a package binary.\n * Reads the package.json bin field to find the actual JS file,\n * avoiding .cmd shims and shell:true on Windows.\n *\n * @returns Absolute path to the JS entry point, or null if not found\n */\nexport async function resolveProjectBinaryEntryPoint(\n\tprojectRoot: string,\n\tpackageName: string,\n\tbinaryName: string,\n): Promise<string | null> {\n\tconst pkgJsonPath = join(projectRoot, 'node_modules', packageName, 'package.json')\n\ttry {\n\t\tconst content = await readFile(pkgJsonPath, 'utf-8')\n\t\tconst pkg = JSON.parse(content) as { bin?: string | Record<string, string> }\n\t\tlet binPath: string | undefined\n\t\tif (typeof pkg.bin === 'string') {\n\t\t\tbinPath = pkg.bin\n\t\t} else if (typeof pkg.bin === 'object' && pkg.bin !== null) {\n\t\t\tbinPath = pkg.bin[binaryName]\n\t\t}\n\t\tif (!binPath) return null\n\t\tconst fullPath = join(projectRoot, 'node_modules', packageName, binPath)\n\t\tawait access(fullPath)\n\t\treturn fullPath\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction isKoraProject(pkg: unknown): boolean {\n\tif (typeof pkg !== 'object' || pkg === null) return false\n\tconst record = pkg as Record<string, unknown>\n\treturn hasKoraDep(record.dependencies) || hasKoraDep(record.devDependencies)\n}\n\nfunction hasKoraDep(deps: unknown): boolean {\n\tif (typeof deps !== 'object' || deps === null) return false\n\treturn Object.keys(deps).some((key) => key === 'kora' || key.startsWith('@korajs/'))\n}\n","/** ANSI escape codes for terminal colors */\nconst RESET = '\\x1b[0m'\nconst BOLD = '\\x1b[1m'\nconst DIM = '\\x1b[2m'\nconst GREEN = '\\x1b[32m'\nconst YELLOW = '\\x1b[33m'\nconst RED = '\\x1b[31m'\nconst CYAN = '\\x1b[36m'\n\nexport interface LoggerOptions {\n\t/** Disable ANSI colors */\n\tnoColor?: boolean\n}\n\nexport interface Logger {\n\tinfo(message: string): void\n\tsuccess(message: string): void\n\twarn(message: string): void\n\terror(message: string): void\n\tstep(message: string): void\n\tblank(): void\n\tbanner(): void\n}\n\n/**\n * Creates a logger with optional ANSI color support.\n * Respects the NO_COLOR environment variable and TTY detection.\n */\nexport function createLogger(options?: LoggerOptions): Logger {\n\tconst colorDisabled =\n\t\toptions?.noColor === true || process.env.NO_COLOR !== undefined || !process.stdout.isTTY\n\n\tfunction color(code: string, text: string): string {\n\t\treturn colorDisabled ? text : `${code}${text}${RESET}`\n\t}\n\n\treturn {\n\t\tinfo(message: string): void {\n\t\t\tconsole.log(color(CYAN, message))\n\t\t},\n\t\tsuccess(message: string): void {\n\t\t\tconsole.log(color(GREEN, ` ✓ ${message}`))\n\t\t},\n\t\twarn(message: string): void {\n\t\t\tconsole.warn(color(YELLOW, ` ⚠ ${message}`))\n\t\t},\n\t\terror(message: string): void {\n\t\t\tconsole.error(color(RED, ` ✗ ${message}`))\n\t\t},\n\t\tstep(message: string): void {\n\t\t\tconsole.log(color(DIM, ` ${message}`))\n\t\t},\n\t\tblank(): void {\n\t\t\tconsole.log()\n\t\t},\n\t\tbanner(): void {\n\t\t\tconsole.log()\n\t\t\tconsole.log(\n\t\t\t\tcolor(BOLD + CYAN, ' Kora.js') + color(DIM, ' — Offline-first application framework'),\n\t\t\t)\n\t\t\tconsole.log()\n\t\t},\n\t}\n}\n","import { execSync } from 'node:child_process'\nimport type { PackageManager } from '../types'\n\n/**\n * Detects the package manager used to invoke the current process\n * by reading the npm_config_user_agent environment variable.\n * Falls back to 'npm' if detection fails.\n */\nexport function detectPackageManager(): PackageManager {\n\tconst userAgent = process.env.npm_config_user_agent\n\tif (!userAgent) return 'npm'\n\n\tif (userAgent.startsWith('pnpm/')) return 'pnpm'\n\tif (userAgent.startsWith('yarn/')) return 'yarn'\n\tif (userAgent.startsWith('bun/')) return 'bun'\n\treturn 'npm'\n}\n\n/** Returns the install command for the given package manager */\nexport function getInstallCommand(pm: PackageManager): string {\n\treturn pm === 'yarn' ? 'yarn' : `${pm} install`\n}\n\n/** Returns the dev server run command for the given package manager */\nexport function getRunDevCommand(pm: PackageManager): string {\n\tif (pm === 'npm') return 'npm run dev'\n\treturn `${pm} dev`\n}\n\n/** Checks if a package manager is available on PATH */\nexport function isPackageManagerAvailable(pm: PackageManager): boolean {\n\ttry {\n\t\texecSync(`${pm} --version`, { stdio: 'ignore' })\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n","import { type Interface as ReadlineInterface, createInterface } from 'node:readline'\n\nexport interface PromptOptions {\n\t/** Input stream (defaults to process.stdin) */\n\tinput?: NodeJS.ReadableStream\n\t/** Output stream (defaults to process.stdout) */\n\toutput?: NodeJS.WritableStream\n}\n\n/**\n * Prompts the user for text input.\n *\n * @param message - The prompt message to display\n * @param defaultValue - Optional default value shown in brackets\n * @param options - Optional input/output streams for testing\n */\nexport function promptText(\n\tmessage: string,\n\tdefaultValue?: string,\n\toptions?: PromptOptions,\n): Promise<string> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createReadline(options)\n\t\tconst suffix = defaultValue !== undefined ? ` (${defaultValue})` : ''\n\t\trl.question(` ? ${message}${suffix}: `, (answer) => {\n\t\t\trl.close()\n\t\t\tconst trimmed = answer.trim()\n\t\t\tresolve(trimmed || defaultValue || '')\n\t\t})\n\t})\n}\n\n/**\n * Prompts the user to select from a numbered list of options.\n *\n * @param message - The prompt message to display\n * @param choices - Array of { label, value } options\n * @param options - Optional input/output streams for testing\n */\nexport function promptSelect<T extends string>(\n\tmessage: string,\n\tchoices: readonly { label: string; value: T }[],\n\toptions?: PromptOptions,\n): Promise<T> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createReadline(options)\n\t\tconst out = options?.output ?? process.stdout\n\n\t\tout.write(` ? ${message}\\n`)\n\t\tfor (let i = 0; i < choices.length; i++) {\n\t\t\tconst choice = choices[i]\n\t\t\tif (choice) {\n\t\t\t\tout.write(` ${i + 1}) ${choice.label}\\n`)\n\t\t\t}\n\t\t}\n\n\t\tconst ask = (): void => {\n\t\t\trl.question(' > ', (answer) => {\n\t\t\t\tconst index = Number.parseInt(answer.trim(), 10) - 1\n\t\t\t\tconst selected = choices[index]\n\t\t\t\tif (selected) {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(selected.value)\n\t\t\t\t} else {\n\t\t\t\t\tout.write(` Please enter a number between 1 and ${choices.length}\\n`)\n\t\t\t\t\task()\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\task()\n\t})\n}\n\n/**\n * Prompts the user with a yes/no confirmation.\n *\n * @param message - The prompt message to display\n * @param defaultValue - Default when input is empty (true => yes)\n * @param options - Optional input/output streams for testing\n */\nexport function promptConfirm(\n\tmessage: string,\n\tdefaultValue = false,\n\toptions?: PromptOptions,\n): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createReadline(options)\n\t\tconst suffix = defaultValue ? 'Y/n' : 'y/N'\n\n\t\tconst ask = (): void => {\n\t\t\trl.question(` ? ${message} (${suffix}): `, (answer) => {\n\t\t\t\tconst normalized = answer.trim().toLowerCase()\n\t\t\t\tif (normalized.length === 0) {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(defaultValue)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (normalized === 'y' || normalized === 'yes') {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(true)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (normalized === 'n' || normalized === 'no') {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(false)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t(options?.output ?? process.stdout).write(' Please answer with y or n\\n')\n\t\t\t\task()\n\t\t\t})\n\t\t}\n\n\t\task()\n\t})\n}\n\nfunction createReadline(options?: PromptOptions): ReadlineInterface {\n\treturn createInterface({\n\t\tinput: options?.input ?? process.stdin,\n\t\toutput: options?.output ?? process.stdout,\n\t})\n}\n","import { existsSync } from 'node:fs'\nimport { copyFile, mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { TemplateContext, TemplateName } from '../../types'\n\n/**\n * Replaces {{variable}} placeholders in a template string with context values.\n *\n * @param template - The template string containing {{variable}} placeholders\n * @param context - Key-value pairs to substitute\n * @returns The template with all placeholders replaced\n */\nexport function substituteVariables(template: string, context: Record<string, string>): string {\n\treturn template.replace(/\\{\\{(\\w+)\\}\\}/g, (_match, key: string) => {\n\t\tconst value = context[key]\n\t\treturn value !== undefined ? value : `{{${key}}}`\n\t})\n}\n\n/**\n * Resolves the absolute path to a bundled template directory.\n *\n * After tsup bundling, import.meta.url points to dist/<file>.js (1 level from root).\n * In source, it's src/commands/create/template-engine.ts (3 levels from root).\n * We walk up from the current file to find the package root containing templates/.\n *\n * @param templateName - Name of the template (e.g. 'react-basic')\n * @returns Absolute path to the template directory\n */\nexport function getTemplatePath(templateName: TemplateName): string {\n\tlet dir = dirname(fileURLToPath(import.meta.url))\n\tfor (let i = 0; i < 5; i++) {\n\t\tif (existsSync(resolve(dir, 'templates'))) {\n\t\t\treturn resolve(dir, 'templates', templateName)\n\t\t}\n\t\tdir = dirname(dir)\n\t}\n\t// Fallback: assume bundled (1 level from package root)\n\tconst currentDir = dirname(fileURLToPath(import.meta.url))\n\treturn resolve(currentDir, '..', 'templates', templateName)\n}\n\n/**\n * Scaffolds a project from a bundled template.\n * Copies all files from the template directory to the target, applying\n * variable substitution to .hbs files and stripping the .hbs extension.\n *\n * @param templateName - Which template to use\n * @param targetDir - Destination directory (must not exist yet)\n * @param context - Variables for template substitution\n */\nexport async function scaffoldTemplate(\n\ttemplateName: TemplateName,\n\ttargetDir: string,\n\tcontext: TemplateContext,\n): Promise<void> {\n\tconst templateDir = getTemplatePath(templateName)\n\tconst vars: Record<string, string> = {\n\t\tprojectName: context.projectName,\n\t\tpackageManager: context.packageManager,\n\t\tkoraVersion: context.koraVersion,\n\t}\n\tawait copyDirectory(templateDir, targetDir, vars)\n}\n\nasync function copyDirectory(\n\tsrc: string,\n\tdest: string,\n\tvars: Record<string, string>,\n): Promise<void> {\n\tawait mkdir(dest, { recursive: true })\n\tconst entries = await readdir(src)\n\n\tfor (const entry of entries) {\n\t\tconst srcPath = join(src, entry)\n\t\tconst srcStat = await stat(srcPath)\n\n\t\tif (srcStat.isDirectory()) {\n\t\t\tawait copyDirectory(srcPath, join(dest, entry), vars)\n\t\t} else if (entry.endsWith('.hbs')) {\n\t\t\t// Template file: substitute variables and strip .hbs extension\n\t\t\tconst content = await readFile(srcPath, 'utf-8')\n\t\t\tconst outputName = entry.slice(0, -4) // Remove .hbs\n\t\t\tawait writeFile(join(dest, outputName), substituteVariables(content, vars), 'utf-8')\n\t\t} else {\n\t\t\t// Regular file: copy as-is\n\t\t\tawait copyFile(srcPath, join(dest, entry))\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["import_citty","import_node_child_process","import_node_fs","import_node_path","import_node_url","resolve","import_promises","import_node_path","import_meta"]}
1
+ {"version":3,"sources":["../src/create.ts","../src/commands/create/create-command.ts","../src/errors.ts","../src/types.ts","../src/utils/fs-helpers.ts","../src/utils/logger.ts","../src/utils/package-manager.ts","../src/utils/prompt.ts","../src/commands/create/template-engine.ts"],"sourcesContent":["import { runMain } from 'citty'\nimport { createCommand } from './commands/create/create-command'\n\nrunMain(createCommand)\n","import { execSync } from 'node:child_process'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { defineCommand } from 'citty'\nimport { ProjectExistsError } from '../../errors'\nimport { PACKAGE_MANAGERS, TEMPLATES, TEMPLATE_INFO } from '../../types'\nimport type { PackageManager, TemplateName } from '../../types'\nimport { directoryExists } from '../../utils/fs-helpers'\nimport { createLogger } from '../../utils/logger'\nimport {\n\tdetectPackageManager,\n\tgetInstallCommand,\n\tgetRunDevCommand,\n} from '../../utils/package-manager'\nimport { promptSelect, promptText } from '../../utils/prompt'\nimport { scaffoldTemplate } from './template-engine'\n\n/**\n * The `create` command — scaffolds a new Kora project.\n * Used as `kora create <name>` or `create-kora-app <name>`.\n */\nexport const createCommand = defineCommand({\n\tmeta: {\n\t\tname: 'create',\n\t\tdescription: 'Create a new Kora application',\n\t},\n\targs: {\n\t\tname: {\n\t\t\ttype: 'positional',\n\t\t\tdescription: 'Project directory name',\n\t\t\trequired: false,\n\t\t},\n\t\ttemplate: {\n\t\t\ttype: 'string',\n\t\t\tdescription:\n\t\t\t\t'Project template (react-tailwind-sync, react-tailwind, react-sync, react-basic)',\n\t\t},\n\t\tpm: {\n\t\t\ttype: 'string',\n\t\t\tdescription: 'Package manager (pnpm, npm, yarn, bun)',\n\t\t},\n\t\t'skip-install': {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Skip installing dependencies',\n\t\t\tdefault: false,\n\t\t},\n\t\tyes: {\n\t\t\ttype: 'boolean',\n\t\t\talias: 'y',\n\t\t\tdescription: 'Accept all defaults (react-tailwind-sync + detected package manager)',\n\t\t\tdefault: false,\n\t\t},\n\t\ttailwind: {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Use Tailwind CSS (use --no-tailwind to skip)',\n\t\t},\n\t\tsync: {\n\t\t\ttype: 'boolean',\n\t\t\tdescription: 'Include sync server (use --no-sync to skip)',\n\t\t},\n\t},\n\tasync run({ args }) {\n\t\tconst logger = createLogger()\n\t\tlogger.banner()\n\n\t\tconst useDefaults = args.yes === true\n\n\t\t// Resolve project name\n\t\tconst projectName =\n\t\t\targs.name || (useDefaults ? 'my-kora-app' : await promptText('Project name', 'my-kora-app'))\n\t\tif (!projectName) {\n\t\t\tlogger.error('Project name is required')\n\t\t\tprocess.exitCode = 1\n\t\t\treturn\n\t\t}\n\n\t\t// Resolve template\n\t\tlet template: TemplateName\n\t\tif (args.template && isValidTemplate(args.template)) {\n\t\t\t// Explicit --template flag takes priority\n\t\t\ttemplate = args.template\n\t\t} else if (useDefaults) {\n\t\t\t// --yes defaults to recommended template\n\t\t\ttemplate = 'react-tailwind-sync'\n\t\t} else if (args.tailwind !== undefined || args.sync !== undefined) {\n\t\t\t// Derive template from --tailwind and --sync flags\n\t\t\ttemplate = resolveTemplateFromFlags(args.tailwind, args.sync)\n\t\t} else {\n\t\t\ttemplate = await promptSelect(\n\t\t\t\t'Select a template:',\n\t\t\t\tTEMPLATE_INFO.map((t) => ({ label: `${t.label} — ${t.description}`, value: t.name })),\n\t\t\t)\n\t\t}\n\n\t\t// Resolve package manager\n\t\tlet pm: PackageManager\n\t\tif (args.pm && isValidPackageManager(args.pm)) {\n\t\t\tpm = args.pm\n\t\t} else if (useDefaults) {\n\t\t\tpm = detectPackageManager()\n\t\t} else {\n\t\t\tconst detected = detectPackageManager()\n\t\t\tpm = await promptSelect(\n\t\t\t\t'Package manager:',\n\t\t\t\tPACKAGE_MANAGERS.map((p) => ({\n\t\t\t\t\tlabel: p === detected ? `${p} (detected)` : p,\n\t\t\t\t\tvalue: p,\n\t\t\t\t})),\n\t\t\t)\n\t\t}\n\n\t\t// Validate target directory\n\t\tconst targetDir = resolve(process.cwd(), projectName)\n\t\tif (await directoryExists(targetDir)) {\n\t\t\tthrow new ProjectExistsError(projectName)\n\t\t}\n\n\t\t// Resolve kora version from this package's own package.json\n\t\tconst koraVersion = resolveKoraVersion()\n\n\t\t// Scaffold\n\t\tlogger.step(`Creating ${projectName} with ${template} template...`)\n\t\tawait scaffoldTemplate(template, targetDir, {\n\t\t\tprojectName,\n\t\t\tpackageManager: pm,\n\t\t\tkoraVersion,\n\t\t})\n\t\tlogger.success('Project scaffolded')\n\n\t\t// Install dependencies\n\t\tif (!args['skip-install']) {\n\t\t\tlogger.step('Installing dependencies...')\n\t\t\ttry {\n\t\t\t\texecSync(getInstallCommand(pm), { cwd: targetDir, stdio: 'inherit' })\n\t\t\t\tlogger.success('Dependencies installed')\n\t\t\t} catch {\n\t\t\t\tlogger.warn('Failed to install dependencies. Run install manually.')\n\t\t\t}\n\t\t}\n\n\t\t// Print next steps\n\t\tlogger.blank()\n\t\tlogger.info('Done! Next steps:')\n\t\tlogger.blank()\n\t\tlogger.step(` cd ${projectName}`)\n\t\tlogger.step(` ${getRunDevCommand(pm)}`)\n\t\tlogger.blank()\n\t},\n})\n\nfunction isValidTemplate(value: string): value is TemplateName {\n\treturn (TEMPLATES as readonly string[]).includes(value)\n}\n\nfunction isValidPackageManager(value: string): value is PackageManager {\n\treturn (PACKAGE_MANAGERS as readonly string[]).includes(value)\n}\n\n/**\n * Derives the template name from --tailwind and --sync boolean flags.\n * Unspecified flags default to true (tailwind and sync are the recommended defaults).\n */\nfunction resolveTemplateFromFlags(\n\ttailwind: boolean | undefined,\n\tsync: boolean | undefined,\n): TemplateName {\n\tconst useTailwind = tailwind !== false\n\tconst useSync = sync !== false\n\tif (useTailwind && useSync) return 'react-tailwind-sync'\n\tif (useTailwind && !useSync) return 'react-tailwind'\n\tif (!useTailwind && useSync) return 'react-sync'\n\treturn 'react-basic'\n}\n\n/**\n * Reads the version from @korajs/cli's own package.json and derives a\n * compatible version range for all @korajs packages.\n *\n * The CLI may be a patch ahead of other packages (e.g. CLI-only fixes),\n * so we use the major.minor range (^major.minor.0) which matches all\n * packages in the same release series.\n */\nfunction resolveKoraVersion(): string {\n\ttry {\n\t\tlet dir = dirname(fileURLToPath(import.meta.url))\n\t\tfor (let i = 0; i < 5; i++) {\n\t\t\tconst pkgPath = resolve(dir, 'package.json')\n\t\t\tif (existsSync(pkgPath)) {\n\t\t\t\tconst pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { name?: string; version: string }\n\t\t\t\tif (pkg.name === '@korajs/cli') {\n\t\t\t\t\tif (pkg.version === '0.0.0') return 'latest'\n\t\t\t\t\t// Use ^major.minor.0 so all packages in the series match\n\t\t\t\t\tconst parts = pkg.version.split('.')\n\t\t\t\t\treturn `^${parts[0]}.${parts[1]}.0`\n\t\t\t\t}\n\t\t\t}\n\t\t\tdir = dirname(dir)\n\t\t}\n\t\treturn 'latest'\n\t} catch {\n\t\treturn 'latest'\n\t}\n}\n","import { KoraError } from '@korajs/core'\n\n/**\n * Base error class for all CLI errors.\n */\nexport class CliError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'CLI_ERROR', context)\n\t\tthis.name = 'CliError'\n\t}\n}\n\n/**\n * Thrown when the target project directory already exists.\n */\nexport class ProjectExistsError extends KoraError {\n\tconstructor(public readonly directory: string) {\n\t\tsuper(\n\t\t\t`Directory \"${directory}\" already exists. Choose a different name or remove the existing directory.`,\n\t\t\t'PROJECT_EXISTS',\n\t\t\t{ directory },\n\t\t)\n\t\tthis.name = 'ProjectExistsError'\n\t}\n}\n\n/**\n * Thrown when a schema file cannot be found in the project.\n */\nexport class SchemaNotFoundError extends KoraError {\n\tconstructor(public readonly searchedPaths: string[]) {\n\t\tsuper(\n\t\t\t`Could not find a schema file. Searched: ${searchedPaths.join(', ')}. Create a schema file using defineSchema() from @korajs/core.`,\n\t\t\t'SCHEMA_NOT_FOUND',\n\t\t\t{ searchedPaths },\n\t\t)\n\t\tthis.name = 'SchemaNotFoundError'\n\t}\n}\n\n/**\n * Thrown when a command is run outside a valid Kora project.\n */\nexport class InvalidProjectError extends KoraError {\n\tconstructor(public readonly directory: string) {\n\t\tsuper(\n\t\t\t`\"${directory}\" is not a valid Kora project. No package.json with a kora dependency found. Run this command from inside a Kora project.`,\n\t\t\t'INVALID_PROJECT',\n\t\t\t{ directory },\n\t\t)\n\t\tthis.name = 'InvalidProjectError'\n\t}\n}\n\n/**\n * Thrown when a required local dev server binary cannot be found.\n */\nexport class DevServerError extends KoraError {\n\tconstructor(\n\t\tpublic readonly binary: string,\n\t\tpublic readonly searchPath: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not find required binary \"${binary}\" at ${searchPath}. Install project dependencies and try again.`,\n\t\t\t'DEV_SERVER_ERROR',\n\t\t\t{ binary, searchPath },\n\t\t)\n\t\tthis.name = 'DevServerError'\n\t}\n}\n","/** Supported package managers */\nexport const PACKAGE_MANAGERS = ['pnpm', 'npm', 'yarn', 'bun'] as const\nexport type PackageManager = (typeof PACKAGE_MANAGERS)[number]\n\n/** Available project templates */\nexport const TEMPLATES = [\n\t'react-tailwind-sync',\n\t'react-tailwind',\n\t'react-sync',\n\t'react-basic',\n] as const\nexport type TemplateName = (typeof TEMPLATES)[number]\n\n/** Metadata for a project template */\nexport interface TemplateInfo {\n\tname: TemplateName\n\tlabel: string\n\tdescription: string\n}\n\n/** Available templates with their descriptions */\nexport const TEMPLATE_INFO: readonly TemplateInfo[] = [\n\t{\n\t\tname: 'react-tailwind-sync',\n\t\tlabel: 'React + Tailwind (with sync)',\n\t\tdescription: 'Polished dark-themed app with Tailwind CSS and sync server (Recommended)',\n\t},\n\t{\n\t\tname: 'react-tailwind',\n\t\tlabel: 'React + Tailwind (local-only)',\n\t\tdescription: 'Polished dark-themed app with Tailwind CSS — no sync server',\n\t},\n\t{\n\t\tname: 'react-sync',\n\t\tlabel: 'React + CSS (with sync)',\n\t\tdescription: 'Clean CSS app with sync server included',\n\t},\n\t{\n\t\tname: 'react-basic',\n\t\tlabel: 'React + CSS (local-only)',\n\t\tdescription: 'Clean CSS app — no sync server',\n\t},\n] as const\n\n/** Variables available for template substitution */\nexport interface TemplateContext {\n\tprojectName: string\n\tpackageManager: PackageManager\n\tkoraVersion: string\n}\n","import { access, readFile } from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\n\n/** Checks if a directory exists at the given path */\nexport async function directoryExists(path: string): Promise<boolean> {\n\ttry {\n\t\tawait access(path)\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Walks up the directory tree from startDir looking for a package.json\n * that contains a kora or @korajs/* dependency.\n *\n * @param startDir - Directory to start searching from (defaults to cwd)\n * @returns Absolute path to the project root, or null if not found\n */\nexport async function findProjectRoot(startDir?: string): Promise<string | null> {\n\tlet current = resolve(startDir ?? process.cwd())\n\n\t// Walk up until the filesystem root (where dirname(x) === x)\n\tfor (;;) {\n\t\tconst pkgPath = join(current, 'package.json')\n\t\ttry {\n\t\t\tconst content = await readFile(pkgPath, 'utf-8')\n\t\t\tconst pkg: unknown = JSON.parse(content)\n\t\t\tif (isKoraProject(pkg)) {\n\t\t\t\treturn current\n\t\t\t}\n\t\t} catch {\n\t\t\t// No package.json at this level, keep walking up\n\t\t}\n\t\tconst parent = dirname(current)\n\t\tif (parent === current) break\n\t\tcurrent = parent\n\t}\n\n\treturn null\n}\n\n/**\n * Searches for a schema file in common locations within a project.\n *\n * @param projectRoot - The project root directory\n * @returns Absolute path to the schema file, or null if not found\n */\nexport async function findSchemaFile(projectRoot: string): Promise<string | null> {\n\tconst candidates = [\n\t\tjoin(projectRoot, 'src', 'schema.ts'),\n\t\tjoin(projectRoot, 'schema.ts'),\n\t\tjoin(projectRoot, 'src', 'schema.js'),\n\t\tjoin(projectRoot, 'schema.js'),\n\t]\n\n\tfor (const candidate of candidates) {\n\t\ttry {\n\t\t\tawait access(candidate)\n\t\t\treturn candidate\n\t\t} catch {\n\t\t\t// Not found, try next\n\t\t}\n\t}\n\n\treturn null\n}\n\n/**\n * Resolves a binary from a project's local node_modules/.bin directory.\n * On Windows, npm creates .cmd shims instead of extensionless files.\n *\n * @param projectRoot - The project root directory\n * @param binaryName - Binary filename (for example: vite, tsx, kora)\n * @returns Absolute path to the binary, or null if not found\n */\nexport async function resolveProjectBinary(\n\tprojectRoot: string,\n\tbinaryName: string,\n): Promise<string | null> {\n\tconst binDir = join(projectRoot, 'node_modules', '.bin')\n\t// On Windows, try .cmd first (npm/pnpm create .cmd shims)\n\tconst candidates =\n\t\tprocess.platform === 'win32'\n\t\t\t? [join(binDir, `${binaryName}.cmd`), join(binDir, binaryName)]\n\t\t\t: [join(binDir, binaryName)]\n\n\tfor (const candidate of candidates) {\n\t\ttry {\n\t\t\tawait access(candidate)\n\t\t\treturn candidate\n\t\t} catch {\n\t\t\t// continue\n\t\t}\n\t}\n\treturn null\n}\n\n/**\n * Checks whether the `tsx` package is installed in the project's node_modules.\n * Used to determine if we can use `--import tsx` with process.execPath.\n */\nexport async function hasTsxInstalled(projectRoot: string): Promise<boolean> {\n\ttry {\n\t\tawait access(join(projectRoot, 'node_modules', 'tsx', 'package.json'))\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/**\n * Resolves the JS entry point for a package binary.\n * Reads the package.json bin field to find the actual JS file,\n * avoiding .cmd shims and shell:true on Windows.\n *\n * @returns Absolute path to the JS entry point, or null if not found\n */\nexport async function resolveProjectBinaryEntryPoint(\n\tprojectRoot: string,\n\tpackageName: string,\n\tbinaryName: string,\n): Promise<string | null> {\n\tconst pkgJsonPath = join(projectRoot, 'node_modules', packageName, 'package.json')\n\ttry {\n\t\tconst content = await readFile(pkgJsonPath, 'utf-8')\n\t\tconst pkg = JSON.parse(content) as { bin?: string | Record<string, string> }\n\t\tlet binPath: string | undefined\n\t\tif (typeof pkg.bin === 'string') {\n\t\t\tbinPath = pkg.bin\n\t\t} else if (typeof pkg.bin === 'object' && pkg.bin !== null) {\n\t\t\tbinPath = pkg.bin[binaryName]\n\t\t}\n\t\tif (!binPath) return null\n\t\tconst fullPath = join(projectRoot, 'node_modules', packageName, binPath)\n\t\tawait access(fullPath)\n\t\treturn fullPath\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction isKoraProject(pkg: unknown): boolean {\n\tif (typeof pkg !== 'object' || pkg === null) return false\n\tconst record = pkg as Record<string, unknown>\n\treturn hasKoraDep(record.dependencies) || hasKoraDep(record.devDependencies)\n}\n\nfunction hasKoraDep(deps: unknown): boolean {\n\tif (typeof deps !== 'object' || deps === null) return false\n\treturn Object.keys(deps).some((key) => key === 'kora' || key.startsWith('@korajs/'))\n}\n","/** ANSI escape codes for terminal colors */\nconst RESET = '\\x1b[0m'\nconst BOLD = '\\x1b[1m'\nconst DIM = '\\x1b[2m'\nconst GREEN = '\\x1b[32m'\nconst YELLOW = '\\x1b[33m'\nconst RED = '\\x1b[31m'\nconst CYAN = '\\x1b[36m'\n\nexport interface LoggerOptions {\n\t/** Disable ANSI colors */\n\tnoColor?: boolean\n}\n\nexport interface Logger {\n\tinfo(message: string): void\n\tsuccess(message: string): void\n\twarn(message: string): void\n\terror(message: string): void\n\tstep(message: string): void\n\tblank(): void\n\tbanner(): void\n}\n\n/**\n * Creates a logger with optional ANSI color support.\n * Respects the NO_COLOR environment variable and TTY detection.\n */\nexport function createLogger(options?: LoggerOptions): Logger {\n\tconst colorDisabled =\n\t\toptions?.noColor === true || process.env.NO_COLOR !== undefined || !process.stdout.isTTY\n\n\tfunction color(code: string, text: string): string {\n\t\treturn colorDisabled ? text : `${code}${text}${RESET}`\n\t}\n\n\treturn {\n\t\tinfo(message: string): void {\n\t\t\tconsole.log(color(CYAN, message))\n\t\t},\n\t\tsuccess(message: string): void {\n\t\t\tconsole.log(color(GREEN, ` ✓ ${message}`))\n\t\t},\n\t\twarn(message: string): void {\n\t\t\tconsole.warn(color(YELLOW, ` ⚠ ${message}`))\n\t\t},\n\t\terror(message: string): void {\n\t\t\tconsole.error(color(RED, ` ✗ ${message}`))\n\t\t},\n\t\tstep(message: string): void {\n\t\t\tconsole.log(color(DIM, ` ${message}`))\n\t\t},\n\t\tblank(): void {\n\t\t\tconsole.log()\n\t\t},\n\t\tbanner(): void {\n\t\t\tconsole.log()\n\t\t\tconsole.log(\n\t\t\t\tcolor(BOLD + CYAN, ' Kora.js') + color(DIM, ' — Offline-first application framework'),\n\t\t\t)\n\t\t\tconsole.log()\n\t\t},\n\t}\n}\n","import { execSync } from 'node:child_process'\nimport type { PackageManager } from '../types'\n\n/**\n * Detects the package manager used to invoke the current process\n * by reading the npm_config_user_agent environment variable.\n * Falls back to 'npm' if detection fails.\n */\nexport function detectPackageManager(): PackageManager {\n\tconst userAgent = process.env.npm_config_user_agent\n\tif (!userAgent) return 'npm'\n\n\tif (userAgent.startsWith('pnpm/')) return 'pnpm'\n\tif (userAgent.startsWith('yarn/')) return 'yarn'\n\tif (userAgent.startsWith('bun/')) return 'bun'\n\treturn 'npm'\n}\n\n/** Returns the install command for the given package manager */\nexport function getInstallCommand(pm: PackageManager): string {\n\treturn pm === 'yarn' ? 'yarn' : `${pm} install`\n}\n\n/** Returns the dev server run command for the given package manager */\nexport function getRunDevCommand(pm: PackageManager): string {\n\tif (pm === 'npm') return 'npm run dev'\n\treturn `${pm} dev`\n}\n\n/** Checks if a package manager is available on PATH */\nexport function isPackageManagerAvailable(pm: PackageManager): boolean {\n\ttry {\n\t\texecSync(`${pm} --version`, { stdio: 'ignore' })\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n","import { type Interface as ReadlineInterface, createInterface } from 'node:readline'\n\nexport interface PromptOptions {\n\t/** Input stream (defaults to process.stdin) */\n\tinput?: NodeJS.ReadableStream\n\t/** Output stream (defaults to process.stdout) */\n\toutput?: NodeJS.WritableStream\n}\n\n/**\n * Prompts the user for text input.\n *\n * @param message - The prompt message to display\n * @param defaultValue - Optional default value shown in brackets\n * @param options - Optional input/output streams for testing\n */\nexport function promptText(\n\tmessage: string,\n\tdefaultValue?: string,\n\toptions?: PromptOptions,\n): Promise<string> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createReadline(options)\n\t\tconst suffix = defaultValue !== undefined ? ` (${defaultValue})` : ''\n\t\trl.question(` ? ${message}${suffix}: `, (answer) => {\n\t\t\trl.close()\n\t\t\tconst trimmed = answer.trim()\n\t\t\tresolve(trimmed || defaultValue || '')\n\t\t})\n\t})\n}\n\n/**\n * Prompts the user to select from a numbered list of options.\n *\n * @param message - The prompt message to display\n * @param choices - Array of { label, value } options\n * @param options - Optional input/output streams for testing\n */\nexport function promptSelect<T extends string>(\n\tmessage: string,\n\tchoices: readonly { label: string; value: T }[],\n\toptions?: PromptOptions,\n): Promise<T> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createReadline(options)\n\t\tconst out = options?.output ?? process.stdout\n\n\t\tout.write(` ? ${message}\\n`)\n\t\tfor (let i = 0; i < choices.length; i++) {\n\t\t\tconst choice = choices[i]\n\t\t\tif (choice) {\n\t\t\t\tout.write(` ${i + 1}) ${choice.label}\\n`)\n\t\t\t}\n\t\t}\n\n\t\tconst ask = (): void => {\n\t\t\trl.question(' > ', (answer) => {\n\t\t\t\tconst index = Number.parseInt(answer.trim(), 10) - 1\n\t\t\t\tconst selected = choices[index]\n\t\t\t\tif (selected) {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(selected.value)\n\t\t\t\t} else {\n\t\t\t\t\tout.write(` Please enter a number between 1 and ${choices.length}\\n`)\n\t\t\t\t\task()\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\task()\n\t})\n}\n\n/**\n * Prompts the user with a yes/no confirmation.\n *\n * @param message - The prompt message to display\n * @param defaultValue - Default when input is empty (true => yes)\n * @param options - Optional input/output streams for testing\n */\nexport function promptConfirm(\n\tmessage: string,\n\tdefaultValue = false,\n\toptions?: PromptOptions,\n): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst rl = createReadline(options)\n\t\tconst suffix = defaultValue ? 'Y/n' : 'y/N'\n\n\t\tconst ask = (): void => {\n\t\t\trl.question(` ? ${message} (${suffix}): `, (answer) => {\n\t\t\t\tconst normalized = answer.trim().toLowerCase()\n\t\t\t\tif (normalized.length === 0) {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(defaultValue)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (normalized === 'y' || normalized === 'yes') {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(true)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif (normalized === 'n' || normalized === 'no') {\n\t\t\t\t\trl.close()\n\t\t\t\t\tresolve(false)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t(options?.output ?? process.stdout).write(' Please answer with y or n\\n')\n\t\t\t\task()\n\t\t\t})\n\t\t}\n\n\t\task()\n\t})\n}\n\nfunction createReadline(options?: PromptOptions): ReadlineInterface {\n\treturn createInterface({\n\t\tinput: options?.input ?? process.stdin,\n\t\toutput: options?.output ?? process.stdout,\n\t})\n}\n","import { existsSync } from 'node:fs'\nimport { copyFile, mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { TemplateContext, TemplateName } from '../../types'\n\n/**\n * Replaces {{variable}} placeholders in a template string with context values.\n *\n * @param template - The template string containing {{variable}} placeholders\n * @param context - Key-value pairs to substitute\n * @returns The template with all placeholders replaced\n */\nexport function substituteVariables(template: string, context: Record<string, string>): string {\n\treturn template.replace(/\\{\\{(\\w+)\\}\\}/g, (_match, key: string) => {\n\t\tconst value = context[key]\n\t\treturn value !== undefined ? value : `{{${key}}}`\n\t})\n}\n\n/**\n * Resolves the absolute path to a bundled template directory.\n *\n * After tsup bundling, import.meta.url points to dist/<file>.js (1 level from root).\n * In source, it's src/commands/create/template-engine.ts (3 levels from root).\n * We walk up from the current file to find the package root containing templates/.\n *\n * @param templateName - Name of the template (e.g. 'react-basic')\n * @returns Absolute path to the template directory\n */\nexport function getTemplatePath(templateName: TemplateName): string {\n\tlet dir = dirname(fileURLToPath(import.meta.url))\n\tfor (let i = 0; i < 5; i++) {\n\t\tif (existsSync(resolve(dir, 'templates'))) {\n\t\t\treturn resolve(dir, 'templates', templateName)\n\t\t}\n\t\tdir = dirname(dir)\n\t}\n\t// Fallback: assume bundled (1 level from package root)\n\tconst currentDir = dirname(fileURLToPath(import.meta.url))\n\treturn resolve(currentDir, '..', 'templates', templateName)\n}\n\n/**\n * Scaffolds a project from a bundled template.\n * Copies all files from the template directory to the target, applying\n * variable substitution to .hbs files and stripping the .hbs extension.\n *\n * @param templateName - Which template to use\n * @param targetDir - Destination directory (must not exist yet)\n * @param context - Variables for template substitution\n */\nexport async function scaffoldTemplate(\n\ttemplateName: TemplateName,\n\ttargetDir: string,\n\tcontext: TemplateContext,\n): Promise<void> {\n\tconst templateDir = getTemplatePath(templateName)\n\tconst vars: Record<string, string> = {\n\t\tprojectName: context.projectName,\n\t\tpackageManager: context.packageManager,\n\t\tkoraVersion: context.koraVersion,\n\t}\n\tawait copyDirectory(templateDir, targetDir, vars)\n}\n\nasync function copyDirectory(\n\tsrc: string,\n\tdest: string,\n\tvars: Record<string, string>,\n): Promise<void> {\n\tawait mkdir(dest, { recursive: true })\n\tconst entries = await readdir(src)\n\n\tfor (const entry of entries) {\n\t\tconst srcPath = join(src, entry)\n\t\tconst srcStat = await stat(srcPath)\n\n\t\tif (srcStat.isDirectory()) {\n\t\t\tawait copyDirectory(srcPath, join(dest, entry), vars)\n\t\t} else if (entry.endsWith('.hbs')) {\n\t\t\t// Template file: substitute variables and strip .hbs extension\n\t\t\tconst content = await readFile(srcPath, 'utf-8')\n\t\t\tconst outputName = entry.slice(0, -4) // Remove .hbs\n\t\t\tawait writeFile(join(dest, outputName), substituteVariables(content, vars), 'utf-8')\n\t\t} else {\n\t\t\t// Regular file: copy as-is\n\t\t\tawait copyFile(srcPath, join(dest, entry))\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["import_citty","import_node_child_process","import_node_fs","import_node_path","import_node_url","resolve","import_promises","import_node_path","import_meta"]}
package/dist/create.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createCommand
4
- } from "./chunk-AUDUNRYA.js";
5
- import "./chunk-ZVB4HAB3.js";
4
+ } from "./chunk-SYOFLJLB.js";
5
+ import "./chunk-VGOOQ56H.js";
6
6
 
7
7
  // src/create.ts
8
8
  import { runMain } from "citty";
package/dist/index.cjs CHANGED
@@ -34,17 +34,32 @@ module.exports = __toCommonJS(index_exports);
34
34
 
35
35
  // src/types.ts
36
36
  var PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
37
- var TEMPLATES = ["react-basic", "react-sync"];
37
+ var TEMPLATES = [
38
+ "react-tailwind-sync",
39
+ "react-tailwind",
40
+ "react-sync",
41
+ "react-basic"
42
+ ];
38
43
  var TEMPLATE_INFO = [
39
44
  {
40
- name: "react-basic",
41
- label: "React (basic)",
42
- description: "Local-only React app with Kora \u2014 no sync server"
45
+ name: "react-tailwind-sync",
46
+ label: "React + Tailwind (with sync)",
47
+ description: "Polished dark-themed app with Tailwind CSS and sync server (Recommended)"
48
+ },
49
+ {
50
+ name: "react-tailwind",
51
+ label: "React + Tailwind (local-only)",
52
+ description: "Polished dark-themed app with Tailwind CSS \u2014 no sync server"
43
53
  },
44
54
  {
45
55
  name: "react-sync",
46
- label: "React (with sync)",
47
- description: "React app with Kora sync server included"
56
+ label: "React + CSS (with sync)",
57
+ description: "Clean CSS app with sync server included"
58
+ },
59
+ {
60
+ name: "react-basic",
61
+ label: "React + CSS (local-only)",
62
+ description: "Clean CSS app \u2014 no sync server"
48
63
  }
49
64
  ];
50
65
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/errors.ts","../src/commands/generate/type-generator.ts"],"sourcesContent":["// @korajs/cli — public API\n// Every export here is a public API commitment. Be explicit.\n\n// === Types ===\nexport type { PackageManager, TemplateName, TemplateContext, TemplateInfo } from './types'\nexport { PACKAGE_MANAGERS, TEMPLATES, TEMPLATE_INFO } from './types'\n\n// === Errors ===\nexport {\n\tCliError,\n\tDevServerError,\n\tInvalidProjectError,\n\tProjectExistsError,\n\tSchemaNotFoundError,\n} from './errors'\n\n// === Type Generation (programmatic use) ===\nexport { generateTypes } from './commands/generate/type-generator'\n","/** Supported package managers */\nexport const PACKAGE_MANAGERS = ['pnpm', 'npm', 'yarn', 'bun'] as const\nexport type PackageManager = (typeof PACKAGE_MANAGERS)[number]\n\n/** Available project templates */\nexport const TEMPLATES = ['react-basic', 'react-sync'] as const\nexport type TemplateName = (typeof TEMPLATES)[number]\n\n/** Metadata for a project template */\nexport interface TemplateInfo {\n\tname: TemplateName\n\tlabel: string\n\tdescription: string\n}\n\n/** Available templates with their descriptions */\nexport const TEMPLATE_INFO: readonly TemplateInfo[] = [\n\t{\n\t\tname: 'react-basic',\n\t\tlabel: 'React (basic)',\n\t\tdescription: 'Local-only React app with Kora — no sync server',\n\t},\n\t{\n\t\tname: 'react-sync',\n\t\tlabel: 'React (with sync)',\n\t\tdescription: 'React app with Kora sync server included',\n\t},\n] as const\n\n/** Variables available for template substitution */\nexport interface TemplateContext {\n\tprojectName: string\n\tpackageManager: PackageManager\n\tkoraVersion: string\n}\n","import { KoraError } from '@korajs/core'\n\n/**\n * Base error class for all CLI errors.\n */\nexport class CliError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'CLI_ERROR', context)\n\t\tthis.name = 'CliError'\n\t}\n}\n\n/**\n * Thrown when the target project directory already exists.\n */\nexport class ProjectExistsError extends KoraError {\n\tconstructor(public readonly directory: string) {\n\t\tsuper(\n\t\t\t`Directory \"${directory}\" already exists. Choose a different name or remove the existing directory.`,\n\t\t\t'PROJECT_EXISTS',\n\t\t\t{ directory },\n\t\t)\n\t\tthis.name = 'ProjectExistsError'\n\t}\n}\n\n/**\n * Thrown when a schema file cannot be found in the project.\n */\nexport class SchemaNotFoundError extends KoraError {\n\tconstructor(public readonly searchedPaths: string[]) {\n\t\tsuper(\n\t\t\t`Could not find a schema file. Searched: ${searchedPaths.join(', ')}. Create a schema file using defineSchema() from @korajs/core.`,\n\t\t\t'SCHEMA_NOT_FOUND',\n\t\t\t{ searchedPaths },\n\t\t)\n\t\tthis.name = 'SchemaNotFoundError'\n\t}\n}\n\n/**\n * Thrown when a command is run outside a valid Kora project.\n */\nexport class InvalidProjectError extends KoraError {\n\tconstructor(public readonly directory: string) {\n\t\tsuper(\n\t\t\t`\"${directory}\" is not a valid Kora project. No package.json with a kora dependency found. Run this command from inside a Kora project.`,\n\t\t\t'INVALID_PROJECT',\n\t\t\t{ directory },\n\t\t)\n\t\tthis.name = 'InvalidProjectError'\n\t}\n}\n\n/**\n * Thrown when a required local dev server binary cannot be found.\n */\nexport class DevServerError extends KoraError {\n\tconstructor(\n\t\tpublic readonly binary: string,\n\t\tpublic readonly searchPath: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not find required binary \"${binary}\" at ${searchPath}. Install project dependencies and try again.`,\n\t\t\t'DEV_SERVER_ERROR',\n\t\t\t{ binary, searchPath },\n\t\t)\n\t\tthis.name = 'DevServerError'\n\t}\n}\n","import type { FieldDescriptor, FieldKind, SchemaDefinition } from '@korajs/core'\n\n/**\n * Generates TypeScript interfaces from a SchemaDefinition.\n * For each collection, produces three interfaces:\n * - {Name}Record: full record type (all fields + id)\n * - {Name}InsertInput: insert input (omit auto fields, optional for fields with defaults)\n * - {Name}UpdateInput: update input (all non-auto fields optional)\n *\n * @param schema - A validated SchemaDefinition from defineSchema()\n * @returns A complete TypeScript file as a string\n */\nexport function generateTypes(schema: SchemaDefinition): string {\n\tconst lines: string[] = [\n\t\t'// Auto-generated by @korajs/cli — do not edit manually',\n\t\t`// Generated from schema version ${String(schema.version)}`,\n\t\t'',\n\t]\n\n\tconst collectionNames = Object.keys(schema.collections)\n\tif (collectionNames.length === 0) {\n\t\treturn lines.join('\\n')\n\t}\n\n\tfor (const [name, collection] of Object.entries(schema.collections)) {\n\t\tconst pascal = toPascalCase(name)\n\t\tconst fields = collection.fields\n\n\t\t// Record type: all fields + id\n\t\tlines.push(`export interface ${pascal}Record {`)\n\t\tlines.push('\\treadonly id: string')\n\t\tfor (const [fieldName, descriptor] of Object.entries(fields)) {\n\t\t\tconst tsType = fieldKindToTypeScript(descriptor)\n\t\t\tconst optional = !descriptor.required && !descriptor.auto ? '?' : ''\n\t\t\tlines.push(`\\treadonly ${fieldName}${optional}: ${tsType}`)\n\t\t}\n\t\tlines.push('}')\n\t\tlines.push('')\n\n\t\t// Insert input: omit auto fields, optional for fields with defaults\n\t\tlines.push(`export interface ${pascal}InsertInput {`)\n\t\tfor (const [fieldName, descriptor] of Object.entries(fields)) {\n\t\t\tif (descriptor.auto) continue\n\t\t\tconst tsType = fieldKindToTypeScript(descriptor)\n\t\t\tconst optional = !descriptor.required || descriptor.defaultValue !== undefined ? '?' : ''\n\t\t\tlines.push(`\\t${fieldName}${optional}: ${tsType}`)\n\t\t}\n\t\tlines.push('}')\n\t\tlines.push('')\n\n\t\t// Update input: all non-auto fields optional\n\t\tlines.push(`export interface ${pascal}UpdateInput {`)\n\t\tfor (const [fieldName, descriptor] of Object.entries(fields)) {\n\t\t\tif (descriptor.auto) continue\n\t\t\tconst tsType = fieldKindToTypeScript(descriptor)\n\t\t\tlines.push(`\\t${fieldName}?: ${tsType}`)\n\t\t}\n\t\tlines.push('}')\n\t\tlines.push('')\n\t}\n\n\treturn lines.join('\\n')\n}\n\n/** Maps a FieldDescriptor to its TypeScript type string */\nfunction fieldKindToTypeScript(descriptor: FieldDescriptor): string {\n\tswitch (descriptor.kind) {\n\t\tcase 'string':\n\t\t\treturn 'string'\n\t\tcase 'number':\n\t\t\treturn 'number'\n\t\tcase 'boolean':\n\t\t\treturn 'boolean'\n\t\tcase 'timestamp':\n\t\t\treturn 'number'\n\t\tcase 'richtext':\n\t\t\treturn 'string'\n\t\tcase 'enum': {\n\t\t\tif (descriptor.enumValues && descriptor.enumValues.length > 0) {\n\t\t\t\treturn descriptor.enumValues.map((v) => `'${v}'`).join(' | ')\n\t\t\t}\n\t\t\treturn 'string'\n\t\t}\n\t\tcase 'array': {\n\t\t\tconst itemType = itemKindToTypeScript(descriptor.itemKind)\n\t\t\treturn `Array<${itemType}>`\n\t\t}\n\t\tdefault:\n\t\t\treturn 'unknown'\n\t}\n}\n\n/** Maps an item FieldKind to its TypeScript type string */\nfunction itemKindToTypeScript(kind: FieldKind | null): string {\n\tswitch (kind) {\n\t\tcase 'string':\n\t\t\treturn 'string'\n\t\tcase 'number':\n\t\t\treturn 'number'\n\t\tcase 'boolean':\n\t\t\treturn 'boolean'\n\t\tcase 'timestamp':\n\t\t\treturn 'number'\n\t\tcase 'richtext':\n\t\t\treturn 'string'\n\t\tcase 'enum':\n\t\t\treturn 'string'\n\t\tcase 'array':\n\t\t\treturn 'unknown[]'\n\t\tdefault:\n\t\t\treturn 'unknown'\n\t}\n}\n\n/** Converts a snake_case or kebab-case name to PascalCase */\nfunction toPascalCase(name: string): string {\n\treturn name\n\t\t.split(/[_-]/)\n\t\t.map((part) => {\n\t\t\tif (part.length === 0) return ''\n\t\t\tconst first = part[0]\n\t\t\treturn first ? first.toUpperCase() + part.slice(1) : ''\n\t\t})\n\t\t.join('')\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/errors.ts","../src/commands/generate/type-generator.ts"],"sourcesContent":["// @korajs/cli — public API\n// Every export here is a public API commitment. Be explicit.\n\n// === Types ===\nexport type { PackageManager, TemplateName, TemplateContext, TemplateInfo } from './types'\nexport { PACKAGE_MANAGERS, TEMPLATES, TEMPLATE_INFO } from './types'\n\n// === Errors ===\nexport {\n\tCliError,\n\tDevServerError,\n\tInvalidProjectError,\n\tProjectExistsError,\n\tSchemaNotFoundError,\n} from './errors'\n\n// === Type Generation (programmatic use) ===\nexport { generateTypes } from './commands/generate/type-generator'\n","/** Supported package managers */\nexport const PACKAGE_MANAGERS = ['pnpm', 'npm', 'yarn', 'bun'] as const\nexport type PackageManager = (typeof PACKAGE_MANAGERS)[number]\n\n/** Available project templates */\nexport const TEMPLATES = [\n\t'react-tailwind-sync',\n\t'react-tailwind',\n\t'react-sync',\n\t'react-basic',\n] as const\nexport type TemplateName = (typeof TEMPLATES)[number]\n\n/** Metadata for a project template */\nexport interface TemplateInfo {\n\tname: TemplateName\n\tlabel: string\n\tdescription: string\n}\n\n/** Available templates with their descriptions */\nexport const TEMPLATE_INFO: readonly TemplateInfo[] = [\n\t{\n\t\tname: 'react-tailwind-sync',\n\t\tlabel: 'React + Tailwind (with sync)',\n\t\tdescription: 'Polished dark-themed app with Tailwind CSS and sync server (Recommended)',\n\t},\n\t{\n\t\tname: 'react-tailwind',\n\t\tlabel: 'React + Tailwind (local-only)',\n\t\tdescription: 'Polished dark-themed app with Tailwind CSS — no sync server',\n\t},\n\t{\n\t\tname: 'react-sync',\n\t\tlabel: 'React + CSS (with sync)',\n\t\tdescription: 'Clean CSS app with sync server included',\n\t},\n\t{\n\t\tname: 'react-basic',\n\t\tlabel: 'React + CSS (local-only)',\n\t\tdescription: 'Clean CSS app — no sync server',\n\t},\n] as const\n\n/** Variables available for template substitution */\nexport interface TemplateContext {\n\tprojectName: string\n\tpackageManager: PackageManager\n\tkoraVersion: string\n}\n","import { KoraError } from '@korajs/core'\n\n/**\n * Base error class for all CLI errors.\n */\nexport class CliError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'CLI_ERROR', context)\n\t\tthis.name = 'CliError'\n\t}\n}\n\n/**\n * Thrown when the target project directory already exists.\n */\nexport class ProjectExistsError extends KoraError {\n\tconstructor(public readonly directory: string) {\n\t\tsuper(\n\t\t\t`Directory \"${directory}\" already exists. Choose a different name or remove the existing directory.`,\n\t\t\t'PROJECT_EXISTS',\n\t\t\t{ directory },\n\t\t)\n\t\tthis.name = 'ProjectExistsError'\n\t}\n}\n\n/**\n * Thrown when a schema file cannot be found in the project.\n */\nexport class SchemaNotFoundError extends KoraError {\n\tconstructor(public readonly searchedPaths: string[]) {\n\t\tsuper(\n\t\t\t`Could not find a schema file. Searched: ${searchedPaths.join(', ')}. Create a schema file using defineSchema() from @korajs/core.`,\n\t\t\t'SCHEMA_NOT_FOUND',\n\t\t\t{ searchedPaths },\n\t\t)\n\t\tthis.name = 'SchemaNotFoundError'\n\t}\n}\n\n/**\n * Thrown when a command is run outside a valid Kora project.\n */\nexport class InvalidProjectError extends KoraError {\n\tconstructor(public readonly directory: string) {\n\t\tsuper(\n\t\t\t`\"${directory}\" is not a valid Kora project. No package.json with a kora dependency found. Run this command from inside a Kora project.`,\n\t\t\t'INVALID_PROJECT',\n\t\t\t{ directory },\n\t\t)\n\t\tthis.name = 'InvalidProjectError'\n\t}\n}\n\n/**\n * Thrown when a required local dev server binary cannot be found.\n */\nexport class DevServerError extends KoraError {\n\tconstructor(\n\t\tpublic readonly binary: string,\n\t\tpublic readonly searchPath: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not find required binary \"${binary}\" at ${searchPath}. Install project dependencies and try again.`,\n\t\t\t'DEV_SERVER_ERROR',\n\t\t\t{ binary, searchPath },\n\t\t)\n\t\tthis.name = 'DevServerError'\n\t}\n}\n","import type { FieldDescriptor, FieldKind, SchemaDefinition } from '@korajs/core'\n\n/**\n * Generates TypeScript interfaces from a SchemaDefinition.\n * For each collection, produces three interfaces:\n * - {Name}Record: full record type (all fields + id)\n * - {Name}InsertInput: insert input (omit auto fields, optional for fields with defaults)\n * - {Name}UpdateInput: update input (all non-auto fields optional)\n *\n * @param schema - A validated SchemaDefinition from defineSchema()\n * @returns A complete TypeScript file as a string\n */\nexport function generateTypes(schema: SchemaDefinition): string {\n\tconst lines: string[] = [\n\t\t'// Auto-generated by @korajs/cli — do not edit manually',\n\t\t`// Generated from schema version ${String(schema.version)}`,\n\t\t'',\n\t]\n\n\tconst collectionNames = Object.keys(schema.collections)\n\tif (collectionNames.length === 0) {\n\t\treturn lines.join('\\n')\n\t}\n\n\tfor (const [name, collection] of Object.entries(schema.collections)) {\n\t\tconst pascal = toPascalCase(name)\n\t\tconst fields = collection.fields\n\n\t\t// Record type: all fields + id\n\t\tlines.push(`export interface ${pascal}Record {`)\n\t\tlines.push('\\treadonly id: string')\n\t\tfor (const [fieldName, descriptor] of Object.entries(fields)) {\n\t\t\tconst tsType = fieldKindToTypeScript(descriptor)\n\t\t\tconst optional = !descriptor.required && !descriptor.auto ? '?' : ''\n\t\t\tlines.push(`\\treadonly ${fieldName}${optional}: ${tsType}`)\n\t\t}\n\t\tlines.push('}')\n\t\tlines.push('')\n\n\t\t// Insert input: omit auto fields, optional for fields with defaults\n\t\tlines.push(`export interface ${pascal}InsertInput {`)\n\t\tfor (const [fieldName, descriptor] of Object.entries(fields)) {\n\t\t\tif (descriptor.auto) continue\n\t\t\tconst tsType = fieldKindToTypeScript(descriptor)\n\t\t\tconst optional = !descriptor.required || descriptor.defaultValue !== undefined ? '?' : ''\n\t\t\tlines.push(`\\t${fieldName}${optional}: ${tsType}`)\n\t\t}\n\t\tlines.push('}')\n\t\tlines.push('')\n\n\t\t// Update input: all non-auto fields optional\n\t\tlines.push(`export interface ${pascal}UpdateInput {`)\n\t\tfor (const [fieldName, descriptor] of Object.entries(fields)) {\n\t\t\tif (descriptor.auto) continue\n\t\t\tconst tsType = fieldKindToTypeScript(descriptor)\n\t\t\tlines.push(`\\t${fieldName}?: ${tsType}`)\n\t\t}\n\t\tlines.push('}')\n\t\tlines.push('')\n\t}\n\n\treturn lines.join('\\n')\n}\n\n/** Maps a FieldDescriptor to its TypeScript type string */\nfunction fieldKindToTypeScript(descriptor: FieldDescriptor): string {\n\tswitch (descriptor.kind) {\n\t\tcase 'string':\n\t\t\treturn 'string'\n\t\tcase 'number':\n\t\t\treturn 'number'\n\t\tcase 'boolean':\n\t\t\treturn 'boolean'\n\t\tcase 'timestamp':\n\t\t\treturn 'number'\n\t\tcase 'richtext':\n\t\t\treturn 'string'\n\t\tcase 'enum': {\n\t\t\tif (descriptor.enumValues && descriptor.enumValues.length > 0) {\n\t\t\t\treturn descriptor.enumValues.map((v) => `'${v}'`).join(' | ')\n\t\t\t}\n\t\t\treturn 'string'\n\t\t}\n\t\tcase 'array': {\n\t\t\tconst itemType = itemKindToTypeScript(descriptor.itemKind)\n\t\t\treturn `Array<${itemType}>`\n\t\t}\n\t\tdefault:\n\t\t\treturn 'unknown'\n\t}\n}\n\n/** Maps an item FieldKind to its TypeScript type string */\nfunction itemKindToTypeScript(kind: FieldKind | null): string {\n\tswitch (kind) {\n\t\tcase 'string':\n\t\t\treturn 'string'\n\t\tcase 'number':\n\t\t\treturn 'number'\n\t\tcase 'boolean':\n\t\t\treturn 'boolean'\n\t\tcase 'timestamp':\n\t\t\treturn 'number'\n\t\tcase 'richtext':\n\t\t\treturn 'string'\n\t\tcase 'enum':\n\t\t\treturn 'string'\n\t\tcase 'array':\n\t\t\treturn 'unknown[]'\n\t\tdefault:\n\t\t\treturn 'unknown'\n\t}\n}\n\n/** Converts a snake_case or kebab-case name to PascalCase */\nfunction toPascalCase(name: string): string {\n\treturn name\n\t\t.split(/[_-]/)\n\t\t.map((part) => {\n\t\t\tif (part.length === 0) return ''\n\t\t\tconst first = part[0]\n\t\t\treturn first ? first.toUpperCase() + part.slice(1) : ''\n\t\t})\n\t\t.join('')\n}\n"],"mappings":"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","names":[]}