scaffoldry 1.0.1
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.
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +198 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-AA2UXYNR.js +1952 -0
- package/dist/chunk-AA2UXYNR.js.map +1 -0
- package/dist/chunk-WOS3F5LR.js +250 -0
- package/dist/chunk-WOS3F5LR.js.map +1 -0
- package/dist/chunk-XIP7YNKZ.js +1216 -0
- package/dist/chunk-XIP7YNKZ.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/platform-Z35MB2P5.js +41 -0
- package/dist/platform-Z35MB2P5.js.map +1 -0
- package/dist/setup-L2PO5OVZ.js +18 -0
- package/dist/setup-L2PO5OVZ.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/templates/index.ts","../src/commands/init.ts","../src/commands/login.ts","../src/commands/logout.ts","../src/commands/rename.ts","../src/commands/create-app.ts","../src/commands/upgrade.ts","../src/commands/migrate.ts","../src/commands/dev.ts","../src/commands/doctor.ts","../src/commands/secret.ts","../src/commands/plan.ts"],"sourcesContent":["import type { TemplateContext, TemplateFile } from \"../types.js\";\nimport { toKebabCase } from \"../utils/index.js\";\n\nexport function generateProjectFiles(context: TemplateContext): TemplateFile[] {\n const files: TemplateFile[] = [];\n\n // Package.json\n files.push(generatePackageJson(context));\n\n // TypeScript config\n files.push(generateTsConfig(context));\n\n // Environment files\n files.push(generateEnvExample(context));\n files.push(generateEnvLocal(context));\n\n // Source files\n files.push(generateMainEntry(context));\n files.push(generatePlatformConfig(context));\n\n // Git files\n files.push(generateGitignore());\n\n return files;\n}\n\nfunction generatePackageJson(context: TemplateContext): TemplateFile {\n const kebabName = toKebabCase(context.projectName);\n\n const dependencies: Record<string, string> = {\n \"scaffoldry-platform\": \"^1.0.0\",\n \"scaffoldry-db\": \"^1.0.0\",\n \"scaffoldry-core\": \"^1.0.0\",\n \"dotenv\": \"^16.5.0\",\n };\n\n if (context.hasAuth) {\n dependencies[\"scaffoldry-auth\"] = \"^1.0.0\";\n }\n if (context.hasBilling) {\n dependencies[\"scaffoldry-billing\"] = \"^1.0.0\";\n }\n if (context.hasEmail) {\n dependencies[\"scaffoldry-notify\"] = \"^1.0.0\";\n }\n if (context.hasStorage) {\n dependencies[\"scaffoldry-storage\"] = \"^1.0.0\";\n }\n if (context.hasJobs) {\n dependencies[\"scaffoldry-jobs\"] = \"^1.0.0\";\n }\n if (context.hasWebhooks) {\n dependencies[\"scaffoldry-webhooks\"] = \"^1.0.0\";\n }\n\n const pkg = {\n name: kebabName,\n version: \"0.1.0\",\n description: context.projectDescription,\n type: \"module\",\n scripts: {\n dev: \"tsx watch src/index.ts\",\n build: \"tsc\",\n start: \"node dist/index.js\",\n typecheck: \"tsc --noEmit\",\n },\n dependencies,\n devDependencies: {\n \"@types/node\": \"^20.17.57\",\n tsx: \"^4.20.3\",\n typescript: \"^5.8.3\",\n },\n };\n\n return {\n path: \"package.json\",\n content: JSON.stringify(pkg, null, 2) + \"\\n\",\n };\n}\n\nfunction generateTsConfig(_context: TemplateContext): TemplateFile {\n const config = {\n compilerOptions: {\n target: \"ES2022\",\n module: \"ESNext\",\n moduleResolution: \"bundler\",\n esModuleInterop: true,\n strict: true,\n skipLibCheck: true,\n outDir: \"./dist\",\n rootDir: \"./src\",\n declaration: true,\n },\n include: [\"src/**/*\"],\n exclude: [\"node_modules\", \"dist\"],\n };\n\n return {\n path: \"tsconfig.json\",\n content: JSON.stringify(config, null, 2) + \"\\n\",\n };\n}\n\nfunction generateEnvExample(context: TemplateContext): TemplateFile {\n const lines = [\n \"# Database\",\n \"DATABASE_URL=postgresql://localhost:5432/my_app\",\n \"\",\n ];\n\n if (context.hasBilling) {\n lines.push(\n \"# Stripe\",\n \"STRIPE_SECRET_KEY=sk_test_...\",\n \"STRIPE_WEBHOOK_SECRET=whsec_...\",\n \"STRIPE_PRICE_ID_STARTER=price_...\",\n \"STRIPE_PRICE_ID_PRO=price_...\",\n \"\"\n );\n }\n\n if (context.hasEmail) {\n lines.push(\n \"# Resend\",\n \"RESEND_API_KEY=re_...\",\n \"RESEND_FROM_EMAIL=noreply@example.com\",\n \"\"\n );\n }\n\n if (context.hasStorage) {\n lines.push(\n \"# S3\",\n \"S3_BUCKET=my-bucket\",\n \"S3_REGION=us-east-1\",\n \"S3_ACCESS_KEY_ID=AKIA...\",\n \"S3_SECRET_ACCESS_KEY=...\",\n \"\"\n );\n }\n\n return {\n path: \".env.example\",\n content: lines.join(\"\\n\"),\n };\n}\n\nfunction generateEnvLocal(_context: TemplateContext): TemplateFile {\n return {\n path: \".env.local\",\n content: \"# Local environment overrides\\n\",\n };\n}\n\nfunction generateMainEntry(context: TemplateContext): TemplateFile {\n const imports = ['import \"dotenv/config\";', 'import { createPlatform } from \"./platform.js\";'];\n\n const content = `${imports.join(\"\\n\")}\n\nasync function main() {\n const platform = createPlatform();\n\n console.log(\"Platform initialized successfully!\");\n console.log(\"Available services:\");\n console.log(\" - Database:\", !!platform.db);\n console.log(\" - Logger:\", !!platform.logger);\n console.log(\" - Audit:\", !!platform.audit);\n${context.hasAuth ? ' console.log(\" - Auth:\", !!platform.auth);' : \"\"}\n${context.hasBilling ? ' console.log(\" - Billing:\", !!platform.billing);' : \"\"}\n${context.hasEmail ? ' console.log(\" - Email:\", !!platform.email);' : \"\"}\n${context.hasStorage ? ' console.log(\" - Storage:\", !!platform.storage);' : \"\"}\n${context.hasJobs ? ' console.log(\" - Jobs:\", !!platform.jobs);' : \"\"}\n${context.hasWebhooks ? ' console.log(\" - Webhook Inbox:\", !!platform.webhookInbox);' : \"\"}\n}\n\nmain().catch(console.error);\n`;\n\n return {\n path: \"src/index.ts\",\n content,\n };\n}\n\nfunction generatePlatformConfig(context: TemplateContext): TemplateFile {\n const content = `import { createPlatform as createScaffoldryPlatform } from \"@scaffoldry/platform\";\n\nexport function createPlatform() {\n return createScaffoldryPlatform({\n database: {\n url: process.env.DATABASE_URL!,\n },\n${context.hasBilling ? ` stripe: {\n secretKey: process.env.STRIPE_SECRET_KEY!,\n webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,\n priceIdStarter: process.env.STRIPE_PRICE_ID_STARTER!,\n priceIdPro: process.env.STRIPE_PRICE_ID_PRO!,\n },` : \"\"}\n${context.hasEmail ? ` resend: {\n apiKey: process.env.RESEND_API_KEY!,\n fromEmail: process.env.RESEND_FROM_EMAIL!,\n },` : \"\"}\n${context.hasStorage ? ` s3: {\n bucket: process.env.S3_BUCKET!,\n region: process.env.S3_REGION!,\n accessKeyId: process.env.S3_ACCESS_KEY_ID!,\n secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,\n },` : \"\"}\n });\n}\n\nexport type Platform = ReturnType<typeof createPlatform>;\n`;\n\n return {\n path: \"src/platform.ts\",\n content,\n };\n}\n\nfunction generateGitignore(): TemplateFile {\n return {\n path: \".gitignore\",\n content: `# Dependencies\nnode_modules/\n\n# Build output\ndist/\n\n# Environment files\n.env\n.env.local\n.env.*.local\n\n# IDE\n.vscode/\n.idea/\n\n# OS\n.DS_Store\nThumbs.db\n\n# Logs\n*.log\nnpm-debug.log*\n\n# Test coverage\ncoverage/\n`,\n };\n}\n","import path from \"node:path\";\nimport fs from \"fs-extra\";\nimport prompts from \"prompts\";\nimport ora from \"ora\";\nimport type { ProjectConfig, ProjectFeature, PackageManager } from \"../types.js\";\nimport {\n logger,\n createTemplateContext,\n toKebabCase,\n getStoredLicense,\n storeLicense,\n isValidLicenseKeyFormat,\n validateLicense,\n} from \"../utils/index.js\";\nimport { generateProjectFiles } from \"../templates/index.js\";\nimport { printBox } from \"../wizards/base.js\";\n\nexport interface InitOptions {\n configFile?: string | undefined;\n skipPrompts?: boolean | undefined;\n}\n\ninterface ConfigFileSchema {\n name: string;\n description?: string;\n licenseKey?: string;\n runSetup?: boolean;\n env?: Record<string, string>;\n}\n\nasync function loadConfigFile(configPath: string): Promise<ConfigFileSchema> {\n const absolutePath = path.resolve(process.cwd(), configPath);\n\n if (!(await fs.pathExists(absolutePath))) {\n throw new Error(`Config file not found: ${absolutePath}`);\n }\n\n const content = await fs.readFile(absolutePath, \"utf-8\");\n const config = JSON.parse(content) as ConfigFileSchema;\n\n if (!config.name || typeof config.name !== \"string\") {\n throw new Error(\"Config file must contain a 'name' field\");\n }\n\n return config;\n}\n\nexport async function initCommand(targetDir?: string, options: InitOptions = {}): Promise<void> {\n const { configFile, skipPrompts } = options;\n let fileConfig: ConfigFileSchema | null = null;\n\n // Load config file if provided\n if (configFile) {\n try {\n fileConfig = await loadConfigFile(configFile);\n logger.log(`Using config file: ${configFile}`);\n logger.newLine();\n } catch (error) {\n logger.error(error instanceof Error ? error.message : \"Failed to load config file\");\n return;\n }\n }\n\n // Display welcome box with the v1 stack (unless in quiet mode)\n if (!skipPrompts) {\n logger.newLine();\n printBox(\"Welcome to Scaffoldry!\", [\n \"Let's build your SaaS in minutes.\",\n \"\",\n \"You're getting our production-ready stack:\",\n \"• Neon (serverless PostgreSQL) + Drizzle ORM\",\n \"• Password + Magic Link authentication\",\n \"• Stripe billing\",\n \"• Resend transactional email\",\n \"• AWS S3 file storage\",\n ]);\n logger.newLine();\n }\n\n // Check for valid license\n const license = await ensureLicense(fileConfig?.licenseKey, skipPrompts);\n if (!license) {\n logger.error(\"A valid license is required to use Scaffoldry.\");\n logger.log(\"Purchase a license at: https://scaffoldry.com\");\n return;\n }\n\n // Get project config from file or prompts\n let config: ProjectConfig | null;\n if (fileConfig) {\n config = {\n name: fileConfig.name,\n description: fileConfig.description || \"A SaaS application built with Scaffoldry\",\n features: [\"auth\", \"billing\", \"email\", \"storage\", \"jobs\", \"webhooks\", \"admin\"] as ProjectFeature[],\n database: \"neon\",\n packageManager: \"pnpm\" as PackageManager,\n };\n logger.log(`Project name: ${config.name}`);\n logger.log(`Description: ${config.description}`);\n logger.newLine();\n } else {\n config = await promptForConfig();\n }\n\n if (!config) {\n logger.error(\"Project setup cancelled.\");\n return;\n }\n\n const projectDir = targetDir\n ? path.resolve(process.cwd(), targetDir)\n : path.resolve(process.cwd(), toKebabCase(config.name));\n\n if (await fs.pathExists(projectDir)) {\n if (skipPrompts) {\n // In non-interactive mode, overwrite by default\n logger.log(`Removing existing directory: ${projectDir}`);\n await fs.remove(projectDir);\n } else {\n const { overwrite } = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Directory ${projectDir} already exists. Overwrite?`,\n initial: false,\n });\n\n if (!overwrite) {\n logger.error(\"Project setup cancelled.\");\n return;\n }\n\n await fs.remove(projectDir);\n }\n }\n\n const spinner = ora(\"Creating project files...\").start();\n\n try {\n const context = createTemplateContext(config);\n const files = generateProjectFiles(context);\n\n for (const file of files) {\n const filePath = path.join(projectDir, file.path);\n await fs.ensureDir(path.dirname(filePath));\n await fs.writeFile(filePath, file.content);\n }\n\n // Write environment variables from config if provided\n if (fileConfig?.env && Object.keys(fileConfig.env).length > 0) {\n const envPath = path.join(projectDir, \".env.local\");\n const envContent = Object.entries(fileConfig.env)\n .map(([key, value]) => `${key}=${value}`)\n .join(\"\\n\");\n await fs.writeFile(envPath, envContent + \"\\n\");\n }\n\n spinner.succeed(\"Project files created!\");\n\n logger.newLine();\n logger.success(`Project \"${config.name}\" created successfully!`);\n\n // Handle setup wizard - either from config, prompt, or skip\n let shouldRunSetup = false;\n\n if (skipPrompts) {\n // In non-interactive mode, use config value or default to false\n shouldRunSetup = fileConfig?.runSetup ?? false;\n if (!shouldRunSetup) {\n logger.newLine();\n logger.log(\"To configure your services:\");\n logger.log(` cd ${toKebabCase(config.name)}`);\n logger.log(\" pnpm install\");\n logger.log(\" scaffoldry setup all\");\n }\n } else {\n // Show next steps with setup wizard prompt\n logger.newLine();\n printBox(\"Next: Configure Your Services\", [\n \"Your project needs API keys for:\",\n \"• Neon Database (required)\",\n \"• Stripe Billing (required for billing)\",\n \"• Resend Email (required for auth emails)\",\n \"• AWS S3 Storage (optional)\",\n ]);\n\n logger.newLine();\n\n const response = await prompts({\n type: \"confirm\",\n name: \"runSetup\",\n message: \"Run setup wizard now?\",\n initial: true,\n });\n shouldRunSetup = response.runSetup;\n\n if (!shouldRunSetup) {\n logger.newLine();\n logger.log(\"To configure your services later:\");\n logger.log(` cd ${toKebabCase(config.name)}`);\n logger.log(\" pnpm install\");\n logger.log(\" scaffoldry setup all\");\n logger.newLine();\n logger.log(\"Or configure individually:\");\n logger.log(\" scaffoldry setup database\");\n logger.log(\" scaffoldry setup stripe\");\n logger.log(\" scaffoldry setup email\");\n logger.log(\" scaffoldry setup storage\");\n }\n }\n\n if (shouldRunSetup) {\n // Change to project directory and run setup\n process.chdir(projectDir);\n const { setupAllCommand } = await import(\"./setup.js\");\n await setupAllCommand();\n }\n\n logger.newLine();\n } catch (error) {\n spinner.fail(\"Failed to create project files.\");\n throw error;\n }\n}\n\nasync function promptForConfig(): Promise<ProjectConfig | null> {\n const response = await prompts([\n {\n type: \"text\",\n name: \"name\",\n message: \"Project name:\",\n initial: \"my-saas-app\",\n validate: (value: string) =>\n value.length > 0 ? true : \"Project name is required\",\n },\n {\n type: \"text\",\n name: \"description\",\n message: \"What are you building? (one sentence)\",\n initial: \"A SaaS application built with Scaffoldry\",\n },\n ]);\n\n if (!response.name) {\n return null;\n }\n\n // v1 uses the full opinionated stack - no choices needed\n return {\n name: response.name as string,\n description: response.description as string,\n // All features enabled by default in v1\n features: [\"auth\", \"billing\", \"email\", \"storage\", \"jobs\", \"webhooks\", \"admin\"] as ProjectFeature[],\n database: \"neon\", // Neon is the v1 choice\n packageManager: \"pnpm\" as PackageManager, // pnpm is the v1 choice\n };\n}\n\ninterface LicenseInfo {\n key: string;\n email: string | undefined;\n}\n\nasync function ensureLicense(providedKey?: string, skipPrompts?: boolean): Promise<LicenseInfo | null> {\n // If a key is provided via config file, use it directly\n if (providedKey) {\n const normalizedKey = providedKey.trim().toUpperCase();\n if (!isValidLicenseKeyFormat(normalizedKey)) {\n logger.error(\"Invalid license key format in config. Expected: SCAF-XXXX-XXXX-XXXX-XXXX\");\n return null;\n }\n\n const spinner = ora(\"Validating license...\").start();\n const validation = await validateLicense(normalizedKey);\n\n if (!validation.valid) {\n spinner.fail(validation.error || \"License validation failed\");\n return null;\n }\n\n spinner.succeed(`License validated for ${validation.email || \"licensed user\"}`);\n\n // Store license locally for future use\n await storeLicense({\n key: normalizedKey,\n ...(validation.email && { email: validation.email }),\n validatedAt: new Date().toISOString(),\n });\n\n logger.newLine();\n return { key: normalizedKey, email: validation.email };\n }\n\n // Check for existing stored license\n const stored = await getStoredLicense();\n\n if (stored) {\n logger.log(`Authenticated as ${stored.email || \"licensed user\"}`);\n logger.newLine();\n\n // Optionally re-validate with API (non-blocking)\n const validation = await validateLicense(stored.key);\n if (validation.valid) {\n return { key: stored.key, email: validation.email || stored.email };\n }\n\n // License no longer valid - need to re-authenticate\n logger.warn(\"Your stored license is no longer valid.\");\n logger.newLine();\n }\n\n // In non-interactive mode without a valid stored license, fail\n if (skipPrompts) {\n logger.error(\"No valid license found. Provide licenseKey in config file or run 'scaffoldry login' first.\");\n return null;\n }\n\n // Prompt for license key\n logger.log(\"Please enter your Scaffoldry license key.\");\n logger.log(\"Purchase at: https://scaffoldry.com\");\n logger.newLine();\n\n const { licenseKey } = await prompts({\n type: \"text\",\n name: \"licenseKey\",\n message: \"License key:\",\n validate: (value: string) => {\n if (!value.trim()) {\n return \"License key is required\";\n }\n if (!isValidLicenseKeyFormat(value.trim().toUpperCase())) {\n return \"Invalid license key format. Expected: SCAF-XXXX-XXXX-XXXX-XXXX\";\n }\n return true;\n },\n format: (value: string) => value.trim().toUpperCase(),\n });\n\n if (!licenseKey) {\n return null;\n }\n\n // Validate with API\n const spinner = ora(\"Validating license...\").start();\n const validation = await validateLicense(licenseKey);\n\n if (!validation.valid) {\n spinner.fail(validation.error || \"License validation failed\");\n return null;\n }\n\n spinner.succeed(`License validated for ${validation.email || \"licensed user\"}`);\n\n // Store license locally\n await storeLicense({\n key: licenseKey,\n ...(validation.email && { email: validation.email }),\n validatedAt: new Date().toISOString(),\n });\n\n logger.newLine();\n return { key: licenseKey, email: validation.email };\n}\n","import prompts from \"prompts\";\nimport ora from \"ora\";\nimport fs from \"fs/promises\";\nimport path from \"path\";\nimport os from \"os\";\nimport {\n logger,\n getStoredLicense,\n storeLicense,\n isValidLicenseKeyFormat,\n validateLicense,\n} from \"../utils/index.js\";\n\nasync function configureNpmrc(licenseKey: string) {\n const npmrcPath = path.join(os.homedir(), \".npmrc\");\n let content = \"\";\n\n try {\n content = await fs.readFile(npmrcPath, \"utf-8\");\n } catch {\n // File doesn't exist, start empty\n }\n\n const lines = content.split(\"\\n\");\n const registryUrl = \"https://scaffoldry.com/api/registry\";\n const registryKey = \"@scaffoldry:registry\";\n const authKey = \"//scaffoldry.com/api/registry/:_authToken\";\n\n let registryFound = false;\n let authFound = false;\n\n const newLines = lines.map((line) => {\n if (line.trim().startsWith(registryKey)) {\n registryFound = true;\n return `${registryKey}=${registryUrl}`;\n }\n if (line.trim().startsWith(authKey)) {\n authFound = true;\n return `${authKey}=${licenseKey}`;\n }\n return line;\n });\n\n if (!registryFound) {\n newLines.push(`${registryKey}=${registryUrl}`);\n }\n if (!authFound) {\n newLines.push(`${authKey}=${licenseKey}`);\n }\n\n // Remove empty lines at the end to avoid pileup\n const cleanContent = newLines.join(\"\\n\").replace(/\\n+$/, \"\") + \"\\n\";\n\n await fs.writeFile(npmrcPath, cleanContent, \"utf-8\");\n}\n\nexport async function loginCommand(): Promise<void> {\n logger.log(\"\");\n logger.info(\"Scaffoldry Login\");\n logger.newLine();\n\n // Check for existing stored license\n const stored = await getStoredLicense();\n\n if (stored) {\n logger.log(`You are already logged in as ${stored.email || \"licensed user\"}`);\n logger.newLine();\n\n const { reauth } = await prompts({\n type: \"confirm\",\n name: \"reauth\",\n message: \"Do you want to login with a different license key?\",\n initial: false,\n });\n\n if (!reauth) {\n return;\n }\n }\n\n // Prompt for license key\n logger.log(\"Enter your Scaffoldry license key.\");\n logger.log(\"Purchase at: https://scaffoldry.com\");\n logger.newLine();\n\n const { licenseKey } = await prompts({\n type: \"text\",\n name: \"licenseKey\",\n message: \"License key:\",\n validate: (value: string) => {\n if (!value.trim()) {\n return \"License key is required\";\n }\n if (!isValidLicenseKeyFormat(value.trim().toUpperCase())) {\n return \"Invalid license key format. Expected: SCAF-XXXX-XXXX-XXXX-XXXX\";\n }\n return true;\n },\n format: (value: string) => value.trim().toUpperCase(),\n });\n\n if (!licenseKey) {\n logger.error(\"Login cancelled.\");\n return;\n }\n\n // Validate with API\n const spinner = ora(\"Validating license...\").start();\n const validation = await validateLicense(licenseKey);\n\n if (!validation.valid) {\n spinner.fail(validation.error || \"License validation failed\");\n return;\n }\n\n spinner.succeed(`License validated!`);\n\n // Store license locally\n await storeLicense({\n key: licenseKey,\n ...(validation.email && { email: validation.email }),\n validatedAt: new Date().toISOString(),\n });\n\n // Configure npm\n await configureNpmrc(licenseKey);\n logger.success(\"Configured .npmrc for private registry access.\");\n\n logger.newLine();\n logger.success(`Logged in as ${validation.email || \"licensed user\"}`);\n logger.newLine();\n logger.log(\"You can now use all Scaffoldry features.\");\n logger.log(\"Run 'scaffoldry init' to create a new project.\");\n logger.newLine();\n}\n","import prompts from \"prompts\";\nimport {\n logger,\n getStoredLicense,\n clearLicense,\n} from \"../utils/index.js\";\n\nexport async function logoutCommand(): Promise<void> {\n logger.log(\"\");\n logger.info(\"Scaffoldry Logout\");\n logger.newLine();\n\n const stored = await getStoredLicense();\n\n if (!stored) {\n logger.log(\"You are not logged in.\");\n return;\n }\n\n const { confirm } = await prompts({\n type: \"confirm\",\n name: \"confirm\",\n message: `Log out from ${stored.email || \"licensed user\"}?`,\n initial: true,\n });\n\n if (!confirm) {\n logger.log(\"Logout cancelled.\");\n return;\n }\n\n await clearLicense();\n logger.success(\"Logged out successfully.\");\n logger.newLine();\n}\n","import path from \"node:path\";\nimport fs from \"fs-extra\";\nimport { glob } from \"glob\";\nimport ora from \"ora\";\nimport { logger, toKebabCase, toPascalCase } from \"../utils/index.js\";\n\nexport interface RenameOptions {\n productName: string;\n productSlug?: string;\n primaryDomain?: string;\n npmScope?: string;\n dryRun?: boolean;\n}\n\nconst SCAFFOLDRY_MARKERS = {\n productName: [\"Scaffoldry\", \"scaffoldry\"],\n productSlug: [\"scaffoldry\"],\n npmScope: [\"@scaffoldry\"],\n};\n\nexport async function renameCommand(options: RenameOptions): Promise<void> {\n const {\n productName,\n productSlug = toKebabCase(productName),\n primaryDomain = `${productSlug}.com`,\n npmScope = `@${productSlug}`,\n dryRun = false,\n } = options;\n\n logger.info(`Renaming project to \"${productName}\"...`);\n if (dryRun) {\n logger.warn(\"DRY RUN - no files will be modified\");\n }\n logger.newLine();\n\n const projectDir = process.cwd();\n\n // Find all files to process\n const files = await glob(\"**/*.{ts,tsx,js,jsx,json,md,yml,yaml,env,env.*}\", {\n cwd: projectDir,\n ignore: [\"**/node_modules/**\", \"**/dist/**\", \"**/.git/**\", \"**/pnpm-lock.yaml\"],\n dot: true,\n });\n\n const spinner = ora(\"Scanning files...\").start();\n\n const replacements: Array<{\n file: string;\n changes: Array<{ from: string; to: string }>;\n }> = [];\n\n for (const file of files) {\n const filePath = path.join(projectDir, file);\n const content = await fs.readFile(filePath, \"utf-8\");\n\n const changes: Array<{ from: string; to: string }> = [];\n\n // Check for Scaffoldry name markers\n if (SCAFFOLDRY_MARKERS.productName.some((m) => content.includes(m))) {\n changes.push({ from: \"Scaffoldry\", to: toPascalCase(productName) });\n changes.push({ from: \"scaffoldry\", to: productSlug });\n }\n\n // Check for npm scope\n if (content.includes(\"@scaffoldry/\")) {\n changes.push({ from: \"@scaffoldry/\", to: `${npmScope}/` });\n }\n if (content.includes(\"@scaffoldry\\\"\")) {\n changes.push({ from: \"@scaffoldry\\\"\", to: `${npmScope}\"` });\n }\n\n // Check for domain markers\n if (content.includes(\"scaffoldry.com\")) {\n changes.push({ from: \"scaffoldry.com\", to: primaryDomain });\n }\n\n if (changes.length > 0) {\n replacements.push({ file, changes });\n }\n }\n\n spinner.succeed(`Found ${replacements.length} files to update`);\n\n if (replacements.length === 0) {\n logger.info(\"No Scaffoldry markers found. Is this a Scaffoldry project?\");\n return;\n }\n\n // Show changes\n logger.newLine();\n logger.log(\"Changes to apply:\");\n for (const { file, changes } of replacements) {\n logger.log(` ${file}:`);\n for (const { from, to } of changes) {\n logger.log(` \"${from}\" -> \"${to}\"`);\n }\n }\n logger.newLine();\n\n if (dryRun) {\n logger.warn(\"DRY RUN complete - no files were modified\");\n return;\n }\n\n // Apply changes\n const applySpinner = ora(\"Applying changes...\").start();\n\n for (const { file, changes } of replacements) {\n const filePath = path.join(projectDir, file);\n let content = await fs.readFile(filePath, \"utf-8\");\n\n for (const { from, to } of changes) {\n content = content.split(from).join(to);\n }\n\n await fs.writeFile(filePath, content);\n }\n\n applySpinner.succeed(\"Changes applied successfully\");\n\n logger.newLine();\n logger.success(`Project renamed to \"${productName}\"!`);\n logger.newLine();\n logger.log(\"Next steps:\");\n logger.log(\" 1. Review the changes\");\n logger.log(\" 2. Run `pnpm install` to update dependencies\");\n logger.log(\" 3. Commit the changes\");\n}\n","import path from \"node:path\";\nimport fs from \"fs-extra\";\nimport ora from \"ora\";\nimport { logger, toKebabCase, toPascalCase } from \"../utils/index.js\";\n\nexport interface CreateAppOptions {\n slug: string;\n name: string;\n}\n\nexport async function createAppCommand(options: CreateAppOptions): Promise<void> {\n const { slug, name } = options;\n const kebabSlug = toKebabCase(slug);\n const pascalName = toPascalCase(name);\n\n logger.info(`Creating new app \"${name}\" (${kebabSlug})...`);\n logger.newLine();\n\n const projectDir = process.cwd();\n const appsDir = path.join(projectDir, \"apps\");\n const appDir = path.join(appsDir, kebabSlug);\n\n // Check if apps directory exists (are we in a Scaffoldry project?)\n if (!(await fs.pathExists(appsDir))) {\n logger.error(\"No 'apps' directory found. Are you in a Scaffoldry project root?\");\n return;\n }\n\n // Check if app already exists\n if (await fs.pathExists(appDir)) {\n logger.error(`App \"${kebabSlug}\" already exists at ${appDir}`);\n return;\n }\n\n const spinner = ora(\"Creating app files...\").start();\n\n try {\n // Create app directory\n await fs.ensureDir(appDir);\n\n // Create package.json\n await fs.writeJSON(\n path.join(appDir, \"package.json\"),\n {\n name: kebabSlug,\n version: \"0.0.0\",\n private: true,\n scripts: {\n dev: \"next dev\",\n build: \"next build\",\n start: \"next start\",\n lint: \"next lint\",\n typecheck: \"tsc --noEmit\",\n },\n dependencies: {\n next: \"^14.0.0\",\n react: \"^18.2.0\",\n \"react-dom\": \"^18.2.0\",\n \"scaffoldry-platform\": \"workspace:*\",\n \"scaffoldry-ui\": \"workspace:*\",\n },\n devDependencies: {\n \"@types/node\": \"^20.10.0\",\n \"@types/react\": \"^18.2.0\",\n \"@types/react-dom\": \"^18.2.0\",\n typescript: \"^5.3.0\",\n },\n },\n { spaces: 2 }\n );\n\n // Create tsconfig.json\n await fs.writeJSON(\n path.join(appDir, \"tsconfig.json\"),\n {\n extends: \"../../tsconfig.json\",\n compilerOptions: {\n outDir: \"./dist\",\n rootDir: \"./src\",\n noEmit: false,\n jsx: \"preserve\",\n module: \"ESNext\",\n moduleResolution: \"bundler\",\n allowJs: true,\n resolveJsonModule: true,\n isolatedModules: true,\n incremental: true,\n plugins: [{ name: \"next\" }],\n paths: {\n \"@/*\": [\"./src/*\"],\n },\n },\n include: [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n exclude: [\"node_modules\", \"dist\"],\n },\n { spaces: 2 }\n );\n\n // Create next.config.js\n await fs.writeFile(\n path.join(appDir, \"next.config.js\"),\n `/** @type {import('next').NextConfig} */\nconst nextConfig = {\n transpilePackages: [\"scaffoldry-platform\", \"scaffoldry-ui\"],\n};\n\nmodule.exports = nextConfig;\n`\n );\n\n // Create src directory structure\n await fs.ensureDir(path.join(appDir, \"src\", \"app\"));\n\n // Create layout.tsx\n await fs.writeFile(\n path.join(appDir, \"src\", \"app\", \"layout.tsx\"),\n `import type { Metadata } from \"next\";\n\nexport const metadata: Metadata = {\n title: \"${name}\",\n description: \"Built with Scaffoldry\",\n};\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}) {\n return (\n <html lang=\"en\">\n <body>{children}</body>\n </html>\n );\n}\n`\n );\n\n // Create page.tsx\n await fs.writeFile(\n path.join(appDir, \"src\", \"app\", \"page.tsx\"),\n `export default function Home() {\n return (\n <main className=\"flex min-h-screen flex-col items-center justify-center p-24\">\n <h1 className=\"text-4xl font-bold\">${pascalName}</h1>\n <p className=\"mt-4 text-lg text-gray-600\">\n Welcome to your new Scaffoldry app!\n </p>\n </main>\n );\n}\n`\n );\n\n // Create next-env.d.ts\n await fs.writeFile(\n path.join(appDir, \"next-env.d.ts\"),\n `/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/basic-features/typescript for more information.\n`\n );\n\n spinner.succeed(\"App files created!\");\n\n logger.newLine();\n logger.success(`App \"${name}\" created successfully at apps/${kebabSlug}!`);\n logger.newLine();\n logger.log(\"Next steps:\");\n logger.log(` pnpm install`);\n logger.log(` pnpm --filter ${kebabSlug} dev`);\n logger.newLine();\n } catch (error) {\n spinner.fail(\"Failed to create app files.\");\n throw error;\n }\n}\n","import path from \"node:path\";\nimport fs from \"fs-extra\";\nimport ora from \"ora\";\nimport { logger } from \"../utils/index.js\";\n\nexport interface UpgradeOptions {\n check?: boolean;\n dryRun?: boolean;\n breaking?: boolean;\n}\n\ninterface PackageVersion {\n name: string;\n current: string;\n latest: string;\n isBreaking: boolean;\n}\n\nconst SCAFFOLDRY_PACKAGES = [\n \"scaffoldry-db\",\n \"scaffoldry-core\",\n \"scaffoldry-auth\",\n \"scaffoldry-webhooks\",\n \"scaffoldry-billing\",\n \"scaffoldry-jobs\",\n \"scaffoldry-notify\",\n \"scaffoldry-storage\",\n \"scaffoldry-platform\",\n \"scaffoldry-ui\",\n \"scaffoldry-admin\",\n \"scaffoldry-cli\",\n];\n\nexport async function upgradeCommand(options: UpgradeOptions): Promise<void> {\n const { check = false, dryRun = false, breaking = false } = options;\n\n logger.info(\"Checking for Scaffoldry updates...\");\n if (dryRun) {\n logger.warn(\"DRY RUN - no packages will be modified\");\n }\n logger.newLine();\n\n const projectDir = process.cwd();\n const packageJsonPath = path.join(projectDir, \"package.json\");\n\n // Check if we're in a Scaffoldry project\n if (!(await fs.pathExists(packageJsonPath))) {\n logger.error(\"No package.json found. Are you in a project root?\");\n return;\n }\n\n const spinner = ora(\"Fetching version information...\").start();\n\n try {\n // Read current package.json\n const packageJson = await fs.readJSON(packageJsonPath);\n const dependencies = {\n ...packageJson.dependencies,\n ...packageJson.devDependencies,\n };\n\n // Check each Scaffoldry package\n const updates: PackageVersion[] = [];\n\n for (const pkgName of SCAFFOLDRY_PACKAGES) {\n const currentVersion = dependencies[pkgName];\n if (!currentVersion) continue;\n\n // In a real implementation, this would fetch from npm registry\n // For now, we simulate by checking workspace packages\n const latestVersion = await getLatestVersion(pkgName);\n\n if (latestVersion && currentVersion !== latestVersion) {\n const isBreaking = isBreakingChange(currentVersion, latestVersion);\n updates.push({\n name: pkgName,\n current: currentVersion,\n latest: latestVersion,\n isBreaking,\n });\n }\n }\n\n spinner.succeed(\"Version check complete\");\n\n if (updates.length === 0) {\n logger.success(\"All Scaffoldry packages are up to date!\");\n return;\n }\n\n // Filter by breaking changes option\n const filteredUpdates = breaking\n ? updates\n : updates.filter((u) => !u.isBreaking);\n\n logger.newLine();\n logger.log(\"Available updates:\");\n logger.newLine();\n\n for (const update of filteredUpdates) {\n const breakingIndicator = update.isBreaking ? \" (BREAKING)\" : \"\";\n logger.log(\n ` ${update.name}: ${update.current} -> ${update.latest}${breakingIndicator}`\n );\n }\n\n if (!breaking && updates.some((u) => u.isBreaking)) {\n logger.newLine();\n logger.warn(\n `${updates.filter((u) => u.isBreaking).length} breaking updates available. Use --breaking to include them.`\n );\n }\n\n if (check) {\n logger.newLine();\n logger.info(\"Run without --check to apply updates.\");\n return;\n }\n\n if (dryRun) {\n logger.newLine();\n logger.warn(\"DRY RUN complete - no packages were modified\");\n return;\n }\n\n // Apply updates\n logger.newLine();\n const applySpinner = ora(\"Applying updates...\").start();\n\n // Update package.json\n for (const update of filteredUpdates) {\n if (packageJson.dependencies?.[update.name]) {\n packageJson.dependencies[update.name] = update.latest;\n }\n if (packageJson.devDependencies?.[update.name]) {\n packageJson.devDependencies[update.name] = update.latest;\n }\n }\n\n await fs.writeJSON(packageJsonPath, packageJson, { spaces: 2 });\n\n applySpinner.succeed(\"Updates applied to package.json\");\n\n logger.newLine();\n logger.success(\"Scaffoldry packages updated!\");\n logger.newLine();\n logger.log(\"Next steps:\");\n logger.log(\" 1. Run `pnpm install` to install updated packages\");\n logger.log(\" 2. Run `scaffoldry migrate` to apply any new migrations\");\n logger.log(\" 3. Review the CHANGELOG for breaking changes\");\n logger.newLine();\n } catch (error) {\n spinner.fail(\"Failed to check for updates\");\n throw error;\n }\n}\n\nasync function getLatestVersion(packageName: string): Promise<string | null> {\n // In a real implementation, this would:\n // 1. Fetch from npm registry: https://registry.npmjs.org/{packageName}\n // 2. Return the latest version\n\n // For workspace packages, check local package.json\n try {\n const pkgPath = path.join(\n process.cwd(),\n \"packages\",\n packageName,\n \"package.json\"\n );\n\n if (await fs.pathExists(pkgPath)) {\n const pkg = await fs.readJSON(pkgPath);\n return pkg.version || \"workspace:*\";\n }\n } catch {\n // Ignore errors\n }\n\n return null;\n}\n\nfunction isBreakingChange(current: string, latest: string): boolean {\n // Simple semver check - major version bump is breaking\n const currentParts = current.replace(/[^\\d.]/g, \"\").split(\".\");\n const latestParts = latest.replace(/[^\\d.]/g, \"\").split(\".\");\n const currentMajor = parseInt(currentParts[0] ?? \"0\", 10);\n const latestMajor = parseInt(latestParts[0] ?? \"0\", 10);\n\n return !isNaN(currentMajor) && !isNaN(latestMajor) && latestMajor > currentMajor;\n}\n","import path from \"node:path\";\nimport fs from \"fs-extra\";\nimport { glob } from \"glob\";\nimport ora from \"ora\";\nimport { neon } from \"@neondatabase/serverless\";\nimport { drizzle } from \"drizzle-orm/neon-http\";\nimport { migrate } from \"drizzle-orm/neon-http/migrator\";\nimport postgres from \"postgres\";\nimport { drizzle as drizzlePostgres } from \"drizzle-orm/postgres-js\";\nimport { migrate as migratePostgres } from \"drizzle-orm/postgres-js/migrator\";\nimport { logger } from \"../utils/index.js\";\n\nexport interface MigrateOptions {\n dryRun?: boolean;\n}\n\nexport async function migrateCommand(options: MigrateOptions = {}): Promise<void> {\n const { dryRun = false } = options;\n\n logger.info(\"Running migrations...\");\n logger.newLine();\n\n const projectDir = process.cwd();\n\n // Check for DATABASE_URL\n const databaseUrl = process.env.DATABASE_URL;\n if (!databaseUrl) {\n logger.error(\"DATABASE_URL environment variable is not set.\");\n logger.log(\"Set it in your .env file or export it in your shell.\");\n return;\n }\n\n // Find migration directories\n const migrationDirs = [\n path.join(projectDir, \"packages\", \"scaffoldry-db\", \"drizzle\"),\n path.join(projectDir, \"drizzle\"),\n ];\n\n let migrationsDir: string | null = null;\n for (const dir of migrationDirs) {\n if (await fs.pathExists(dir)) {\n migrationsDir = dir;\n break;\n }\n }\n\n if (!migrationsDir) {\n logger.error(\"No migrations directory found.\");\n logger.log(\"Expected one of:\");\n for (const dir of migrationDirs) {\n logger.log(` - ${dir}`);\n }\n logger.newLine();\n logger.log(\"Run 'pnpm --filter scaffoldry-db generate' to create migrations.\");\n return;\n }\n\n // Find migration files\n const migrations = await glob(\"*.sql\", { cwd: migrationsDir });\n if (migrations.length === 0) {\n logger.warn(\"No migration files found.\");\n logger.log(\"Run 'pnpm --filter scaffoldry-db generate' to create migrations.\");\n return;\n }\n\n logger.info(`Found ${migrations.length} migration file(s) in ${migrationsDir}`);\n for (const migration of migrations.sort()) {\n logger.log(` - ${migration}`);\n }\n logger.newLine();\n\n if (dryRun) {\n logger.info(\"Dry run mode - no migrations will be applied.\");\n return;\n }\n\n const spinner = ora(\"Applying migrations...\").start();\n\n try {\n // Determine connection type based on URL\n const isNeon = databaseUrl.includes(\"neon.tech\") || databaseUrl.includes(\"neon-\");\n\n if (isNeon) {\n // Use Neon serverless adapter for Neon databases\n const sql = neon(databaseUrl);\n const db = drizzle(sql);\n await migrate(db, { migrationsFolder: migrationsDir });\n } else {\n // Use postgres.js for standard PostgreSQL connections\n const sql = postgres(databaseUrl, { max: 1 });\n const db = drizzlePostgres(sql);\n await migratePostgres(db, { migrationsFolder: migrationsDir });\n await sql.end();\n }\n\n spinner.succeed(\"Migrations applied successfully!\");\n logger.newLine();\n logger.success(`Applied ${migrations.length} migration(s).`);\n logger.newLine();\n } catch (error) {\n spinner.fail(\"Migration failed\");\n if (error instanceof Error) {\n logger.error(error.message);\n if (error.message.includes(\"already exists\")) {\n logger.newLine();\n logger.log(\"This usually means the migration was partially applied.\");\n logger.log(\"Check your database state and consider resetting if in development.\");\n }\n }\n throw error;\n }\n}\n\nexport interface MigrateCreateOptions {\n name: string;\n}\n\nexport async function migrateCreateCommand(options: MigrateCreateOptions): Promise<void> {\n const { name } = options;\n\n // Validate name\n if (!/^[a-z][a-z0-9_]*$/.test(name)) {\n logger.error(\"Migration name must be snake_case (e.g., add_user_preferences)\");\n return;\n }\n\n logger.info(`Creating migration \"${name}\"...`);\n logger.newLine();\n\n const projectDir = process.cwd();\n const migrationsDir = path.join(projectDir, \"drizzle\");\n\n // Ensure migrations directory exists\n await fs.ensureDir(migrationsDir);\n\n // Generate timestamp\n const timestamp = new Date()\n .toISOString()\n .replace(/[-:T]/g, \"\")\n .slice(0, 14);\n\n const fileName = `${timestamp}_${name}.sql`;\n const filePath = path.join(migrationsDir, fileName);\n\n // Check if file already exists\n if (await fs.pathExists(filePath)) {\n logger.error(`Migration file already exists: ${fileName}`);\n return;\n }\n\n // Create migration file with template\n const template = `-- Migration: ${name}\n-- Created at: ${new Date().toISOString()}\n\n-- Write your migration SQL here\n\n-- Example:\n-- CREATE TABLE user_preferences (\n-- id TEXT PRIMARY KEY,\n-- user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,\n-- theme TEXT NOT NULL DEFAULT 'light',\n-- notifications_enabled BOOLEAN NOT NULL DEFAULT true,\n-- created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),\n-- updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()\n-- );\n\n-- CREATE INDEX idx_user_preferences_user_id ON user_preferences(user_id);\n`;\n\n await fs.writeFile(filePath, template);\n\n logger.success(`Migration created: ${fileName}`);\n logger.newLine();\n logger.log(`Edit the file at: ${filePath}`);\n logger.newLine();\n logger.log(\"After editing, run:\");\n logger.log(\" scaffoldry migrate\");\n logger.newLine();\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface MigrateStatusOptions {\n // Future: add options for status command\n}\n\nexport async function migrateStatusCommand(_options: MigrateStatusOptions = {}): Promise<void> {\n logger.info(\"Checking migration status...\");\n logger.newLine();\n\n const projectDir = process.cwd();\n\n // Find migration directories\n const migrationDirs = [\n path.join(projectDir, \"packages\", \"scaffoldry-db\", \"drizzle\"),\n path.join(projectDir, \"drizzle\"),\n ];\n\n for (const migrationsDir of migrationDirs) {\n if (!(await fs.pathExists(migrationsDir))) continue;\n\n const migrations = await glob(\"*.sql\", { cwd: migrationsDir });\n logger.info(`Migrations in ${migrationsDir}:`);\n\n if (migrations.length === 0) {\n logger.log(\" No migrations found.\");\n } else {\n for (const migration of migrations.sort()) {\n logger.log(` - ${migration}`);\n }\n }\n logger.newLine();\n }\n\n // Check for journal file\n const journalPath = path.join(projectDir, \"packages\", \"scaffoldry-db\", \"drizzle\", \"meta\", \"_journal.json\");\n if (await fs.pathExists(journalPath)) {\n try {\n const journal = await fs.readJson(journalPath);\n logger.info(`Migration journal: ${journal.entries?.length || 0} entries recorded`);\n } catch {\n logger.warn(\"Could not read migration journal.\");\n }\n }\n}\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport chalk from \"chalk\";\nimport {\n checkNodeVersion,\n checkPnpmVersion,\n checkStripeCli,\n checkEnvVar,\n type CheckResult,\n} from \"../utils/diagnostics.js\";\nimport { readEnvLocal } from \"../utils/env.js\";\nimport { printBox, printSuccess, printError, printWarning, printInfo } from \"../wizards/base.js\";\nimport {\n getSpawnOptions,\n setupShutdownHandlers,\n getTermSignal,\n getKillSignal,\n} from \"../utils/platform.js\";\n\ninterface DevOptions {\n skipStripe?: boolean;\n skipJobs?: boolean;\n port?: number;\n}\n\ntype ChalkFunction = (text: string) => string;\n\ninterface ProcessInfo {\n name: string;\n process: ChildProcess;\n color: ChalkFunction;\n}\n\nconst colors: ChalkFunction[] = [\n chalk.cyan,\n chalk.magenta,\n chalk.yellow,\n chalk.green,\n chalk.blue,\n];\n\n/**\n * Print a prefixed log line for a process\n */\nfunction prefixLog(prefix: string, color: ChalkFunction, message: string): void {\n const lines = message.split(\"\\n\").filter(Boolean);\n for (const line of lines) {\n console.log(color(`[${prefix}]`) + \" \" + line);\n }\n}\n\n/**\n * Check prerequisites before starting dev server\n */\nasync function checkPrerequisites(projectDir: string, options: DevOptions): Promise<{ pass: boolean; stripeCli: boolean }> {\n console.log();\n printBox(\"Starting Development Environment\", [\n \"Checking prerequisites...\",\n ]);\n\n const checks: { result: CheckResult; required: boolean }[] = [];\n\n // Environment checks\n checks.push({ result: await checkNodeVersion(), required: true });\n checks.push({ result: await checkPnpmVersion(), required: true });\n\n // Stripe CLI check (not required if skipped or not configured)\n const stripeCli = await checkStripeCli();\n const env = await readEnvLocal(projectDir);\n const hasStripeKey = Boolean(env.STRIPE_SECRET_KEY);\n checks.push({ result: stripeCli, required: !options.skipStripe && hasStripeKey });\n\n // Config checks\n checks.push({ result: await checkEnvVar(projectDir, \"DATABASE_URL\"), required: true });\n\n if (!options.skipStripe) {\n checks.push({ result: await checkEnvVar(projectDir, \"STRIPE_SECRET_KEY\", { required: false }), required: false });\n }\n\n // Print results\n let allPassed = true;\n const stripeCliAvailable = stripeCli.status === \"pass\";\n\n for (const { result, required } of checks) {\n const icon = result.status === \"pass\" ? chalk.green(\"✓\") :\n result.status === \"warn\" ? chalk.yellow(\"⚠\") :\n chalk.red(\"✗\");\n\n let line = ` ${icon} ${result.label}`;\n if (result.message) {\n line += chalk.gray(` (${result.message})`);\n }\n console.log(line);\n\n if (required && result.status === \"fail\") {\n allPassed = false;\n }\n }\n\n if (!allPassed) {\n console.log();\n printError(\"Some required prerequisites are missing\");\n printInfo(\"Run `scaffoldry doctor` for more details\");\n return { pass: false, stripeCli: stripeCliAvailable };\n }\n\n return { pass: true, stripeCli: stripeCliAvailable };\n}\n\n/**\n * Start the development server\n */\nexport async function devCommand(options: DevOptions = {}): Promise<void> {\n const projectDir = process.cwd();\n const port = options.port || 3000;\n\n // Check prerequisites\n const prereqs = await checkPrerequisites(projectDir, options);\n if (!prereqs.pass) {\n process.exit(1);\n }\n\n const processes: ProcessInfo[] = [];\n let colorIndex = 0;\n\n const getNextColor = (): ChalkFunction => {\n const color = colors[colorIndex++ % colors.length];\n return color ?? chalk.white;\n };\n\n // Start Next.js dev server\n console.log();\n console.log(\"Starting services...\");\n\n const nextProcess = spawn(\"pnpm\", [\"run\", \"dev\", \"--\", \"--port\", String(port)], {\n ...getSpawnOptions({\n cwd: projectDir,\n stdio: [\"inherit\", \"pipe\", \"pipe\"],\n }),\n });\n\n const nextInfo: ProcessInfo = {\n name: \"next\",\n process: nextProcess,\n color: getNextColor(),\n };\n processes.push(nextInfo);\n\n nextProcess.stdout?.on(\"data\", (data) => {\n prefixLog(\"next\", nextInfo.color, data.toString());\n });\n\n nextProcess.stderr?.on(\"data\", (data) => {\n prefixLog(\"next\", nextInfo.color, data.toString());\n });\n\n printSuccess(`Next.js dev server starting on http://localhost:${port}`);\n\n // Start Stripe CLI listener if available and not skipped\n const env = await readEnvLocal(projectDir);\n if (!options.skipStripe && prereqs.stripeCli && env.STRIPE_SECRET_KEY) {\n const stripeProcess = spawn(\n \"stripe\",\n [\"listen\", \"--forward-to\", `localhost:${port}/api/webhooks/stripe`],\n {\n ...getSpawnOptions({\n cwd: projectDir,\n stdio: [\"inherit\", \"pipe\", \"pipe\"],\n }),\n }\n );\n\n const stripeInfo: ProcessInfo = {\n name: \"stripe\",\n process: stripeProcess,\n color: getNextColor(),\n };\n processes.push(stripeInfo);\n\n stripeProcess.stdout?.on(\"data\", (data) => {\n const output = data.toString();\n prefixLog(\"stripe\", stripeInfo.color, output);\n\n // Extract webhook secret if printed\n const secretMatch = output.match(/whsec_[A-Za-z0-9]+/);\n if (secretMatch) {\n console.log();\n printInfo(`Webhook secret: ${secretMatch[0]}`);\n printInfo(\"Update STRIPE_WEBHOOK_SECRET in .env.local if needed\");\n }\n });\n\n stripeProcess.stderr?.on(\"data\", (data) => {\n prefixLog(\"stripe\", stripeInfo.color, data.toString());\n });\n\n printSuccess(\"Stripe CLI listening for webhooks\");\n } else if (!options.skipStripe && !prereqs.stripeCli && env.STRIPE_SECRET_KEY) {\n printWarning(\"Stripe CLI not installed - webhook forwarding disabled\");\n const { getInstallInstructions } = await import(\"../utils/platform.js\");\n printInfo(getInstallInstructions(\"stripe-cli\"));\n }\n\n // Ready message\n console.log();\n printBox(\"Development Server Ready\", [\n `App: http://localhost:${port}`,\n \"\",\n \"Press Ctrl+C to stop all services.\",\n ]);\n console.log();\n\n // Handle graceful shutdown\n const cleanup = () => {\n console.log();\n console.log(\"Shutting down...\");\n\n const termSignal = getTermSignal();\n const killSignal = getKillSignal();\n\n for (const { name, process: proc, color } of processes) {\n prefixLog(name, color, \"Stopping...\");\n proc.kill(termSignal);\n }\n\n // Give processes time to cleanup, then force kill\n setTimeout(() => {\n for (const { process: proc } of processes) {\n if (!proc.killed) {\n proc.kill(killSignal);\n }\n }\n process.exit(0);\n }, 3000);\n };\n\n // Set up cross-platform shutdown handlers\n setupShutdownHandlers(cleanup);\n\n // Handle process exits\n for (const { name, process: proc, color } of processes) {\n proc.on(\"exit\", (code) => {\n prefixLog(name, color, `Exited with code ${code}`);\n });\n\n proc.on(\"error\", (error) => {\n prefixLog(name, color, `Error: ${error.message}`);\n });\n }\n}\n","import chalk from \"chalk\";\nimport prompts from \"prompts\";\nimport {\n runEnvironmentChecks,\n runConfigurationChecks,\n runServiceChecks,\n type CheckResult,\n} from \"../utils/diagnostics.js\";\nimport { getStoredLicense, validateLicense } from \"../utils/license.js\";\nimport { printBox, printInfo } from \"../wizards/base.js\";\nimport { getPlatformDisplayName } from \"../utils/platform.js\";\n\ninterface Issue {\n label: string;\n message: string;\n fix?: string;\n}\n\n/**\n * Print check results with status icons\n */\nfunction printCheckResults(results: CheckResult[]): void {\n for (const result of results) {\n const icon = result.status === \"pass\" ? chalk.green(\"✓\") :\n result.status === \"warn\" ? chalk.yellow(\"⚠\") :\n chalk.red(\"✗\");\n\n let line = ` ${icon} ${result.label}`;\n if (result.message) {\n line += chalk.gray(` (${result.message})`);\n }\n console.log(line);\n }\n}\n\n/**\n * Collect issues from check results\n */\nfunction collectIssues(results: CheckResult[]): Issue[] {\n const issues: Issue[] = [];\n\n for (const result of results) {\n if (result.status === \"fail\" || result.status === \"warn\") {\n const issue: Issue = {\n label: result.label,\n message: result.message || \"Issue detected\",\n };\n if (result.fix) {\n issue.fix = result.fix;\n }\n issues.push(issue);\n }\n }\n\n return issues;\n}\n\n/**\n * Check license validity\n */\nasync function checkLicense(): Promise<CheckResult> {\n const stored = await getStoredLicense();\n\n if (!stored) {\n return {\n status: \"fail\",\n label: \"Scaffoldry license\",\n message: \"Not authenticated\",\n fix: \"Run: scaffoldry login\",\n };\n }\n\n const validation = await validateLicense(stored.key);\n\n if (validation.valid) {\n return {\n status: \"pass\",\n label: \"Scaffoldry license\",\n message: stored.email || \"Valid\",\n };\n }\n\n return {\n status: \"fail\",\n label: \"Scaffoldry license\",\n message: validation.error || \"Invalid\",\n fix: \"Run: scaffoldry login\",\n };\n}\n\n/**\n * Doctor command - diagnose and fix project issues\n */\nexport async function doctorCommand(): Promise<void> {\n const projectDir = process.cwd();\n\n console.log();\n printBox(\"Scaffoldry Doctor\", [\n \"Checking your project configuration...\",\n ]);\n\n // Show platform info\n console.log();\n console.log(chalk.bold(\"Platform:\"), getPlatformDisplayName());\n\n // 1. Environment checks\n console.log();\n console.log(chalk.bold(\"Checking environment...\"));\n\n const envResults = await runEnvironmentChecks();\n printCheckResults(envResults);\n\n // 2. License check\n console.log();\n console.log(chalk.bold(\"Checking license...\"));\n\n const licenseResult = await checkLicense();\n printCheckResults([licenseResult]);\n\n // 3. Configuration checks\n console.log();\n console.log(chalk.bold(\"Checking configuration...\"));\n\n const configResults = await runConfigurationChecks(projectDir);\n printCheckResults(configResults);\n\n // 4. Service connectivity checks\n console.log();\n console.log(chalk.bold(\"Checking services...\"));\n\n const serviceResults = await runServiceChecks(projectDir);\n printCheckResults(serviceResults);\n\n // Collect all issues\n const licenseIssue: Issue | null = licenseResult.status !== \"pass\" ? {\n label: licenseResult.label,\n message: licenseResult.message || \"Issue\",\n ...(licenseResult.fix ? { fix: licenseResult.fix } : {}),\n } : null;\n\n const allIssues: Issue[] = [\n ...collectIssues(envResults),\n ...(licenseIssue ? [licenseIssue] : []),\n ...collectIssues(configResults),\n ...collectIssues(serviceResults),\n ];\n\n // Filter to only failures (not warnings)\n const criticalIssues = allIssues.filter(issue =>\n !issue.message.includes(\"optional\") && !issue.message.includes(\"network error\")\n );\n\n // Summary\n console.log();\n\n if (criticalIssues.length === 0) {\n console.log(chalk.green.bold(\"✓ All checks passed!\"));\n console.log();\n printInfo(\"Your project is configured correctly.\");\n printInfo(\"Run `scaffoldry dev` to start development.\");\n return;\n }\n\n // Show issues\n const borderTop = \"┌\" + \"─\".repeat(61) + \"┐\";\n const borderBottom = \"└\" + \"─\".repeat(61) + \"┘\";\n\n console.log(chalk.yellow(borderTop));\n console.log(chalk.yellow(\"│\") + \" \".repeat(61) + chalk.yellow(\"│\"));\n console.log(chalk.yellow(\"│\") + chalk.bold(` ${criticalIssues.length} issue${criticalIssues.length === 1 ? \"\" : \"s\"} need attention:`).padEnd(64) + chalk.yellow(\"│\"));\n console.log(chalk.yellow(\"│\") + \" \".repeat(61) + chalk.yellow(\"│\"));\n\n criticalIssues.forEach((issue, i) => {\n console.log(chalk.yellow(\"│\") + ` ${i + 1}. ${issue.label}`.padEnd(61) + chalk.yellow(\"│\"));\n console.log(chalk.yellow(\"│\") + chalk.gray(` ${issue.message}`).padEnd(70) + chalk.yellow(\"│\"));\n if (issue.fix) {\n console.log(chalk.yellow(\"│\") + chalk.cyan(` → ${issue.fix}`).padEnd(70) + chalk.yellow(\"│\"));\n }\n console.log(chalk.yellow(\"│\") + \" \".repeat(61) + chalk.yellow(\"│\"));\n });\n\n console.log(chalk.yellow(borderBottom));\n\n // Offer to auto-fix\n const fixableIssues = criticalIssues.filter(issue => issue.fix?.startsWith(\"Run: scaffoldry setup\"));\n\n if (fixableIssues.length > 0) {\n console.log();\n const { autoFix } = await prompts({\n type: \"confirm\",\n name: \"autoFix\",\n message: \"Run setup wizards to fix issues?\",\n initial: true,\n });\n\n if (autoFix) {\n // Import setup functions dynamically to avoid circular imports\n const { setupCommand } = await import(\"./setup.js\");\n\n // Determine which services need setup\n const services = new Set<string>();\n for (const issue of fixableIssues) {\n if (issue.fix?.includes(\"setup stripe\")) services.add(\"stripe\");\n if (issue.fix?.includes(\"setup database\")) services.add(\"database\");\n if (issue.fix?.includes(\"setup email\")) services.add(\"email\");\n if (issue.fix?.includes(\"setup storage\")) services.add(\"storage\");\n if (issue.fix?.includes(\"setup all\")) {\n services.add(\"database\");\n services.add(\"stripe\");\n services.add(\"email\");\n }\n }\n\n for (const service of services) {\n await setupCommand({ service: service as \"stripe\" | \"database\" | \"email\" | \"storage\" });\n }\n\n console.log();\n printInfo(\"Run `scaffoldry doctor` again to verify fixes\");\n }\n }\n}\n","import crypto from \"node:crypto\";\nimport prompts from \"prompts\";\nimport { logger } from \"../utils/logger.js\";\nimport { readEnvLocal, writeEnvLocal } from \"../utils/env.js\";\nimport {\n printBox,\n printSuccess,\n printError,\n printWarning,\n printInfo,\n} from \"../wizards/base.js\";\n\nexport type SecretType = \"auth\" | \"db\";\n\nexport interface SecretSetOptions {\n key: string;\n value: string;\n}\n\nexport interface SecretRotateOptions {\n type: SecretType;\n}\n\n/**\n * Generate a secure random string\n */\nfunction generateSecureSecret(bytes = 32): string {\n return crypto.randomBytes(bytes).toString(\"base64url\");\n}\n\n/**\n * Set a secret in .env.local\n */\nexport async function secretSetCommand(options: SecretSetOptions): Promise<void> {\n const projectDir = process.cwd();\n\n if (!options.key || !options.value) {\n printError(\"Both key and value are required\");\n printInfo(\"Usage: scaffoldry secret set <KEY> <VALUE>\");\n return;\n }\n\n // Validate key format\n if (!/^[A-Z_][A-Z0-9_]*$/.test(options.key)) {\n printError(\"Invalid key format. Use UPPERCASE_WITH_UNDERSCORES\");\n return;\n }\n\n await writeEnvLocal(projectDir, { [options.key]: options.value });\n\n logger.newLine();\n printSuccess(`Set ${options.key} in .env.local`);\n}\n\n/**\n * Rotate a secret following Section 32 rules\n */\nexport async function secretRotateCommand(options: SecretRotateOptions): Promise<void> {\n const projectDir = process.cwd();\n\n switch (options.type) {\n case \"auth\":\n await rotateAuthSecret(projectDir);\n break;\n case \"db\":\n await rotateDatabaseCredentials(projectDir);\n break;\n default:\n printError(`Unknown secret type: ${options.type}`);\n printInfo(\"Supported types: auth, db\");\n }\n}\n\n/**\n * Rotate AUTH_SECRET per Section 32 rules\n */\nasync function rotateAuthSecret(projectDir: string): Promise<void> {\n console.log();\n printBox(\"Rotating AUTH_SECRET\", [\n \"Per Section 32 rotation rules:\",\n \"1. Current AUTH_SECRET → AUTH_SECRET_PREVIOUS\",\n \"2. Generate new 32-byte random AUTH_SECRET\",\n \"3. Both keys valid during rotation window\",\n ]);\n\n // Read current secrets\n const env = await readEnvLocal(projectDir);\n const currentSecret = env.AUTH_SECRET;\n\n if (!currentSecret) {\n printWarning(\"No AUTH_SECRET found in .env.local\");\n console.log();\n const { generate } = await prompts({\n type: \"confirm\",\n name: \"generate\",\n message: \"Generate a new AUTH_SECRET?\",\n initial: true,\n });\n\n if (generate) {\n const newSecret = generateSecureSecret(32);\n await writeEnvLocal(projectDir, { AUTH_SECRET: newSecret });\n printSuccess(\"Generated new AUTH_SECRET\");\n }\n return;\n }\n\n // Confirm rotation\n console.log();\n const { proceed } = await prompts({\n type: \"confirm\",\n name: \"proceed\",\n message: \"Proceed with rotation?\",\n initial: true,\n });\n\n if (!proceed) {\n printInfo(\"Rotation cancelled\");\n return;\n }\n\n // Perform rotation\n console.log();\n console.log(\"Rotating...\");\n\n const newSecret = generateSecureSecret(32);\n\n await writeEnvLocal(projectDir, {\n AUTH_SECRET: newSecret,\n AUTH_SECRET_PREVIOUS: currentSecret,\n });\n\n printSuccess(\"Moved current key to AUTH_SECRET_PREVIOUS\");\n printSuccess(\"Generated new AUTH_SECRET\");\n printSuccess(\"Updated .env.local\");\n\n console.log();\n printWarning(\"Important: Restart your server to apply changes.\");\n printInfo(\"Sessions signed with old key will remain valid\");\n printInfo(\"during the rotation window (AUTH_SECRET_PREVIOUS).\");\n}\n\n/**\n * Guide for database credential rotation\n */\nasync function rotateDatabaseCredentials(projectDir: string): Promise<void> {\n console.log();\n printBox(\"Database Credential Rotation\", [\n \"Database credentials require careful rotation to avoid downtime.\",\n \"\",\n \"Steps for Neon PostgreSQL:\",\n \"1. Create new role in Neon dashboard\",\n \"2. Grant same permissions as current role\",\n \"3. Update DATABASE_URL with new credentials\",\n \"4. Verify connection works\",\n \"5. Revoke old credentials\",\n ]);\n\n const env = await readEnvLocal(projectDir);\n const currentUrl = env.DATABASE_URL;\n\n if (!currentUrl) {\n printError(\"No DATABASE_URL found in .env.local\");\n printInfo(\"Run: scaffoldry setup database\");\n return;\n }\n\n console.log();\n printInfo(\"Current database host:\");\n try {\n const url = new URL(currentUrl);\n console.log(` ${url.hostname}`);\n } catch {\n console.log(\" (unable to parse URL)\");\n }\n\n console.log();\n printInfo(\"To rotate database credentials:\");\n console.log(\" 1. Go to https://console.neon.tech\");\n console.log(\" 2. Navigate to your project > Settings > Roles\");\n console.log(\" 3. Create a new role\");\n console.log(\" 4. Update the connection string\");\n console.log(\" 5. Run: scaffoldry setup database\");\n console.log();\n\n const { openDocs } = await prompts({\n type: \"confirm\",\n name: \"openDocs\",\n message: \"Open Neon documentation for credential rotation?\",\n initial: true,\n });\n\n if (openDocs) {\n const { exec } = await import(\"node:child_process\");\n const { promisify } = await import(\"node:util\");\n const execAsync = promisify(exec);\n const os = await import(\"node:os\");\n\n const url = \"https://neon.tech/docs/manage/roles\";\n const platform = os.platform();\n\n try {\n if (platform === \"darwin\") {\n await execAsync(`open \"${url}\"`);\n } else if (platform === \"win32\") {\n await execAsync(`start \"${url}\"`);\n } else {\n await execAsync(`xdg-open \"${url}\"`);\n }\n printInfo(\"Opened Neon documentation\");\n } catch {\n printInfo(`Visit: ${url}`);\n }\n }\n}\n","import fs from \"fs-extra\";\nimport path from \"node:path\";\nimport prompts from \"prompts\";\nimport { saveProjectData } from \"../utils/config.js\";\nimport {\n printBox,\n printSuccess,\n printInfo,\n} from \"../wizards/base.js\";\nimport { copyToClipboard } from \"../utils/platform.js\";\n\n/**\n * Master Spec Section 2: Invariants (Critical constraints for AI)\n */\nconst MASTER_SPEC_INVARIANTS = `\n## Scaffoldry v1 Invariants (MUST FOLLOW)\n\nThese are non-negotiable constraints that all generated code MUST follow:\n\n### Authentication\n- Use Argon2id for password hashing (NOT bcrypt)\n- Implement Pwned Passwords check on registration\n- Magic link tokens: 32-byte random, 15-minute expiry\n- Session cookies: HttpOnly, Secure, SameSite=Lax\n\n### Database\n- All tables MUST have tenant_id column for multi-tenancy\n- Use Row-Level Security (RLS) policies\n- Soft-delete with deleted_at timestamp (NOT hard delete)\n- All timestamps in UTC\n\n### API Design\n- All endpoints MUST be rate-limited\n- Input validation with Zod schemas\n- Return consistent error format: { error: string, code?: string }\n- Use POST for mutations, GET for queries\n\n### Security\n- No secrets in client-side code (NEXT_PUBLIC_ prefix rules)\n- CSRF protection on state-changing operations\n- Validate webhook signatures before processing\n- Log all authentication events to audit table\n`;\n\n/**\n * Master Spec Section 4: Security Hardening (Critical for AI)\n */\nconst MASTER_SPEC_SECURITY = `\n## Security Hardening Requirements\n\n### Password Security\n- Minimum 8 characters, check against Pwned Passwords API\n- Hash with Argon2id: memoryCost=65536, timeCost=3, parallelism=4\n- Never store plaintext passwords\n- Never log passwords or password hashes\n\n### Session Security\n- Rotate session ID on privilege escalation\n- 24-hour session expiry, refresh on activity\n- Store sessions in database with user_agent and ip_hash\n- Invalidate all sessions on password change\n\n### Rate Limiting\n- Login: 5 attempts per 15 minutes per IP\n- API: 100 requests per minute per user\n- Webhook: 1000 requests per minute per endpoint\n\n### Input Validation\n- Validate ALL user input server-side\n- Sanitize HTML output to prevent XSS\n- Use parameterized queries (Drizzle handles this)\n- Validate file uploads: type, size, content\n\n### Audit Logging\n- Log: login, logout, password_change, role_change, data_export\n- Include: user_id, action, ip_hash, timestamp, metadata\n- IP addresses MUST be hashed with salt (privacy compliance)\n`;\n\n/**\n * Technical stack definition for AI context\n */\nconst TECHNICAL_STACK = `\n## Technical Stack (v1 - Locked)\n\nThis is the production-ready stack used by Scaffoldry:\n\n- **Framework**: Next.js 14+ with App Router\n- **Language**: TypeScript (strict mode)\n- **Database**: PostgreSQL via Neon (serverless)\n- **ORM**: Drizzle ORM with migrations\n- **Auth**: Custom implementation (password + magic link)\n- **Payments**: Stripe (subscriptions + one-time)\n- **Email**: Resend (transactional emails)\n- **Storage**: AWS S3 (file uploads)\n- **Styling**: Tailwind CSS\n- **Validation**: Zod\n- **Testing**: Vitest\n\n### File Structure\n\\`\\`\\`\napps/web/\n├── src/\n│ ├── app/ # Next.js App Router pages\n│ ├── components/ # React components\n│ └── lib/ # Utilities and helpers\npackages/\n├── database/ # Drizzle schema and migrations\n├── auth/ # Authentication logic\n├── billing/ # Stripe integration\n├── email/ # Email templates and sending\n└── shared/ # Shared types and utilities\n\\`\\`\\`\n`;\n\n/**\n * Generate the development plan markdown\n */\nfunction generateDevelopmentPlan(\n projectName: string,\n description: string,\n features: string[]\n): string {\n return `# Project: ${projectName}\n\n## Executive Summary\n${description}\n\n## Technical Stack\n- **Frontend**: Next.js 14 with App Router, TypeScript, Tailwind CSS\n- **Backend**: Next.js API Routes, PostgreSQL (Neon)\n- **Authentication**: Email/password + Magic links\n- **Payments**: Stripe subscriptions\n- **Email**: Transactional emails via Resend\n- **Database**: Neon (serverless PostgreSQL) + Drizzle ORM\n\n## Core Features\n${features.map(f => `- ${f}`).join(\"\\n\")}\n\n## Implementation Phases\n\n### Phase 1: Foundation\n- [ ] Project setup and configuration\n- [ ] Database schema design\n- [ ] User authentication flows\n- [ ] Basic CRUD operations\n\n### Phase 2: Core Features\n- [ ] Implement primary features\n- [ ] API endpoints\n- [ ] Frontend components\n- [ ] Email notifications\n\n### Phase 3: Polish\n- [ ] Error handling\n- [ ] Loading states\n- [ ] Testing\n- [ ] Documentation\n\n## Security Considerations\n- Row-level security for multi-tenancy\n- Rate limiting on all endpoints\n- Input validation with Zod\n- Audit logging for sensitive operations\n\n## Deployment\n- Vercel (recommended) or any Node.js host\n- Neon for database\n- Environment variables configured via \\`scaffoldry setup\\`\n`;\n}\n\n/**\n * Generate the AI prompt with Master Spec context\n */\nfunction generateAIPrompt(\n projectName: string,\n description: string,\n features: string[]\n): string {\n return `# AI Implementation Prompt for ${projectName}\n\n## Project Description\n${description}\n\n## Features to Implement\n${features.map((f, i) => `${i + 1}. ${f}`).join(\"\\n\")}\n\n---\n\n# CRITICAL: Scaffoldry Framework Context\n\nYou are implementing features for a Scaffoldry project. You MUST follow these constraints exactly.\n\n${MASTER_SPEC_INVARIANTS}\n\n${MASTER_SPEC_SECURITY}\n\n${TECHNICAL_STACK}\n\n---\n\n## Implementation Guidelines\n\nWhen generating code:\n\n1. **Use existing patterns** - Check existing code in the codebase for patterns\n2. **Follow the schema** - Database tables in packages/database/src/schema/\n3. **Type everything** - No \\`any\\` types, use Zod for runtime validation\n4. **Handle errors** - All API routes must have try/catch with proper error responses\n5. **Add audit logs** - Log sensitive operations to the audit table\n6. **Test critical paths** - Auth, payments, and data access MUST have tests\n\n## Example Patterns\n\n### API Route Pattern\n\\`\\`\\`typescript\n// apps/web/src/app/api/example/route.ts\nimport { NextRequest, NextResponse } from \"next/server\";\nimport { z } from \"zod\";\nimport { getSession } from \"@scaffoldry/auth\";\nimport { db } from \"@scaffoldry/database\";\n\nconst schema = z.object({\n // Define your schema\n});\n\nexport async function POST(req: NextRequest) {\n try {\n const session = await getSession();\n if (!session) {\n return NextResponse.json({ error: \"Unauthorized\" }, { status: 401 });\n }\n\n const body = await req.json();\n const data = schema.parse(body);\n\n // Implementation here\n\n return NextResponse.json({ success: true });\n } catch (error) {\n if (error instanceof z.ZodError) {\n return NextResponse.json({ error: \"Invalid input\" }, { status: 400 });\n }\n console.error(\"API Error:\", error);\n return NextResponse.json({ error: \"Internal error\" }, { status: 500 });\n }\n}\n\\`\\`\\`\n\n### Database Query Pattern\n\\`\\`\\`typescript\nimport { db, eq, and } from \"@scaffoldry/database\";\nimport { users } from \"@scaffoldry/database/schema\";\n\n// Always filter by tenant_id for multi-tenancy\nconst user = await db.query.users.findFirst({\n where: and(\n eq(users.id, userId),\n eq(users.tenantId, session.tenantId)\n ),\n});\n\\`\\`\\`\n\n---\n\nNow implement the features described above, following all constraints and patterns.\n`;\n}\n\n/**\n * Plan command - generate AI-ready implementation prompts\n */\nexport async function planCommand(): Promise<void> {\n console.log();\n printBox(\"Project Planner\", [\n \"Let's create a development plan for your SaaS.\",\n \"\",\n \"This will generate:\",\n \"• DEVELOPMENT_PLAN.md - Share with your team\",\n \"• AI_PROMPT.md - Paste into Claude, GPT, or Cursor\",\n ]);\n\n // Get project description\n const { description } = await prompts({\n type: \"text\",\n name: \"description\",\n message: \"Describe what you want to build:\",\n validate: (value) => value.length > 10 ? true : \"Please provide more detail\",\n });\n\n if (!description) {\n printInfo(\"Cancelled\");\n return;\n }\n\n // Get features\n console.log();\n printInfo(\"Now list the key features (one per line, empty line to finish):\");\n console.log();\n\n const features: string[] = [];\n let collecting = true;\n\n while (collecting) {\n const { feature } = await prompts({\n type: \"text\",\n name: \"feature\",\n message: features.length === 0 ? \"Feature 1:\" : `Feature ${features.length + 1}:`,\n });\n\n if (!feature || feature.trim() === \"\") {\n if (features.length === 0) {\n printInfo(\"Please add at least one feature\");\n continue;\n }\n collecting = false;\n } else {\n features.push(feature.trim());\n }\n }\n\n // Generate project name from description\n const projectName = description\n .split(\" \")\n .slice(0, 3)\n .map((w: string) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())\n .join(\"\");\n\n // Review\n console.log();\n console.log(\"┌\" + \"─\".repeat(61) + \"┐\");\n console.log(\"│\" + \" \".repeat(61) + \"│\");\n console.log(\"│\" + \" \" + `Project: ${projectName}`.padEnd(58) + \"│\");\n console.log(\"│\" + \" \".repeat(61) + \"│\");\n console.log(\"│\" + \" Features:\".padEnd(58) + \"│\");\n for (const feature of features) {\n console.log(\"│\" + ` • ${feature}`.slice(0, 58).padEnd(58) + \"│\");\n }\n console.log(\"│\" + \" \".repeat(61) + \"│\");\n console.log(\"└\" + \"─\".repeat(61) + \"┘\");\n\n const { confirm } = await prompts({\n type: \"confirm\",\n name: \"confirm\",\n message: \"Generate plans?\",\n initial: true,\n });\n\n if (!confirm) {\n printInfo(\"Cancelled\");\n return;\n }\n\n // Generate files\n console.log();\n console.log(\"Generating implementation plan...\");\n\n const devPlan = generateDevelopmentPlan(projectName, description, features);\n const aiPrompt = generateAIPrompt(projectName, description, features);\n\n const projectDir = process.cwd();\n\n await fs.writeFile(path.join(projectDir, \"DEVELOPMENT_PLAN.md\"), devPlan);\n await fs.writeFile(path.join(projectDir, \"AI_PROMPT.md\"), aiPrompt);\n\n // Also save to ~/.scaffoldry/projects/\n await saveProjectData(projectName, \"plan.md\", devPlan);\n await saveProjectData(projectName, \"ai-prompt.md\", aiPrompt);\n\n printSuccess(\"Plan saved to: ./DEVELOPMENT_PLAN.md\");\n printSuccess(\"AI prompt saved to: ./AI_PROMPT.md\");\n\n // Offer to copy to clipboard\n console.log();\n const { wantsCopy } = await prompts({\n type: \"confirm\",\n name: \"wantsCopy\",\n message: \"Copy AI prompt to clipboard?\",\n initial: true,\n });\n\n if (wantsCopy) {\n const copied = await copyToClipboard(aiPrompt);\n if (copied) {\n printSuccess(\"Copied to clipboard!\");\n } else {\n printInfo(\"Could not copy to clipboard. Open AI_PROMPT.md manually.\");\n }\n }\n\n // Next steps\n console.log();\n printBox(\"Next Steps\", [\n \"1. Review DEVELOPMENT_PLAN.md with your team\",\n \"2. Use AI_PROMPT.md with Claude, GPT, or Cursor\",\n \"3. The AI prompt includes critical Scaffoldry\",\n \" constraints for compliant code generation\",\n ]);\n}\n"],"mappings":"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","names":["setupAllCommand","spinner","validation","prompts","ora","fs","path","path","fs","prompts","ora","prompts","prompts","path","fs","ora","ora","path","fs","path","fs","ora","path","fs","ora","path","fs","ora","path","fs","ora","path","fs","glob","ora","path","fs","glob","ora","chalk","prompts","chalk","prompts","setupCommand","prompts","prompts","newSecret","os","fs","path","prompts","prompts","fs","path"]}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// src/utils/platform.ts
|
|
2
|
+
import { exec, spawn } from "child_process";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
import os from "os";
|
|
5
|
+
var execAsync = promisify(exec);
|
|
6
|
+
function detectPlatform() {
|
|
7
|
+
const platform = os.platform();
|
|
8
|
+
if (platform === "linux") {
|
|
9
|
+
try {
|
|
10
|
+
const release = os.release().toLowerCase();
|
|
11
|
+
if (release.includes("microsoft") || release.includes("wsl")) {
|
|
12
|
+
return "wsl";
|
|
13
|
+
}
|
|
14
|
+
} catch {
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return platform;
|
|
18
|
+
}
|
|
19
|
+
function isWindows() {
|
|
20
|
+
const platform = detectPlatform();
|
|
21
|
+
return platform === "win32";
|
|
22
|
+
}
|
|
23
|
+
function isWSL() {
|
|
24
|
+
return detectPlatform() === "wsl";
|
|
25
|
+
}
|
|
26
|
+
function isMacOS() {
|
|
27
|
+
return detectPlatform() === "darwin";
|
|
28
|
+
}
|
|
29
|
+
function isLinux() {
|
|
30
|
+
return detectPlatform() === "linux";
|
|
31
|
+
}
|
|
32
|
+
async function openBrowser(url) {
|
|
33
|
+
const platform = detectPlatform();
|
|
34
|
+
try {
|
|
35
|
+
switch (platform) {
|
|
36
|
+
case "darwin":
|
|
37
|
+
await execAsync(`open "${url}"`);
|
|
38
|
+
break;
|
|
39
|
+
case "win32":
|
|
40
|
+
await execAsync(`start "" "${url}"`);
|
|
41
|
+
break;
|
|
42
|
+
case "wsl":
|
|
43
|
+
await execAsync(`cmd.exe /c start "" "${url}"`);
|
|
44
|
+
break;
|
|
45
|
+
case "linux":
|
|
46
|
+
try {
|
|
47
|
+
await execAsync(`xdg-open "${url}"`);
|
|
48
|
+
} catch {
|
|
49
|
+
try {
|
|
50
|
+
await execAsync(`sensible-browser "${url}"`);
|
|
51
|
+
} catch {
|
|
52
|
+
await execAsync(`x-www-browser "${url}"`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function copyToClipboard(text) {
|
|
61
|
+
const platform = detectPlatform();
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
let proc;
|
|
64
|
+
try {
|
|
65
|
+
switch (platform) {
|
|
66
|
+
case "darwin":
|
|
67
|
+
proc = spawn("pbcopy");
|
|
68
|
+
break;
|
|
69
|
+
case "win32":
|
|
70
|
+
proc = spawn("clip");
|
|
71
|
+
break;
|
|
72
|
+
case "wsl":
|
|
73
|
+
proc = spawn("clip.exe");
|
|
74
|
+
break;
|
|
75
|
+
case "linux":
|
|
76
|
+
proc = spawn("xclip", ["-selection", "clipboard"]);
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
resolve(false);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
proc.on("error", () => resolve(false));
|
|
83
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
84
|
+
if (proc.stdin) {
|
|
85
|
+
proc.stdin.write(text);
|
|
86
|
+
proc.stdin.end();
|
|
87
|
+
} else {
|
|
88
|
+
resolve(false);
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
resolve(false);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function getInstallInstructions(tool) {
|
|
96
|
+
const platform = detectPlatform();
|
|
97
|
+
switch (tool) {
|
|
98
|
+
case "stripe-cli":
|
|
99
|
+
return getStripeCLIInstructions(platform);
|
|
100
|
+
case "node":
|
|
101
|
+
return getNodeInstructions(platform);
|
|
102
|
+
case "pnpm":
|
|
103
|
+
return getPnpmInstructions(platform);
|
|
104
|
+
case "git":
|
|
105
|
+
return getGitInstructions(platform);
|
|
106
|
+
default:
|
|
107
|
+
return "See official documentation for installation instructions";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function getStripeCLIInstructions(platform) {
|
|
111
|
+
switch (platform) {
|
|
112
|
+
case "darwin":
|
|
113
|
+
return "Run: brew install stripe/stripe-cli/stripe";
|
|
114
|
+
case "win32":
|
|
115
|
+
return "Run: scoop install stripe\n Or: choco install stripe-cli\n Or: winget install Stripe.StripeCLI";
|
|
116
|
+
case "wsl":
|
|
117
|
+
case "linux":
|
|
118
|
+
return `For Ubuntu/Debian:
|
|
119
|
+
curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg --dearmor | sudo tee /usr/share/keyrings/stripe.gpg
|
|
120
|
+
echo "deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main" | sudo tee -a /etc/apt/sources.list.d/stripe.list
|
|
121
|
+
sudo apt update && sudo apt install stripe
|
|
122
|
+
|
|
123
|
+
For other distros: https://stripe.com/docs/stripe-cli#install`;
|
|
124
|
+
default:
|
|
125
|
+
return "See https://stripe.com/docs/stripe-cli#install";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function getNodeInstructions(platform) {
|
|
129
|
+
switch (platform) {
|
|
130
|
+
case "darwin":
|
|
131
|
+
return "Run: brew install node@20\n Or use nvm: nvm install 20";
|
|
132
|
+
case "win32":
|
|
133
|
+
return "Download from: https://nodejs.org\n Or: winget install OpenJS.NodeJS.LTS\n Or: scoop install nodejs-lts";
|
|
134
|
+
case "wsl":
|
|
135
|
+
case "linux":
|
|
136
|
+
return `Using nvm (recommended):
|
|
137
|
+
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
|
138
|
+
nvm install 20
|
|
139
|
+
|
|
140
|
+
Or using apt (Ubuntu/Debian):
|
|
141
|
+
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
|
142
|
+
sudo apt install -y nodejs`;
|
|
143
|
+
default:
|
|
144
|
+
return "See https://nodejs.org for installation instructions";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function getPnpmInstructions(_platform) {
|
|
148
|
+
return `Run: npm install -g pnpm
|
|
149
|
+
Or: corepack enable && corepack prepare pnpm@latest --activate`;
|
|
150
|
+
}
|
|
151
|
+
function getGitInstructions(platform) {
|
|
152
|
+
switch (platform) {
|
|
153
|
+
case "darwin":
|
|
154
|
+
return "Run: brew install git\n Or: xcode-select --install";
|
|
155
|
+
case "win32":
|
|
156
|
+
return "Download from: https://git-scm.com/download/win\n Or: winget install Git.Git";
|
|
157
|
+
case "wsl":
|
|
158
|
+
case "linux":
|
|
159
|
+
return "Run: sudo apt install git (Ubuntu/Debian)\n Or: sudo dnf install git (Fedora)";
|
|
160
|
+
default:
|
|
161
|
+
return "See https://git-scm.com/downloads";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function getSpawnOptions(options = {}) {
|
|
165
|
+
const platform = detectPlatform();
|
|
166
|
+
const baseOptions = {
|
|
167
|
+
...options,
|
|
168
|
+
shell: true
|
|
169
|
+
};
|
|
170
|
+
if (platform === "win32") {
|
|
171
|
+
baseOptions.windowsHide = true;
|
|
172
|
+
}
|
|
173
|
+
return baseOptions;
|
|
174
|
+
}
|
|
175
|
+
function spawnCrossplatform(command, args = [], options = {}) {
|
|
176
|
+
return spawn(command, args, getSpawnOptions(options));
|
|
177
|
+
}
|
|
178
|
+
function getTermSignal() {
|
|
179
|
+
return "SIGTERM";
|
|
180
|
+
}
|
|
181
|
+
function getKillSignal() {
|
|
182
|
+
return "SIGKILL";
|
|
183
|
+
}
|
|
184
|
+
function setupShutdownHandlers(cleanup) {
|
|
185
|
+
const platform = detectPlatform();
|
|
186
|
+
process.on("SIGINT", cleanup);
|
|
187
|
+
process.on("SIGTERM", cleanup);
|
|
188
|
+
if (platform === "win32") {
|
|
189
|
+
process.on("SIGHUP", cleanup);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function getHomeDir() {
|
|
193
|
+
return os.homedir();
|
|
194
|
+
}
|
|
195
|
+
function getLineEnding() {
|
|
196
|
+
return "\n";
|
|
197
|
+
}
|
|
198
|
+
function normalizePath(filePath) {
|
|
199
|
+
return filePath.replace(/\\/g, "/");
|
|
200
|
+
}
|
|
201
|
+
async function commandExists(command) {
|
|
202
|
+
const platform = detectPlatform();
|
|
203
|
+
try {
|
|
204
|
+
if (platform === "win32") {
|
|
205
|
+
await execAsync(`where ${command}`);
|
|
206
|
+
} else {
|
|
207
|
+
await execAsync(`which ${command}`);
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
210
|
+
} catch {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function getPlatformDisplayName() {
|
|
215
|
+
const platform = detectPlatform();
|
|
216
|
+
switch (platform) {
|
|
217
|
+
case "darwin":
|
|
218
|
+
return "macOS";
|
|
219
|
+
case "win32":
|
|
220
|
+
return "Windows";
|
|
221
|
+
case "wsl":
|
|
222
|
+
return "Windows Subsystem for Linux (WSL)";
|
|
223
|
+
case "linux":
|
|
224
|
+
return "Linux";
|
|
225
|
+
default:
|
|
226
|
+
return "Unknown";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export {
|
|
231
|
+
detectPlatform,
|
|
232
|
+
isWindows,
|
|
233
|
+
isWSL,
|
|
234
|
+
isMacOS,
|
|
235
|
+
isLinux,
|
|
236
|
+
openBrowser,
|
|
237
|
+
copyToClipboard,
|
|
238
|
+
getInstallInstructions,
|
|
239
|
+
getSpawnOptions,
|
|
240
|
+
spawnCrossplatform,
|
|
241
|
+
getTermSignal,
|
|
242
|
+
getKillSignal,
|
|
243
|
+
setupShutdownHandlers,
|
|
244
|
+
getHomeDir,
|
|
245
|
+
getLineEnding,
|
|
246
|
+
normalizePath,
|
|
247
|
+
commandExists,
|
|
248
|
+
getPlatformDisplayName
|
|
249
|
+
};
|
|
250
|
+
//# sourceMappingURL=chunk-WOS3F5LR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/platform.ts"],"sourcesContent":["import { exec, spawn, type ChildProcess, type SpawnOptions } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport os from \"node:os\";\n\nconst execAsync = promisify(exec);\n\nexport type Platform = \"darwin\" | \"win32\" | \"linux\" | \"wsl\";\n\n/**\n * Detect the current platform, including WSL detection\n */\nexport function detectPlatform(): Platform {\n const platform = os.platform();\n\n if (platform === \"linux\") {\n // Check if running in WSL\n try {\n const release = os.release().toLowerCase();\n if (release.includes(\"microsoft\") || release.includes(\"wsl\")) {\n return \"wsl\";\n }\n } catch {\n // Ignore errors, assume native Linux\n }\n }\n\n return platform as Platform;\n}\n\n/**\n * Check if running on Windows (native or WSL)\n */\nexport function isWindows(): boolean {\n const platform = detectPlatform();\n return platform === \"win32\";\n}\n\n/**\n * Check if running in WSL\n */\nexport function isWSL(): boolean {\n return detectPlatform() === \"wsl\";\n}\n\n/**\n * Check if running on macOS\n */\nexport function isMacOS(): boolean {\n return detectPlatform() === \"darwin\";\n}\n\n/**\n * Check if running on Linux (native, not WSL)\n */\nexport function isLinux(): boolean {\n return detectPlatform() === \"linux\";\n}\n\n/**\n * Open a URL in the default browser\n */\nexport async function openBrowser(url: string): Promise<void> {\n const platform = detectPlatform();\n\n try {\n switch (platform) {\n case \"darwin\":\n await execAsync(`open \"${url}\"`);\n break;\n case \"win32\":\n // Windows needs special handling - use start with empty title\n await execAsync(`start \"\" \"${url}\"`);\n break;\n case \"wsl\":\n // In WSL, use Windows browser via cmd.exe\n await execAsync(`cmd.exe /c start \"\" \"${url}\"`);\n break;\n case \"linux\":\n // Try xdg-open first, fall back to other options\n try {\n await execAsync(`xdg-open \"${url}\"`);\n } catch {\n // Try alternatives\n try {\n await execAsync(`sensible-browser \"${url}\"`);\n } catch {\n await execAsync(`x-www-browser \"${url}\"`);\n }\n }\n break;\n }\n } catch {\n // Silently fail - user can manually open URL\n }\n}\n\n/**\n * Copy text to clipboard\n */\nexport async function copyToClipboard(text: string): Promise<boolean> {\n const platform = detectPlatform();\n\n return new Promise((resolve) => {\n let proc: ChildProcess;\n\n try {\n switch (platform) {\n case \"darwin\":\n proc = spawn(\"pbcopy\");\n break;\n case \"win32\":\n proc = spawn(\"clip\");\n break;\n case \"wsl\":\n // In WSL, use Windows clip.exe\n proc = spawn(\"clip.exe\");\n break;\n case \"linux\":\n // Try xclip first, could also try xsel\n proc = spawn(\"xclip\", [\"-selection\", \"clipboard\"]);\n break;\n default:\n resolve(false);\n return;\n }\n\n proc.on(\"error\", () => resolve(false));\n proc.on(\"close\", (code) => resolve(code === 0));\n\n if (proc.stdin) {\n proc.stdin.write(text);\n proc.stdin.end();\n } else {\n resolve(false);\n }\n } catch {\n resolve(false);\n }\n });\n}\n\n/**\n * Get platform-specific installation instructions for a tool\n */\nexport function getInstallInstructions(tool: \"stripe-cli\" | \"node\" | \"pnpm\" | \"git\"): string {\n const platform = detectPlatform();\n\n switch (tool) {\n case \"stripe-cli\":\n return getStripeCLIInstructions(platform);\n case \"node\":\n return getNodeInstructions(platform);\n case \"pnpm\":\n return getPnpmInstructions(platform);\n case \"git\":\n return getGitInstructions(platform);\n default:\n return \"See official documentation for installation instructions\";\n }\n}\n\nfunction getStripeCLIInstructions(platform: Platform): string {\n switch (platform) {\n case \"darwin\":\n return \"Run: brew install stripe/stripe-cli/stripe\";\n case \"win32\":\n return \"Run: scoop install stripe\\n Or: choco install stripe-cli\\n Or: winget install Stripe.StripeCLI\";\n case \"wsl\":\n case \"linux\":\n return `For Ubuntu/Debian:\n curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg --dearmor | sudo tee /usr/share/keyrings/stripe.gpg\n echo \"deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main\" | sudo tee -a /etc/apt/sources.list.d/stripe.list\n sudo apt update && sudo apt install stripe\n\nFor other distros: https://stripe.com/docs/stripe-cli#install`;\n default:\n return \"See https://stripe.com/docs/stripe-cli#install\";\n }\n}\n\nfunction getNodeInstructions(platform: Platform): string {\n switch (platform) {\n case \"darwin\":\n return \"Run: brew install node@20\\n Or use nvm: nvm install 20\";\n case \"win32\":\n return \"Download from: https://nodejs.org\\n Or: winget install OpenJS.NodeJS.LTS\\n Or: scoop install nodejs-lts\";\n case \"wsl\":\n case \"linux\":\n return `Using nvm (recommended):\n curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash\n nvm install 20\n\nOr using apt (Ubuntu/Debian):\n curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -\n sudo apt install -y nodejs`;\n default:\n return \"See https://nodejs.org for installation instructions\";\n }\n}\n\nfunction getPnpmInstructions(_platform: Platform): string {\n // pnpm installation is mostly cross-platform\n return `Run: npm install -g pnpm\n Or: corepack enable && corepack prepare pnpm@latest --activate`;\n}\n\nfunction getGitInstructions(platform: Platform): string {\n switch (platform) {\n case \"darwin\":\n return \"Run: brew install git\\n Or: xcode-select --install\";\n case \"win32\":\n return \"Download from: https://git-scm.com/download/win\\n Or: winget install Git.Git\";\n case \"wsl\":\n case \"linux\":\n return \"Run: sudo apt install git (Ubuntu/Debian)\\n Or: sudo dnf install git (Fedora)\";\n default:\n return \"See https://git-scm.com/downloads\";\n }\n}\n\n/**\n * Get spawn options appropriate for the current platform\n */\nexport function getSpawnOptions(options: SpawnOptions = {}): SpawnOptions {\n const platform = detectPlatform();\n\n const baseOptions: SpawnOptions = {\n ...options,\n shell: true,\n };\n\n // Windows-specific options\n if (platform === \"win32\") {\n baseOptions.windowsHide = true;\n }\n\n return baseOptions;\n}\n\n/**\n * Spawn a process with cross-platform compatibility\n */\nexport function spawnCrossplatform(\n command: string,\n args: string[] = [],\n options: SpawnOptions = {}\n): ChildProcess {\n return spawn(command, args, getSpawnOptions(options));\n}\n\n/**\n * Get the appropriate signal to gracefully terminate a process\n */\nexport function getTermSignal(): NodeJS.Signals {\n // Windows doesn't support SIGTERM well, but Node handles it internally\n return \"SIGTERM\";\n}\n\n/**\n * Get the appropriate signal to forcefully kill a process\n */\nexport function getKillSignal(): NodeJS.Signals {\n // Windows doesn't support SIGKILL, but Node translates it\n return \"SIGKILL\";\n}\n\n/**\n * Set up graceful shutdown handlers\n */\nexport function setupShutdownHandlers(cleanup: () => void): void {\n const platform = detectPlatform();\n\n // SIGINT works on all platforms (Ctrl+C)\n process.on(\"SIGINT\", cleanup);\n\n // SIGTERM works on Unix, Node emulates on Windows\n process.on(\"SIGTERM\", cleanup);\n\n // Windows-specific: handle SIGHUP\n if (platform === \"win32\") {\n process.on(\"SIGHUP\", cleanup);\n }\n}\n\n/**\n * Get the user's home directory in a cross-platform way\n */\nexport function getHomeDir(): string {\n return os.homedir();\n}\n\n/**\n * Get the appropriate line ending for the current platform\n */\nexport function getLineEnding(): string {\n // Always use LF for consistency in config files\n return \"\\n\";\n}\n\n/**\n * Normalize a path for the current platform\n */\nexport function normalizePath(filePath: string): string {\n // Convert backslashes to forward slashes for consistency\n return filePath.replace(/\\\\/g, \"/\");\n}\n\n/**\n * Check if a command exists on the system\n */\nexport async function commandExists(command: string): Promise<boolean> {\n const platform = detectPlatform();\n\n try {\n if (platform === \"win32\") {\n await execAsync(`where ${command}`);\n } else {\n await execAsync(`which ${command}`);\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get platform display name\n */\nexport function getPlatformDisplayName(): string {\n const platform = detectPlatform();\n\n switch (platform) {\n case \"darwin\":\n return \"macOS\";\n case \"win32\":\n return \"Windows\";\n case \"wsl\":\n return \"Windows Subsystem for Linux (WSL)\";\n case \"linux\":\n return \"Linux\";\n default:\n return \"Unknown\";\n }\n}\n"],"mappings":"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","names":[]}
|