// ── One-command Setup ────────────────────────────────────────────── // Run via: npm run setup // Does the following in order: // 1. npm install (Node.js dependencies) // 2. pip install -r requirements.txt (Python dependencies) // 3. playwright install firefox chromium (Playwright browsers) // 4. Copies .env.example to .env.local if not exists // 5. --self-heal flag: runs build check + auto-generates missing modules // // All steps are cross-platform (Windows, Mac, Linux). import { execSync } from "node:child_process" import { existsSync, copyFileSync, readFileSync, writeFileSync, mkdirSync } from "node:fs" import { platform } from "node:os" import { resolve, dirname } from "node:path" import { fileURLToPath } from "node:url" const SEP = platform() === "win32" ? "&" : ";" const SELF = dirname(fileURLToPath(import.meta.url)) const ROOT = resolve(SELF, "..") // ── Helpers ──────────────────────────────────────────────────────── function detectPython() { const candidates = platform() === "win32" ? ["python", "python3"] : ["python3", "python"] for (const cmd of candidates) { try { execSync(`${cmd} --version`, { stdio: "pipe", timeout: 5000 }) return cmd } catch {} } console.error("Python not found. Install Python 3 from https://python.org") process.exit(1) } function detectPip(python) { const candidates = platform() === "win32" ? ["pip", "pip3"] : ["pip3", "pip"] for (const cmd of candidates) { try { execSync(`${cmd} --version`, { stdio: "pipe", timeout: 5000 }) return cmd } catch {} } return `${python} -m pip` } function run(cmd, label, opts = {}) { console.log(`\n── ${label} ──`) try { execSync(cmd, { stdio: "inherit", timeout: opts.timeout || 120000, cwd: opts.cwd || ROOT }) } catch (e) { if (opts.optional) { console.log(` ~ Skipped (${e.message})`) return false } console.error(` ✗ Failed: ${e.message}`) process.exit(1) } return true } // ── Self-Healing Module Registry ─────────────────────────────────── function loadRegistry() { const registryPath = resolve(ROOT, ".setup-templates", "registry.json") const templatesDir = resolve(ROOT, ".setup-templates", "templates") const builtInTemplates = { "data/stickers": { template: "stickers.ts" }, "data/constants": { template: "constants.ts" }, "lib/utils": { template: "utils.ts" }, "types/index": { template: "types-index.ts" }, "hooks/use-media": { template: "hooks/use-media.ts" }, "hooks/use-debounce": { template: "hooks/use-debounce.ts" }, "hooks/use-local-storage": { template: "hooks/use-local-storage.ts" }, } const builtInFallbacks = { "@/data/*": "data-generic.ts", "@/lib/*": "lib-generic.ts", "@/hooks/*": "hook-generic.ts", "@/types/*": "types-generic.ts", "@/components/*": "component-generic.tsx", "@/utils/*": "utils-generic.ts", } let templates = { ...builtInTemplates } let fallbacks = { ...builtInFallbacks } try { if (existsSync(registryPath)) { const custom = JSON.parse(readFileSync(registryPath, "utf-8")) templates = { ...templates, ...(custom.templates || {}) } fallbacks = { ...fallbacks, ...(custom.fallbackTemplates || {}) } } } catch {} const templateCache = {} function getTemplate(templateName) { if (templateCache[templateName]) return templateCache[templateName] const candidatePaths = [ resolve(templatesDir, templateName), resolve(templatesDir, "hooks", templateName.replace("hooks/", "")), ] for (const p of candidatePaths) { if (existsSync(p)) { const content = readFileSync(p, "utf-8") templateCache[templateName] = content return content } } return "" } function matchFallback(internalPath) { for (const [pattern, templateFile] of Object.entries(fallbacks)) { const regexStr = "^" + pattern .replace(/\//g, "\\/").replace(/\./g, "\\.") .replace(/\*/g, ".*") .replace(/[$^+(){}[\]|]/g, "\\$&") + "$" const regex = new RegExp(regexStr) if (regex.test(internalPath)) return templateFile } return null } function generateModule(internalPath) { let templateFile = templates[internalPath]?.template if (!templateFile) templateFile = matchFallback(internalPath) if (!templateFile) return { success: false, error: `No template for @/${internalPath}` } const content = getTemplate(templateFile) if (!content) return { success: false, error: `Template file not found: ${templateFile}` } const ext = templateFile.endsWith(".tsx") ? ".tsx" : ".ts" const filePath = resolve(ROOT, "src", internalPath + ext) mkdirSync(dirname(filePath), { recursive: true }) writeFileSync(filePath, content, "utf-8") return { success: true, filePath, message: `Generated @/${internalPath}` } } return { generateModule } } function parseMissingModules(buildOutput) { const results = [] const seen = new Set() // Pattern: error TS2307: Cannot find module '@/path' or its corresponding type declarations. const errorRegex = /error\s+TS\d+:\s+Cannot find module\s+'([^']+)'/g let match while ((match = errorRegex.exec(buildOutput)) !== null) { const importPath = match[1] if (!seen.has(importPath)) { seen.add(importPath) const isInternal = importPath.startsWith("@/") || importPath.startsWith("~/") const internalPath = isInternal ? importPath.replace(/^[@~]\//, "") : undefined results.push({ importPath, isInternal, internalPath }) } } // Also catch webpack-style: Module not found: Error: Can't resolve '@/path' const webpackRegex = /Module not found: Error: Can't resolve\s+'([^']+)'/g while ((match = webpackRegex.exec(buildOutput)) !== null) { const importPath = match[1] if (!seen.has(importPath)) { seen.add(importPath) const isInternal = importPath.startsWith("@/") || importPath.startsWith("~/") const internalPath = isInternal ? importPath.replace(/^[@~]\//, "") : undefined results.push({ importPath, isInternal, internalPath }) } } return results } // ── Main ─────────────────────────────────────────────────────────── const PY = detectPython() const PIP = detectPip(PY) console.log("=== CoastIT CRM Setup ===\n") // 1. Node dependencies run("npm install", "Installing Node.js dependencies") // 2. Python dependencies run(`cd browser-use-service ${SEP} ${PIP} install -r requirements.txt`, "Installing Python dependencies") // 3. Playwright browsers run(`${PY} -m playwright install firefox chromium`, "Installing Playwright browsers") // 4. .env file if (!existsSync(resolve(ROOT, ".env.local"))) { console.log("\n── Creating .env.local ──") copyFileSync(resolve(ROOT, ".env.example"), resolve(ROOT, ".env.local")) console.log(" ✓ Created .env.local from .env.example — edit it with your settings") } else { console.log("\n── .env.local already exists, skipping ──") } // 5. Final summary console.log("\n── Final Steps ──") console.log(" 1. Make sure PostgreSQL is running with database 'crm'") console.log(" 2. Pull the Ollama model: ollama pull dolphin-llama3:8b") console.log(" 3. Edit .env.local with your settings") console.log(" 4. Run: npm run db:migrate (apply database migrations)") console.log(" 5. Run: npm run dev") console.log("\n=== Setup complete! ===")