import type { AdapterCommand, FrameworkAdapter } from './types.js'; import { detectJsPackageManager, installCommandFor, readPackageJson, runScriptCommandFor, type JsPackageManager, type PackageJson, } from './packageManager.js'; /** * Fallback adapter for any project that isn't recognized as Laravel, * CodeIgniter, or React. Drives everything off `package.json` scripts when * one exists; otherwise tool calls fail with a clear, actionable message * instead of guessing. */ export class GenericAdapter implements FrameworkAdapter { readonly kind = 'generic' as const; readonly label: string; readonly packageManager?: JsPackageManager; private readonly pkg: PackageJson | null; constructor(readonly root: string) { this.pkg = readPackageJson(root); this.packageManager = this.pkg ? detectJsPackageManager(root) : undefined; this.label = this.pkg ? `Generic Node project (${this.packageManager})` : 'Unrecognized project'; } installCommand(): AdapterCommand { if (!this.packageManager) { throw new Error( `No package.json found at "${this.root}" and no recognized framework detected. ` + `Nothing for run_install to do here.`, ); } const { command, args } = installCommandFor(this.packageManager); return { command, args, cwd: this.root }; } devCommand(): AdapterCommand { return this.scriptCommand(['dev', 'start', 'serve']); } buildCommand(): AdapterCommand | null { return this.scriptCommandOrNull(['build']); } testCommand(extraArgs: string[] = []): AdapterCommand | null { return this.scriptCommandOrNull(['test'], extraArgs); } lintCommand(fix = false): AdapterCommand | null { return this.scriptCommandOrNull(fix ? ['format', 'lint:fix'] : ['lint']); } readyPattern(): RegExp { return /(https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)[:\d]*\S*)|ready|listening/i; } describe(): Record { return { kind: this.kind, label: this.label, root: this.root, packageManager: this.packageManager ?? null, scripts: this.pkg?.scripts ?? {}, }; } private scriptCommand(candidates: string[]): AdapterCommand { const result = this.scriptCommandOrNull(candidates); if (!result) { throw new Error( `No package.json script found for any of [${candidates.join(', ')}] in "${this.root}". ` + `Add one of these scripts, or use run_script with an explicit script name.`, ); } return result; } private scriptCommandOrNull(candidates: string[], extraArgs: string[] = []): AdapterCommand | null { if (!this.pkg?.scripts || !this.packageManager) return null; const script = candidates.find((name) => this.pkg?.scripts?.[name]); if (!script) return null; const { command, args } = runScriptCommandFor(this.packageManager, script, extraArgs); return { command, args, cwd: this.root }; } }