@betterstart/cli 0.1.1 → 0.1.2

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.
@@ -11,6 +11,7 @@ import {
11
11
  // src/config/loader.ts
12
12
  import fs from "fs";
13
13
  import path from "path";
14
+ import { fileURLToPath } from "url";
14
15
  var CONFIG_FILE_NAMES = [
15
16
  "betterstart.config.ts",
16
17
  "betterstart.config.js",
@@ -38,7 +39,12 @@ async function loadConfigFile(configPath) {
38
39
  }
39
40
  if (ext === ".ts") {
40
41
  const { createJiti } = await import("jiti");
41
- const jiti = createJiti(import.meta.url);
42
+ const alias = {};
43
+ try {
44
+ alias["@betterstart/cli"] = fileURLToPath(import.meta.resolve("@betterstart/cli"));
45
+ } catch {
46
+ }
47
+ const jiti = createJiti(import.meta.url, { alias });
42
48
  const module = await jiti.import(configPath);
43
49
  return module.default || module;
44
50
  }
@@ -226,4 +232,4 @@ export {
226
232
  validateConfig,
227
233
  checkPaths
228
234
  };
229
- //# sourceMappingURL=chunk-J2XUG4RG.js.map
235
+ //# sourceMappingURL=chunk-QLVSHP7X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/loader.ts"],"sourcesContent":["/**\n * Configuration loader for @betterstart/cli\n * Handles loading, merging, and resolving configuration\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { ConfigurationError } from '../core/errors'\nimport { getLogger } from '../core/logger'\nimport { detectPreset, getDefaultConfig, getPreset } from './presets'\nimport type { BetterstartConfig, ResolvedConfig, ResolvedPaths, UserConfig } from './types'\n\n// ============================================================================\n// Configuration File Names\n// ============================================================================\n\n/**\n * Supported configuration file names (in priority order)\n */\nexport const CONFIG_FILE_NAMES = [\n 'betterstart.config.ts',\n 'betterstart.config.js',\n 'betterstart.config.mjs',\n 'betterstart.config.json',\n '.betterstartrc.json',\n '.betterstartrc'\n]\n\n// ============================================================================\n// Configuration Loading\n// ============================================================================\n\n/**\n * Find the configuration file in a directory\n * @param cwd - Directory to search in\n * @returns Path to the config file or undefined if not found\n */\nexport function findConfigFile(cwd: string): string | undefined {\n for (const fileName of CONFIG_FILE_NAMES) {\n const filePath = path.join(cwd, fileName)\n if (fs.existsSync(filePath)) {\n return filePath\n }\n }\n return undefined\n}\n\n/**\n * Load configuration from a file\n * @param configPath - Path to the configuration file\n * @returns The loaded configuration\n */\nexport async function loadConfigFile(configPath: string): Promise<UserConfig> {\n const ext = path.extname(configPath)\n const logger = getLogger()\n\n try {\n if (ext === '.json' || configPath.endsWith('.betterstartrc')) {\n // Load JSON config\n const content = fs.readFileSync(configPath, 'utf-8')\n return JSON.parse(content) as UserConfig\n }\n\n if (ext === '.ts') {\n // Use jiti to transpile TypeScript config at runtime\n const { createJiti } = await import('jiti')\n // Alias @betterstart/cli so jiti can resolve it even when the CLI\n // is invoked via npx and isn't installed in the project's node_modules\n const alias: Record<string, string> = {}\n try {\n alias['@betterstart/cli'] = fileURLToPath(import.meta.resolve('@betterstart/cli'))\n } catch {\n // Not critical — config may not import from us\n }\n const jiti = createJiti(import.meta.url, { alias })\n const module = await jiti.import(configPath)\n return ((module as Record<string, unknown>).default || module) as UserConfig\n }\n\n if (ext === '.js' || ext === '.mjs') {\n const configUrl = `file://${configPath}`\n const module = await import(configUrl)\n return (module.default || module) as UserConfig\n }\n\n throw new ConfigurationError(`Unsupported config file type: ${ext}`, { path: configPath })\n } catch (error) {\n if (error instanceof ConfigurationError) {\n throw error\n }\n logger.error(`Failed to load config file: ${configPath}`)\n throw new ConfigurationError(\n `Failed to load configuration from ${configPath}: ${error instanceof Error ? error.message : String(error)}`,\n { path: configPath, originalError: error }\n )\n }\n}\n\n/**\n * Deep merge two configuration objects\n * Arrays are replaced, not merged\n */\nfunction deepMerge<T extends Record<string, unknown>>(base: T, override: Partial<T>): T {\n const result = { ...base }\n\n for (const key of Object.keys(override) as Array<keyof T>) {\n const baseValue = base[key]\n const overrideValue = override[key]\n\n if (overrideValue === undefined) {\n continue\n }\n\n if (\n typeof baseValue === 'object' &&\n baseValue !== null &&\n !Array.isArray(baseValue) &&\n typeof overrideValue === 'object' &&\n overrideValue !== null &&\n !Array.isArray(overrideValue)\n ) {\n // Recursively merge objects\n result[key] = deepMerge(\n baseValue as Record<string, unknown>,\n overrideValue as Record<string, unknown>\n ) as T[keyof T]\n } else {\n // Replace arrays and primitives\n result[key] = overrideValue as T[keyof T]\n }\n }\n\n return result\n}\n\n/**\n * Load configuration from a directory\n * Automatically finds and loads the config file, or uses defaults\n *\n * @param cwd - Directory to load config from (defaults to process.cwd())\n * @param presetName - Optional preset name to use as base\n * @returns The loaded and merged configuration\n */\nexport async function loadConfig(cwd?: string, presetName?: string): Promise<BetterstartConfig> {\n const workingDir = cwd || process.cwd()\n const logger = getLogger()\n\n // Get base configuration from preset or auto-detection\n let baseConfig: UserConfig\n if (presetName) {\n const preset = getPreset(presetName)\n if (!preset) {\n throw new ConfigurationError(`Unknown preset: ${presetName}`, {\n availablePresets: ['nextjs-monorepo', 'nextjs-standalone', 'custom']\n })\n }\n baseConfig = preset.config\n logger.debug(`Using preset: ${presetName}`)\n } else {\n baseConfig = getDefaultConfig(workingDir)\n const detectedPreset = detectPreset(workingDir)\n logger.debug(`Auto-detected preset: ${detectedPreset.name}`)\n }\n\n // Look for config file\n const configPath = findConfigFile(workingDir)\n let userConfig: UserConfig = {}\n\n if (configPath) {\n logger.debug(`Loading config from: ${configPath}`)\n userConfig = await loadConfigFile(configPath)\n }\n\n // Merge configurations\n const mergedConfig = deepMerge(\n baseConfig as Record<string, unknown>,\n userConfig as Record<string, unknown>\n )\n\n return mergedConfig as unknown as BetterstartConfig\n}\n\n// ============================================================================\n// Path Resolution\n// ============================================================================\n\n/**\n * Find the project root directory\n * Looks for common root indicators (package.json, pnpm-workspace.yaml, turbo.json)\n *\n * @param startDir - Directory to start searching from\n * @returns The project root directory\n */\nexport function findProjectRoot(startDir: string): string {\n let currentDir = path.resolve(startDir)\n const _rootIndicators = ['pnpm-workspace.yaml', 'turbo.json', 'package.json']\n\n // Walk up the directory tree\n while (currentDir !== path.dirname(currentDir)) {\n // Check for monorepo root indicators first\n if (\n fs.existsSync(path.join(currentDir, 'pnpm-workspace.yaml')) ||\n fs.existsSync(path.join(currentDir, 'turbo.json'))\n ) {\n return currentDir\n }\n\n // Check for package.json\n if (fs.existsSync(path.join(currentDir, 'package.json'))) {\n // If there's a package.json but no monorepo indicators,\n // check if parent has monorepo indicators\n const parentDir = path.dirname(currentDir)\n if (\n fs.existsSync(path.join(parentDir, 'pnpm-workspace.yaml')) ||\n fs.existsSync(path.join(parentDir, 'turbo.json'))\n ) {\n return parentDir\n }\n // Otherwise, this package.json is our root\n return currentDir\n }\n\n currentDir = path.dirname(currentDir)\n }\n\n // Fallback to start directory\n return path.resolve(startDir)\n}\n\n/**\n * Resolve configuration paths to absolute paths\n *\n * @param config - The configuration to resolve\n * @param projectRoot - The project root directory\n * @returns Configuration with resolved absolute paths\n */\nexport function resolvePaths(config: BetterstartConfig, projectRoot: string): ResolvedPaths {\n const root = path.resolve(projectRoot)\n\n // Resolve main paths\n const appPath = path.resolve(root, config.paths.app)\n const databasePath = path.resolve(root, config.paths.database)\n const libPath = path.resolve(root, config.paths.lib)\n const hooksPath = path.resolve(root, config.paths.hooks)\n const schemasPath = path.resolve(root, config.paths.schemas)\n\n // Resolve output paths relative to their parent packages\n const outputPaths = {\n actions: path.resolve(libPath, config.paths.output.actions),\n hooks: path.resolve(hooksPath, config.paths.output.hooks),\n components: path.resolve(appPath, config.paths.output.components),\n pages: path.resolve(appPath, config.paths.output.pages),\n emails: path.resolve(appPath, config.paths.output.emails)\n }\n\n return {\n root,\n app: appPath,\n database: databasePath,\n lib: libPath,\n hooks: hooksPath,\n schemas: schemasPath,\n output: outputPaths\n }\n}\n\n/**\n * Fully resolve configuration including paths\n *\n * @param config - The configuration to resolve\n * @param projectRoot - The project root directory (optional, auto-detected if not provided)\n * @returns Fully resolved configuration\n */\nexport function resolveConfig(config: BetterstartConfig, projectRoot?: string): ResolvedConfig {\n const root = projectRoot || findProjectRoot(process.cwd())\n const resolvedPaths = resolvePaths(config, root)\n\n return {\n ...config,\n paths: resolvedPaths\n }\n}\n\n// ============================================================================\n// Configuration Helper\n// ============================================================================\n\n/**\n * Helper function for defining configuration in betterstart.config.ts\n * Provides type checking and autocomplete\n *\n * @example\n * ```ts\n * // betterstart.config.ts\n * import { defineConfig } from '@betterstart/cli'\n *\n * export default defineConfig({\n * paths: {\n * app: 'apps/web',\n * database: 'packages/database'\n * }\n * })\n * ```\n */\nexport function defineConfig(config: UserConfig): UserConfig {\n return config\n}\n\n// ============================================================================\n// Validation\n// ============================================================================\n\n/**\n * Validate a configuration\n * @param config - The configuration to validate\n * @returns Array of validation errors (empty if valid)\n */\nexport function validateConfig(config: BetterstartConfig): string[] {\n const errors: string[] = []\n\n // Validate paths\n if (!config.paths) {\n errors.push('Configuration must have a \"paths\" object')\n } else {\n if (!config.paths.app) errors.push('paths.app is required')\n if (!config.paths.database) errors.push('paths.database is required')\n if (!config.paths.lib) errors.push('paths.lib is required')\n if (!config.paths.hooks) errors.push('paths.hooks is required')\n if (!config.paths.schemas) errors.push('paths.schemas is required')\n }\n\n // Validate database config\n if (!config.database) {\n errors.push('Configuration must have a \"database\" object')\n } else {\n if (config.database.provider !== 'drizzle') {\n errors.push('database.provider must be \"drizzle\"')\n }\n }\n\n // Validate UI config\n if (!config.ui) {\n errors.push('Configuration must have a \"ui\" object')\n } else {\n if (config.ui.framework !== 'shadcn') {\n errors.push('ui.framework must be \"shadcn\"')\n }\n }\n\n return errors\n}\n\n/**\n * Check if resolved paths exist and are accessible\n * @param paths - The resolved paths to check\n * @returns Object with path existence status\n */\nexport function checkPaths(paths: ResolvedPaths): Record<keyof ResolvedPaths, boolean> {\n const exists = (p: string) => {\n try {\n fs.accessSync(p)\n return true\n } catch {\n return false\n }\n }\n\n return {\n root: exists(paths.root),\n app: exists(paths.app),\n database: exists(paths.database),\n lib: exists(paths.lib),\n hooks: exists(paths.hooks),\n schemas: exists(paths.schemas),\n output: {\n actions: exists(paths.output.actions),\n hooks: exists(paths.output.hooks),\n components: exists(paths.output.components),\n pages: exists(paths.output.pages),\n emails: exists(paths.output.emails)\n }\n } as unknown as Record<keyof ResolvedPaths, boolean>\n}\n"],"mappings":";;;;;;;;;;;AAKA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAavB,IAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAWO,SAAS,eAAe,KAAiC;AAC9D,aAAW,YAAY,mBAAmB;AACxC,UAAM,WAAW,KAAK,KAAK,KAAK,QAAQ;AACxC,QAAI,GAAG,WAAW,QAAQ,GAAG;AAC3B,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAOA,eAAsB,eAAe,YAAyC;AAC5E,QAAM,MAAM,KAAK,QAAQ,UAAU;AACnC,QAAM,SAAS,UAAU;AAEzB,MAAI;AACF,QAAI,QAAQ,WAAW,WAAW,SAAS,gBAAgB,GAAG;AAE5D,YAAM,UAAU,GAAG,aAAa,YAAY,OAAO;AACnD,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B;AAEA,QAAI,QAAQ,OAAO;AAEjB,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,MAAM;AAG1C,YAAM,QAAgC,CAAC;AACvC,UAAI;AACF,cAAM,kBAAkB,IAAI,cAAc,YAAY,QAAQ,kBAAkB,CAAC;AAAA,MACnF,QAAQ;AAAA,MAER;AACA,YAAM,OAAO,WAAW,YAAY,KAAK,EAAE,MAAM,CAAC;AAClD,YAAM,SAAS,MAAM,KAAK,OAAO,UAAU;AAC3C,aAAS,OAAmC,WAAW;AAAA,IACzD;AAEA,QAAI,QAAQ,SAAS,QAAQ,QAAQ;AACnC,YAAM,YAAY,UAAU,UAAU;AACtC,YAAM,SAAS,MAAM,OAAO;AAC5B,aAAQ,OAAO,WAAW;AAAA,IAC5B;AAEA,UAAM,IAAI,mBAAmB,iCAAiC,GAAG,IAAI,EAAE,MAAM,WAAW,CAAC;AAAA,EAC3F,SAAS,OAAO;AACd,QAAI,iBAAiB,oBAAoB;AACvC,YAAM;AAAA,IACR;AACA,WAAO,MAAM,+BAA+B,UAAU,EAAE;AACxD,UAAM,IAAI;AAAA,MACR,qCAAqC,UAAU,KAAK,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MAC1G,EAAE,MAAM,YAAY,eAAe,MAAM;AAAA,IAC3C;AAAA,EACF;AACF;AAMA,SAAS,UAA6C,MAAS,UAAyB;AACtF,QAAM,SAAS,EAAE,GAAG,KAAK;AAEzB,aAAW,OAAO,OAAO,KAAK,QAAQ,GAAqB;AACzD,UAAM,YAAY,KAAK,GAAG;AAC1B,UAAM,gBAAgB,SAAS,GAAG;AAElC,QAAI,kBAAkB,QAAW;AAC/B;AAAA,IACF;AAEA,QACE,OAAO,cAAc,YACrB,cAAc,QACd,CAAC,MAAM,QAAQ,SAAS,KACxB,OAAO,kBAAkB,YACzB,kBAAkB,QAClB,CAAC,MAAM,QAAQ,aAAa,GAC5B;AAEA,aAAO,GAAG,IAAI;AAAA,QACZ;AAAA,QACA;AAAA,MACF;AAAA,IACF,OAAO;AAEL,aAAO,GAAG,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AACT;AAUA,eAAsB,WAAW,KAAc,YAAiD;AAC9F,QAAM,aAAa,OAAO,QAAQ,IAAI;AACtC,QAAM,SAAS,UAAU;AAGzB,MAAI;AACJ,MAAI,YAAY;AACd,UAAM,SAAS,UAAU,UAAU;AACnC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,mBAAmB,mBAAmB,UAAU,IAAI;AAAA,QAC5D,kBAAkB,CAAC,mBAAmB,qBAAqB,QAAQ;AAAA,MACrE,CAAC;AAAA,IACH;AACA,iBAAa,OAAO;AACpB,WAAO,MAAM,iBAAiB,UAAU,EAAE;AAAA,EAC5C,OAAO;AACL,iBAAa,iBAAiB,UAAU;AACxC,UAAM,iBAAiB,aAAa,UAAU;AAC9C,WAAO,MAAM,yBAAyB,eAAe,IAAI,EAAE;AAAA,EAC7D;AAGA,QAAM,aAAa,eAAe,UAAU;AAC5C,MAAI,aAAyB,CAAC;AAE9B,MAAI,YAAY;AACd,WAAO,MAAM,wBAAwB,UAAU,EAAE;AACjD,iBAAa,MAAM,eAAe,UAAU;AAAA,EAC9C;AAGA,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AACT;AAaO,SAAS,gBAAgB,UAA0B;AACxD,MAAI,aAAa,KAAK,QAAQ,QAAQ;AACtC,QAAM,kBAAkB,CAAC,uBAAuB,cAAc,cAAc;AAG5E,SAAO,eAAe,KAAK,QAAQ,UAAU,GAAG;AAE9C,QACE,GAAG,WAAW,KAAK,KAAK,YAAY,qBAAqB,CAAC,KAC1D,GAAG,WAAW,KAAK,KAAK,YAAY,YAAY,CAAC,GACjD;AACA,aAAO;AAAA,IACT;AAGA,QAAI,GAAG,WAAW,KAAK,KAAK,YAAY,cAAc,CAAC,GAAG;AAGxD,YAAM,YAAY,KAAK,QAAQ,UAAU;AACzC,UACE,GAAG,WAAW,KAAK,KAAK,WAAW,qBAAqB,CAAC,KACzD,GAAG,WAAW,KAAK,KAAK,WAAW,YAAY,CAAC,GAChD;AACA,eAAO;AAAA,MACT;AAEA,aAAO;AAAA,IACT;AAEA,iBAAa,KAAK,QAAQ,UAAU;AAAA,EACtC;AAGA,SAAO,KAAK,QAAQ,QAAQ;AAC9B;AASO,SAAS,aAAa,QAA2B,aAAoC;AAC1F,QAAM,OAAO,KAAK,QAAQ,WAAW;AAGrC,QAAM,UAAU,KAAK,QAAQ,MAAM,OAAO,MAAM,GAAG;AACnD,QAAM,eAAe,KAAK,QAAQ,MAAM,OAAO,MAAM,QAAQ;AAC7D,QAAM,UAAU,KAAK,QAAQ,MAAM,OAAO,MAAM,GAAG;AACnD,QAAM,YAAY,KAAK,QAAQ,MAAM,OAAO,MAAM,KAAK;AACvD,QAAM,cAAc,KAAK,QAAQ,MAAM,OAAO,MAAM,OAAO;AAG3D,QAAM,cAAc;AAAA,IAClB,SAAS,KAAK,QAAQ,SAAS,OAAO,MAAM,OAAO,OAAO;AAAA,IAC1D,OAAO,KAAK,QAAQ,WAAW,OAAO,MAAM,OAAO,KAAK;AAAA,IACxD,YAAY,KAAK,QAAQ,SAAS,OAAO,MAAM,OAAO,UAAU;AAAA,IAChE,OAAO,KAAK,QAAQ,SAAS,OAAO,MAAM,OAAO,KAAK;AAAA,IACtD,QAAQ,KAAK,QAAQ,SAAS,OAAO,MAAM,OAAO,MAAM;AAAA,EAC1D;AAEA,SAAO;AAAA,IACL;AAAA,IACA,KAAK;AAAA,IACL,UAAU;AAAA,IACV,KAAK;AAAA,IACL,OAAO;AAAA,IACP,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AACF;AASO,SAAS,cAAc,QAA2B,aAAsC;AAC7F,QAAM,OAAO,eAAe,gBAAgB,QAAQ,IAAI,CAAC;AACzD,QAAM,gBAAgB,aAAa,QAAQ,IAAI;AAE/C,SAAO;AAAA,IACL,GAAG;AAAA,IACH,OAAO;AAAA,EACT;AACF;AAuBO,SAAS,aAAa,QAAgC;AAC3D,SAAO;AACT;AAWO,SAAS,eAAe,QAAqC;AAClE,QAAM,SAAmB,CAAC;AAG1B,MAAI,CAAC,OAAO,OAAO;AACjB,WAAO,KAAK,0CAA0C;AAAA,EACxD,OAAO;AACL,QAAI,CAAC,OAAO,MAAM,IAAK,QAAO,KAAK,uBAAuB;AAC1D,QAAI,CAAC,OAAO,MAAM,SAAU,QAAO,KAAK,4BAA4B;AACpE,QAAI,CAAC,OAAO,MAAM,IAAK,QAAO,KAAK,uBAAuB;AAC1D,QAAI,CAAC,OAAO,MAAM,MAAO,QAAO,KAAK,yBAAyB;AAC9D,QAAI,CAAC,OAAO,MAAM,QAAS,QAAO,KAAK,2BAA2B;AAAA,EACpE;AAGA,MAAI,CAAC,OAAO,UAAU;AACpB,WAAO,KAAK,6CAA6C;AAAA,EAC3D,OAAO;AACL,QAAI,OAAO,SAAS,aAAa,WAAW;AAC1C,aAAO,KAAK,qCAAqC;AAAA,IACnD;AAAA,EACF;AAGA,MAAI,CAAC,OAAO,IAAI;AACd,WAAO,KAAK,uCAAuC;AAAA,EACrD,OAAO;AACL,QAAI,OAAO,GAAG,cAAc,UAAU;AACpC,aAAO,KAAK,+BAA+B;AAAA,IAC7C;AAAA,EACF;AAEA,SAAO;AACT;AAOO,SAAS,WAAW,OAA4D;AACrF,QAAM,SAAS,CAAC,MAAc;AAC5B,QAAI;AACF,SAAG,WAAW,CAAC;AACf,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,OAAO,MAAM,IAAI;AAAA,IACvB,KAAK,OAAO,MAAM,GAAG;AAAA,IACrB,UAAU,OAAO,MAAM,QAAQ;AAAA,IAC/B,KAAK,OAAO,MAAM,GAAG;AAAA,IACrB,OAAO,OAAO,MAAM,KAAK;AAAA,IACzB,SAAS,OAAO,MAAM,OAAO;AAAA,IAC7B,QAAQ;AAAA,MACN,SAAS,OAAO,MAAM,OAAO,OAAO;AAAA,MACpC,OAAO,OAAO,MAAM,OAAO,KAAK;AAAA,MAChC,YAAY,OAAO,MAAM,OAAO,UAAU;AAAA,MAC1C,OAAO,OAAO,MAAM,OAAO,KAAK;AAAA,MAChC,QAAQ,OAAO,MAAM,OAAO,MAAM;AAAA,IACpC;AAAA,EACF;AACF;","names":[]}
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  findProjectRoot,
4
4
  loadConfig,
5
5
  resolveConfig
6
- } from "./chunk-J2XUG4RG.js";
6
+ } from "./chunk-QLVSHP7X.js";
7
7
  import {
8
8
  toKebabCase,
9
9
  toPascalCase
@@ -9,7 +9,7 @@ import {
9
9
  resolveConfig,
10
10
  resolvePaths,
11
11
  validateConfig
12
- } from "../chunk-J2XUG4RG.js";
12
+ } from "../chunk-QLVSHP7X.js";
13
13
  import {
14
14
  DEFAULT_DATABASE_CONFIG,
15
15
  DEFAULT_GENERATOR_CONFIG,
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  findProjectRoot,
4
4
  loadConfig,
5
5
  resolveConfig
6
- } from "./chunk-J2XUG4RG.js";
6
+ } from "./chunk-QLVSHP7X.js";
7
7
  import {
8
8
  ensureDir,
9
9
  getImportResolver,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@betterstart/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Generate admin CRUD interfaces for Next.js projects with Drizzle and shadcn/ui",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config/loader.ts"],"sourcesContent":["/**\n * Configuration loader for @betterstart/cli\n * Handles loading, merging, and resolving configuration\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { ConfigurationError } from '../core/errors'\nimport { getLogger } from '../core/logger'\nimport { detectPreset, getDefaultConfig, getPreset } from './presets'\nimport type { BetterstartConfig, ResolvedConfig, ResolvedPaths, UserConfig } from './types'\n\n// ============================================================================\n// Configuration File Names\n// ============================================================================\n\n/**\n * Supported configuration file names (in priority order)\n */\nexport const CONFIG_FILE_NAMES = [\n 'betterstart.config.ts',\n 'betterstart.config.js',\n 'betterstart.config.mjs',\n 'betterstart.config.json',\n '.betterstartrc.json',\n '.betterstartrc'\n]\n\n// ============================================================================\n// Configuration Loading\n// ============================================================================\n\n/**\n * Find the configuration file in a directory\n * @param cwd - Directory to search in\n * @returns Path to the config file or undefined if not found\n */\nexport function findConfigFile(cwd: string): string | undefined {\n for (const fileName of CONFIG_FILE_NAMES) {\n const filePath = path.join(cwd, fileName)\n if (fs.existsSync(filePath)) {\n return filePath\n }\n }\n return undefined\n}\n\n/**\n * Load configuration from a file\n * @param configPath - Path to the configuration file\n * @returns The loaded configuration\n */\nexport async function loadConfigFile(configPath: string): Promise<UserConfig> {\n const ext = path.extname(configPath)\n const logger = getLogger()\n\n try {\n if (ext === '.json' || configPath.endsWith('.betterstartrc')) {\n // Load JSON config\n const content = fs.readFileSync(configPath, 'utf-8')\n return JSON.parse(content) as UserConfig\n }\n\n if (ext === '.ts') {\n // Use jiti to transpile TypeScript config at runtime\n const { createJiti } = await import('jiti')\n const jiti = createJiti(import.meta.url)\n const module = await jiti.import(configPath)\n return ((module as Record<string, unknown>).default || module) as UserConfig\n }\n\n if (ext === '.js' || ext === '.mjs') {\n const configUrl = `file://${configPath}`\n const module = await import(configUrl)\n return (module.default || module) as UserConfig\n }\n\n throw new ConfigurationError(`Unsupported config file type: ${ext}`, { path: configPath })\n } catch (error) {\n if (error instanceof ConfigurationError) {\n throw error\n }\n logger.error(`Failed to load config file: ${configPath}`)\n throw new ConfigurationError(\n `Failed to load configuration from ${configPath}: ${error instanceof Error ? error.message : String(error)}`,\n { path: configPath, originalError: error }\n )\n }\n}\n\n/**\n * Deep merge two configuration objects\n * Arrays are replaced, not merged\n */\nfunction deepMerge<T extends Record<string, unknown>>(base: T, override: Partial<T>): T {\n const result = { ...base }\n\n for (const key of Object.keys(override) as Array<keyof T>) {\n const baseValue = base[key]\n const overrideValue = override[key]\n\n if (overrideValue === undefined) {\n continue\n }\n\n if (\n typeof baseValue === 'object' &&\n baseValue !== null &&\n !Array.isArray(baseValue) &&\n typeof overrideValue === 'object' &&\n overrideValue !== null &&\n !Array.isArray(overrideValue)\n ) {\n // Recursively merge objects\n result[key] = deepMerge(\n baseValue as Record<string, unknown>,\n overrideValue as Record<string, unknown>\n ) as T[keyof T]\n } else {\n // Replace arrays and primitives\n result[key] = overrideValue as T[keyof T]\n }\n }\n\n return result\n}\n\n/**\n * Load configuration from a directory\n * Automatically finds and loads the config file, or uses defaults\n *\n * @param cwd - Directory to load config from (defaults to process.cwd())\n * @param presetName - Optional preset name to use as base\n * @returns The loaded and merged configuration\n */\nexport async function loadConfig(cwd?: string, presetName?: string): Promise<BetterstartConfig> {\n const workingDir = cwd || process.cwd()\n const logger = getLogger()\n\n // Get base configuration from preset or auto-detection\n let baseConfig: UserConfig\n if (presetName) {\n const preset = getPreset(presetName)\n if (!preset) {\n throw new ConfigurationError(`Unknown preset: ${presetName}`, {\n availablePresets: ['nextjs-monorepo', 'nextjs-standalone', 'custom']\n })\n }\n baseConfig = preset.config\n logger.debug(`Using preset: ${presetName}`)\n } else {\n baseConfig = getDefaultConfig(workingDir)\n const detectedPreset = detectPreset(workingDir)\n logger.debug(`Auto-detected preset: ${detectedPreset.name}`)\n }\n\n // Look for config file\n const configPath = findConfigFile(workingDir)\n let userConfig: UserConfig = {}\n\n if (configPath) {\n logger.debug(`Loading config from: ${configPath}`)\n userConfig = await loadConfigFile(configPath)\n }\n\n // Merge configurations\n const mergedConfig = deepMerge(\n baseConfig as Record<string, unknown>,\n userConfig as Record<string, unknown>\n )\n\n return mergedConfig as unknown as BetterstartConfig\n}\n\n// ============================================================================\n// Path Resolution\n// ============================================================================\n\n/**\n * Find the project root directory\n * Looks for common root indicators (package.json, pnpm-workspace.yaml, turbo.json)\n *\n * @param startDir - Directory to start searching from\n * @returns The project root directory\n */\nexport function findProjectRoot(startDir: string): string {\n let currentDir = path.resolve(startDir)\n const _rootIndicators = ['pnpm-workspace.yaml', 'turbo.json', 'package.json']\n\n // Walk up the directory tree\n while (currentDir !== path.dirname(currentDir)) {\n // Check for monorepo root indicators first\n if (\n fs.existsSync(path.join(currentDir, 'pnpm-workspace.yaml')) ||\n fs.existsSync(path.join(currentDir, 'turbo.json'))\n ) {\n return currentDir\n }\n\n // Check for package.json\n if (fs.existsSync(path.join(currentDir, 'package.json'))) {\n // If there's a package.json but no monorepo indicators,\n // check if parent has monorepo indicators\n const parentDir = path.dirname(currentDir)\n if (\n fs.existsSync(path.join(parentDir, 'pnpm-workspace.yaml')) ||\n fs.existsSync(path.join(parentDir, 'turbo.json'))\n ) {\n return parentDir\n }\n // Otherwise, this package.json is our root\n return currentDir\n }\n\n currentDir = path.dirname(currentDir)\n }\n\n // Fallback to start directory\n return path.resolve(startDir)\n}\n\n/**\n * Resolve configuration paths to absolute paths\n *\n * @param config - The configuration to resolve\n * @param projectRoot - The project root directory\n * @returns Configuration with resolved absolute paths\n */\nexport function resolvePaths(config: BetterstartConfig, projectRoot: string): ResolvedPaths {\n const root = path.resolve(projectRoot)\n\n // Resolve main paths\n const appPath = path.resolve(root, config.paths.app)\n const databasePath = path.resolve(root, config.paths.database)\n const libPath = path.resolve(root, config.paths.lib)\n const hooksPath = path.resolve(root, config.paths.hooks)\n const schemasPath = path.resolve(root, config.paths.schemas)\n\n // Resolve output paths relative to their parent packages\n const outputPaths = {\n actions: path.resolve(libPath, config.paths.output.actions),\n hooks: path.resolve(hooksPath, config.paths.output.hooks),\n components: path.resolve(appPath, config.paths.output.components),\n pages: path.resolve(appPath, config.paths.output.pages),\n emails: path.resolve(appPath, config.paths.output.emails)\n }\n\n return {\n root,\n app: appPath,\n database: databasePath,\n lib: libPath,\n hooks: hooksPath,\n schemas: schemasPath,\n output: outputPaths\n }\n}\n\n/**\n * Fully resolve configuration including paths\n *\n * @param config - The configuration to resolve\n * @param projectRoot - The project root directory (optional, auto-detected if not provided)\n * @returns Fully resolved configuration\n */\nexport function resolveConfig(config: BetterstartConfig, projectRoot?: string): ResolvedConfig {\n const root = projectRoot || findProjectRoot(process.cwd())\n const resolvedPaths = resolvePaths(config, root)\n\n return {\n ...config,\n paths: resolvedPaths\n }\n}\n\n// ============================================================================\n// Configuration Helper\n// ============================================================================\n\n/**\n * Helper function for defining configuration in betterstart.config.ts\n * Provides type checking and autocomplete\n *\n * @example\n * ```ts\n * // betterstart.config.ts\n * import { defineConfig } from '@betterstart/cli'\n *\n * export default defineConfig({\n * paths: {\n * app: 'apps/web',\n * database: 'packages/database'\n * }\n * })\n * ```\n */\nexport function defineConfig(config: UserConfig): UserConfig {\n return config\n}\n\n// ============================================================================\n// Validation\n// ============================================================================\n\n/**\n * Validate a configuration\n * @param config - The configuration to validate\n * @returns Array of validation errors (empty if valid)\n */\nexport function validateConfig(config: BetterstartConfig): string[] {\n const errors: string[] = []\n\n // Validate paths\n if (!config.paths) {\n errors.push('Configuration must have a \"paths\" object')\n } else {\n if (!config.paths.app) errors.push('paths.app is required')\n if (!config.paths.database) errors.push('paths.database is required')\n if (!config.paths.lib) errors.push('paths.lib is required')\n if (!config.paths.hooks) errors.push('paths.hooks is required')\n if (!config.paths.schemas) errors.push('paths.schemas is required')\n }\n\n // Validate database config\n if (!config.database) {\n errors.push('Configuration must have a \"database\" object')\n } else {\n if (config.database.provider !== 'drizzle') {\n errors.push('database.provider must be \"drizzle\"')\n }\n }\n\n // Validate UI config\n if (!config.ui) {\n errors.push('Configuration must have a \"ui\" object')\n } else {\n if (config.ui.framework !== 'shadcn') {\n errors.push('ui.framework must be \"shadcn\"')\n }\n }\n\n return errors\n}\n\n/**\n * Check if resolved paths exist and are accessible\n * @param paths - The resolved paths to check\n * @returns Object with path existence status\n */\nexport function checkPaths(paths: ResolvedPaths): Record<keyof ResolvedPaths, boolean> {\n const exists = (p: string) => {\n try {\n fs.accessSync(p)\n return true\n } catch {\n return false\n }\n }\n\n return {\n root: exists(paths.root),\n app: exists(paths.app),\n database: exists(paths.database),\n lib: exists(paths.lib),\n hooks: exists(paths.hooks),\n schemas: exists(paths.schemas),\n output: {\n actions: exists(paths.output.actions),\n hooks: exists(paths.output.hooks),\n components: exists(paths.output.components),\n pages: exists(paths.output.pages),\n emails: exists(paths.output.emails)\n }\n } as unknown as Record<keyof ResolvedPaths, boolean>\n}\n"],"mappings":"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","names":[]}