@korajs/cli 0.1.1 → 0.1.3

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.
@@ -8,7 +8,7 @@ import {
8
8
 
9
9
  // src/commands/create/create-command.ts
10
10
  import { execSync as execSync2 } from "child_process";
11
- import { readFileSync } from "fs";
11
+ import { existsSync as existsSync2, readFileSync } from "fs";
12
12
  import { dirname as dirname3, resolve as resolve3 } from "path";
13
13
  import { fileURLToPath as fileURLToPath2 } from "url";
14
14
  import { defineCommand } from "citty";
@@ -59,13 +59,16 @@ async function findSchemaFile(projectRoot) {
59
59
  return null;
60
60
  }
61
61
  async function resolveProjectBinary(projectRoot, binaryName) {
62
- const binaryPath = join(projectRoot, "node_modules", ".bin", binaryName);
63
- try {
64
- await access(binaryPath);
65
- return binaryPath;
66
- } catch {
67
- return null;
62
+ const binDir = join(projectRoot, "node_modules", ".bin");
63
+ const candidates = process.platform === "win32" ? [join(binDir, `${binaryName}.cmd`), join(binDir, binaryName)] : [join(binDir, binaryName)];
64
+ for (const candidate of candidates) {
65
+ try {
66
+ await access(candidate);
67
+ return candidate;
68
+ } catch {
69
+ }
68
70
  }
71
+ return null;
69
72
  }
70
73
  function isKoraProject(pkg) {
71
74
  if (typeof pkg !== "object" || pkg === null) return false;
@@ -217,6 +220,7 @@ function createReadline(options) {
217
220
  }
218
221
 
219
222
  // src/commands/create/template-engine.ts
223
+ import { existsSync } from "fs";
220
224
  import { copyFile, mkdir, readFile as readFile2, readdir, stat, writeFile } from "fs/promises";
221
225
  import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
222
226
  import { fileURLToPath } from "url";
@@ -227,8 +231,15 @@ function substituteVariables(template, context) {
227
231
  });
228
232
  }
229
233
  function getTemplatePath(templateName) {
234
+ let dir = dirname2(fileURLToPath(import.meta.url));
235
+ for (let i = 0; i < 5; i++) {
236
+ if (existsSync(resolve2(dir, "templates"))) {
237
+ return resolve2(dir, "templates", templateName);
238
+ }
239
+ dir = dirname2(dir);
240
+ }
230
241
  const currentDir = dirname2(fileURLToPath(import.meta.url));
231
- return resolve2(currentDir, "..", "..", "..", "templates", templateName);
242
+ return resolve2(currentDir, "..", "templates", templateName);
232
243
  }
233
244
  async function scaffoldTemplate(templateName, targetDir, context) {
234
245
  const templateDir = getTemplatePath(templateName);
@@ -351,9 +362,18 @@ function isValidPackageManager(value) {
351
362
  }
352
363
  function resolveKoraVersion() {
353
364
  try {
354
- const cliDir = resolve3(dirname3(fileURLToPath2(import.meta.url)), "..", "..", "..");
355
- const pkg = JSON.parse(readFileSync(resolve3(cliDir, "package.json"), "utf-8"));
356
- return pkg.version === "0.0.0" ? "latest" : `^${pkg.version}`;
365
+ let dir = dirname3(fileURLToPath2(import.meta.url));
366
+ for (let i = 0; i < 5; i++) {
367
+ const pkgPath = resolve3(dir, "package.json");
368
+ if (existsSync2(pkgPath)) {
369
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
370
+ if (pkg.name === "@korajs/cli") {
371
+ return pkg.version === "0.0.0" ? "latest" : `^${pkg.version}`;
372
+ }
373
+ }
374
+ dir = dirname3(dir);
375
+ }
376
+ return "latest";
357
377
  } catch {
358
378
  return "latest";
359
379
  }
@@ -367,4 +387,4 @@ export {
367
387
  promptConfirm,
368
388
  createCommand
369
389
  };
370
- //# sourceMappingURL=chunk-REOTYAM6.js.map
390
+ //# sourceMappingURL=chunk-BVPZDTYV.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: '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.\n * Scaffolded projects pin their kora dependencies to this version.\n * Walks up from the current file to find the package root (works both\n * in source and after tsup bundling).\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\treturn pkg.version === '0.0.0' ? 'latest' : `^${pkg.version}`\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\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"]}
File without changes
File without changes
package/dist/create.cjs CHANGED
@@ -5,7 +5,7 @@ var import_citty2 = require("citty");
5
5
 
6
6
  // src/commands/create/create-command.ts
7
7
  var import_node_child_process2 = require("child_process");
8
- var import_node_fs = require("fs");
8
+ var import_node_fs2 = require("fs");
9
9
  var import_node_path3 = require("path");
10
10
  var import_node_url2 = require("url");
11
11
  var import_citty = require("citty");
@@ -164,6 +164,7 @@ function createReadline(options) {
164
164
  }
165
165
 
166
166
  // src/commands/create/template-engine.ts
167
+ var import_node_fs = require("fs");
167
168
  var import_promises2 = require("fs/promises");
168
169
  var import_node_path2 = require("path");
169
170
  var import_node_url = require("url");
@@ -175,8 +176,15 @@ function substituteVariables(template, context) {
175
176
  });
176
177
  }
177
178
  function getTemplatePath(templateName) {
179
+ let dir = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
180
+ for (let i = 0; i < 5; i++) {
181
+ if ((0, import_node_fs.existsSync)((0, import_node_path2.resolve)(dir, "templates"))) {
182
+ return (0, import_node_path2.resolve)(dir, "templates", templateName);
183
+ }
184
+ dir = (0, import_node_path2.dirname)(dir);
185
+ }
178
186
  const currentDir = (0, import_node_path2.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
179
- return (0, import_node_path2.resolve)(currentDir, "..", "..", "..", "templates", templateName);
187
+ return (0, import_node_path2.resolve)(currentDir, "..", "templates", templateName);
180
188
  }
181
189
  async function scaffoldTemplate(templateName, targetDir, context) {
182
190
  const templateDir = getTemplatePath(templateName);
@@ -300,9 +308,18 @@ function isValidPackageManager(value) {
300
308
  }
301
309
  function resolveKoraVersion() {
302
310
  try {
303
- const cliDir = (0, import_node_path3.resolve)((0, import_node_path3.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url)), "..", "..", "..");
304
- const pkg = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path3.resolve)(cliDir, "package.json"), "utf-8"));
305
- return pkg.version === "0.0.0" ? "latest" : `^${pkg.version}`;
311
+ let dir = (0, import_node_path3.dirname)((0, import_node_url2.fileURLToPath)(import_meta2.url));
312
+ for (let i = 0; i < 5; i++) {
313
+ const pkgPath = (0, import_node_path3.resolve)(dir, "package.json");
314
+ if ((0, import_node_fs2.existsSync)(pkgPath)) {
315
+ const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
316
+ if (pkg.name === "@korajs/cli") {
317
+ return pkg.version === "0.0.0" ? "latest" : `^${pkg.version}`;
318
+ }
319
+ }
320
+ dir = (0, import_node_path3.dirname)(dir);
321
+ }
322
+ return "latest";
306
323
  } catch {
307
324
  return "latest";
308
325
  }
@@ -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 { 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.\n * Scaffolded projects pin their kora dependencies to this version.\n */\nfunction resolveKoraVersion(): string {\n\ttry {\n\t\tconst cliDir = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')\n\t\tconst pkg = JSON.parse(readFileSync(resolve(cliDir, 'package.json'), 'utf-8')) as { version: string }\n\t\treturn pkg.version === '0.0.0' ? 'latest' : `^${pkg.version}`\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 *\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 binaryPath = join(projectRoot, 'node_modules', '.bin', binaryName)\n\n\ttry {\n\t\tawait access(binaryPath)\n\t\treturn binaryPath\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 { 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 * @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\t// Navigate from src/commands/create/ up to package root, then into templates/\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_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: '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.\n * Scaffolded projects pin their kora dependencies to this version.\n * Walks up from the current file to find the package root (works both\n * in source and after tsup bundling).\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\treturn pkg.version === '0.0.0' ? 'latest' : `^${pkg.version}`\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\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,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createCommand
4
- } from "./chunk-REOTYAM6.js";
4
+ } from "./chunk-BVPZDTYV.js";
5
5
  import "./chunk-ZVB4HAB3.js";
6
6
 
7
7
  // src/create.ts
package/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@korajs/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Kora.js CLI tooling and project scaffolding",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -30,16 +30,6 @@
30
30
  "dist",
31
31
  "templates"
32
32
  ],
33
- "dependencies": {
34
- "citty": "^0.1.6",
35
- "@korajs/core": "0.1.0"
36
- },
37
- "devDependencies": {
38
- "typescript": "^5.7.3",
39
- "tsup": "^8.3.6",
40
- "vitest": "^3.0.4"
41
- },
42
- "license": "MIT",
43
33
  "scripts": {
44
34
  "build": "tsup",
45
35
  "dev": "tsup --watch",
@@ -48,5 +38,15 @@
48
38
  "typecheck": "tsc --noEmit",
49
39
  "lint": "biome check src/ tests/",
50
40
  "clean": "rm -rf dist .turbo coverage"
51
- }
52
- }
41
+ },
42
+ "dependencies": {
43
+ "@korajs/core": "workspace:*",
44
+ "citty": "^0.1.6"
45
+ },
46
+ "devDependencies": {
47
+ "typescript": "^5.7.3",
48
+ "tsup": "^8.3.6",
49
+ "vitest": "^3.0.4"
50
+ },
51
+ "license": "MIT"
52
+ }
@@ -1 +0,0 @@
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 { 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.\n * Scaffolded projects pin their kora dependencies to this version.\n */\nfunction resolveKoraVersion(): string {\n\ttry {\n\t\tconst cliDir = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')\n\t\tconst pkg = JSON.parse(readFileSync(resolve(cliDir, 'package.json'), 'utf-8')) as { version: string }\n\t\treturn pkg.version === '0.0.0' ? 'latest' : `^${pkg.version}`\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 *\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 binaryPath = join(projectRoot, 'node_modules', '.bin', binaryName)\n\n\ttry {\n\t\tawait access(binaryPath)\n\t\treturn binaryPath\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 { 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 * @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\t// Navigate from src/commands/create/ up to package root, then into templates/\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","dirname","resolve","fileURLToPath","resolve","readFile","dirname","join","resolve","resolve","execSync","dirname","fileURLToPath"]}