@weirdfingers/baseboards 0.2.1 → 0.4.0
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/README.md +14 -4
- package/dist/index.js +13 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/api/ARTIFACT_RESOLUTION_GUIDE.md +148 -0
- package/templates/api/Dockerfile +2 -2
- package/templates/api/README.md +138 -6
- package/templates/api/config/generators.yaml +41 -7
- package/templates/api/docs/TESTING_LIVE_APIS.md +417 -0
- package/templates/api/pyproject.toml +49 -9
- package/templates/api/src/boards/__init__.py +1 -1
- package/templates/api/src/boards/auth/adapters/__init__.py +9 -2
- package/templates/api/src/boards/auth/factory.py +16 -2
- package/templates/api/src/boards/generators/__init__.py +2 -2
- package/templates/api/src/boards/generators/artifact_resolution.py +372 -0
- package/templates/api/src/boards/generators/artifacts.py +4 -4
- package/templates/api/src/boards/generators/base.py +8 -4
- package/templates/api/src/boards/generators/implementations/__init__.py +4 -2
- package/templates/api/src/boards/generators/implementations/fal/__init__.py +25 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +4 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_music_v2.py +173 -0
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +221 -0
- package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +17 -0
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_kontext.py +216 -0
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_ultra.py +197 -0
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview.py +191 -0
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview_fast.py +179 -0
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana.py +183 -0
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_edit.py +212 -0
- package/templates/api/src/boards/generators/implementations/fal/utils.py +61 -0
- package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +13 -0
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_text_to_video.py +168 -0
- package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2.py +167 -0
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +180 -0
- package/templates/api/src/boards/generators/implementations/openai/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/openai/audio/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/{audio → openai/audio}/whisper.py +9 -6
- package/templates/api/src/boards/generators/implementations/openai/image/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/{image → openai/image}/dalle3.py +8 -5
- package/templates/api/src/boards/generators/implementations/replicate/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/replicate/image/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/{image → replicate/image}/flux_pro.py +8 -5
- package/templates/api/src/boards/generators/implementations/replicate/video/__init__.py +1 -0
- package/templates/api/src/boards/generators/implementations/{video → replicate/video}/lipsync.py +9 -6
- package/templates/api/src/boards/generators/resolution.py +80 -20
- package/templates/api/src/boards/jobs/repository.py +49 -0
- package/templates/api/src/boards/storage/factory.py +16 -6
- package/templates/api/src/boards/workers/actors.py +69 -5
- package/templates/api/src/boards/workers/context.py +177 -21
- package/templates/web/package.json +2 -1
- package/templates/web/src/components/boards/GenerationInput.tsx +154 -52
- package/templates/web/src/components/boards/GeneratorSelector.tsx +57 -59
- package/templates/web/src/components/ui/dropdown-menu.tsx +200 -0
- package/templates/api/src/boards/generators/implementations/audio/__init__.py +0 -3
- package/templates/api/src/boards/generators/implementations/image/__init__.py +0 -3
- package/templates/api/src/boards/generators/implementations/video/__init__.py +0 -3
package/README.md
CHANGED
|
@@ -19,13 +19,13 @@ open http://localhost:3300
|
|
|
19
19
|
|
|
20
20
|
### `up [directory]`
|
|
21
21
|
|
|
22
|
-
Scaffold and start Baseboards. If the directory doesn't exist, creates a new project from templates.
|
|
22
|
+
Scaffold and start Baseboards. If the directory doesn't exist, creates a new project from templates. Runs in detached mode (background) by default.
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
baseboards up # Current directory
|
|
26
|
-
baseboards up my-app # New directory
|
|
25
|
+
baseboards up # Current directory (detached)
|
|
26
|
+
baseboards up my-app # New directory (detached)
|
|
27
27
|
baseboards up --prod # Production mode
|
|
28
|
-
baseboards up --
|
|
28
|
+
baseboards up --attach # Attach to logs (foreground)
|
|
29
29
|
baseboards up --ports web=3300 # Custom ports
|
|
30
30
|
```
|
|
31
31
|
|
|
@@ -186,6 +186,16 @@ Templates are copied during build time via `scripts/prepare-templates.js`.
|
|
|
186
186
|
|
|
187
187
|
When users run `baseboards up`, templates are copied to their machine and Docker Compose orchestrates the services.
|
|
188
188
|
|
|
189
|
+
## Community & Social
|
|
190
|
+
|
|
191
|
+
Join the Weirdfingers community:
|
|
192
|
+
|
|
193
|
+
- **TikTok**: [https://www.tiktok.com/@weirdfingers](https://www.tiktok.com/@weirdfingers)
|
|
194
|
+
- **X (Twitter)**: [https://x.com/_Weirdfingers_](https://x.com/_Weirdfingers_)
|
|
195
|
+
- **YouTube**: [https://www.youtube.com/@Weirdfingers](https://www.youtube.com/@Weirdfingers)
|
|
196
|
+
- **Discord**: [https://discord.gg/rvVuHyuPEx](https://discord.gg/rvVuHyuPEx)
|
|
197
|
+
- **Instagram**: [https://www.instagram.com/_weirdfingers_/](https://www.instagram.com/_weirdfingers_/)
|
|
198
|
+
|
|
189
199
|
## License
|
|
190
200
|
|
|
191
201
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -127,7 +127,7 @@ function generateSecret(length = 32) {
|
|
|
127
127
|
return crypto.randomBytes(length).toString("hex");
|
|
128
128
|
}
|
|
129
129
|
function generatePassword(length = 24) {
|
|
130
|
-
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
|
|
130
|
+
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#%^&*";
|
|
131
131
|
let password = "";
|
|
132
132
|
const bytes = crypto.randomBytes(length);
|
|
133
133
|
for (let i = 0; i < length; i++) {
|
|
@@ -269,8 +269,8 @@ async function up(directory, options) {
|
|
|
269
269
|
await startDockerCompose(ctx, true);
|
|
270
270
|
await waitForHealthy(ctx);
|
|
271
271
|
await runMigrations(ctx);
|
|
272
|
-
printSuccessMessage(ctx, options.
|
|
273
|
-
if (
|
|
272
|
+
printSuccessMessage(ctx, !options.attach, missingKeys.length > 0);
|
|
273
|
+
if (options.attach) {
|
|
274
274
|
try {
|
|
275
275
|
await attachToLogs(ctx);
|
|
276
276
|
} catch (error) {
|
|
@@ -369,6 +369,12 @@ async function promptForApiKeys(ctx) {
|
|
|
369
369
|
message: "Replicate API Key (https://replicate.com/account/api-tokens):",
|
|
370
370
|
initial: ""
|
|
371
371
|
},
|
|
372
|
+
{
|
|
373
|
+
type: "text",
|
|
374
|
+
name: "FAL_KEY",
|
|
375
|
+
message: "Fal AI API Key (https://fal.ai/dashboard/keys):",
|
|
376
|
+
initial: ""
|
|
377
|
+
},
|
|
372
378
|
{
|
|
373
379
|
type: "text",
|
|
374
380
|
name: "OPENAI_API_KEY",
|
|
@@ -380,6 +386,9 @@ async function promptForApiKeys(ctx) {
|
|
|
380
386
|
if (response.REPLICATE_API_TOKEN && response.REPLICATE_API_TOKEN.trim()) {
|
|
381
387
|
apiKeys.REPLICATE_API_TOKEN = response.REPLICATE_API_TOKEN.trim();
|
|
382
388
|
}
|
|
389
|
+
if (response.FAL_KEY && response.FAL_KEY.trim()) {
|
|
390
|
+
apiKeys.FAL_KEY = response.FAL_KEY.trim();
|
|
391
|
+
}
|
|
383
392
|
if (response.OPENAI_API_KEY && response.OPENAI_API_KEY.trim()) {
|
|
384
393
|
apiKeys.OPENAI_API_KEY = response.OPENAI_API_KEY.trim();
|
|
385
394
|
}
|
|
@@ -850,7 +859,7 @@ var program = new Command();
|
|
|
850
859
|
program.name("baseboards").description(
|
|
851
860
|
"\u{1F3A8} One-command launcher for the Boards image generation platform"
|
|
852
861
|
).version(packageJson.version, "-v, --version", "Output the current version");
|
|
853
|
-
program.command("up").description("Start Baseboards (scaffolds if needed)").argument("[directory]", "Project directory", ".").option("--dev", "Development mode with hot reload (default)", true).option("--prod", "Production mode with prebuilt images").option("--
|
|
862
|
+
program.command("up").description("Start Baseboards (scaffolds if needed)").argument("[directory]", "Project directory", ".").option("--dev", "Development mode with hot reload (default)", true).option("--prod", "Production mode with prebuilt images").option("--attach", "Attach to logs (runs in foreground)").option("--ports <ports>", "Custom ports (e.g., web=3300 api=8800)").action(up);
|
|
854
863
|
program.command("down").description("Stop Baseboards").argument("[directory]", "Project directory", ".").option("--volumes", "Also remove volumes").action(down);
|
|
855
864
|
program.command("logs").description("View logs from services").argument("[directory]", "Project directory", ".").argument(
|
|
856
865
|
"[services...]",
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/up.ts","../src/utils.ts","../src/commands/down.ts","../src/commands/logs.ts","../src/commands/status.ts","../src/commands/clean.ts","../src/commands/update.ts","../src/commands/doctor.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Baseboards CLI\n *\n * Main entry point for the @weirdfingers/baseboards command-line interface.\n * Provides commands to scaffold, run, and manage Baseboards installations.\n */\n\nimport { Command } from \"commander\";\nimport { readFileSync } from \"fs\";\nimport { fileURLToPath } from \"url\";\nimport { dirname, join } from \"path\";\nimport chalk from \"chalk\";\n\n// Import commands\nimport { up } from \"./commands/up.js\";\nimport { down } from \"./commands/down.js\";\nimport { logs } from \"./commands/logs.js\";\nimport { status } from \"./commands/status.js\";\nimport { clean } from \"./commands/clean.js\";\nimport { update } from \"./commands/update.js\";\nimport { doctor } from \"./commands/doctor.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Read package.json for version\nconst packageJson = JSON.parse(\n readFileSync(join(__dirname, \"../package.json\"), \"utf-8\")\n);\n\nconst program = new Command();\n\nprogram\n .name(\"baseboards\")\n .description(\n \"🎨 One-command launcher for the Boards image generation platform\"\n )\n .version(packageJson.version, \"-v, --version\", \"Output the current version\");\n\n// up command\nprogram\n .command(\"up\")\n .description(\"Start Baseboards (scaffolds if needed)\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--dev\", \"Development mode with hot reload (default)\", true)\n .option(\"--prod\", \"Production mode with prebuilt images\")\n .option(\"--detached\", \"Run in detached mode (background)\")\n .option(\"--ports <ports>\", \"Custom ports (e.g., web=3300 api=8800)\")\n .action(up);\n\n// down command\nprogram\n .command(\"down\")\n .description(\"Stop Baseboards\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--volumes\", \"Also remove volumes\")\n .action(down);\n\n// logs command\nprogram\n .command(\"logs\")\n .description(\"View logs from services\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .argument(\n \"[services...]\",\n \"Services to show logs for (web, api, db, cache)\",\n []\n )\n .option(\"-f, --follow\", \"Follow log output\")\n .option(\"--since <time>\", \"Show logs since timestamp (e.g., 1h, 30m)\")\n .option(\"--tail <lines>\", \"Number of lines to show from end\", \"100\")\n .action(logs);\n\n// status command\nprogram\n .command(\"status\")\n .description(\"Show status of services\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .action(status);\n\n// clean command\nprogram\n .command(\"clean\")\n .description(\"Clean up Docker resources\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--hard\", \"Remove volumes and images (WARNING: deletes data)\")\n .action(clean);\n\n// update command\nprogram\n .command(\"update\")\n .description(\"Update Baseboards to latest version\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--force\", \"Force update without safety checks\")\n .option(\"--version <version>\", \"Update to specific version\")\n .action(update);\n\n// doctor command\nprogram\n .command(\"doctor\")\n .description(\"Run diagnostics and show system info\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .action(doctor);\n\n// Parse without exitOverride - let commander handle exits naturally\ntry {\n await program.parseAsync(process.argv);\n} catch (error: unknown) {\n const err = error as { message?: string; stderr?: string };\n // Actual error (not help/version)\n console.error(chalk.red(\"\\n❌ Error:\"), err.message || \"Unknown error\");\n\n if (err.stderr) {\n console.error(chalk.gray(\"\\nDetails:\"));\n console.error(chalk.gray(err.stderr));\n }\n\n console.error(\n chalk.yellow(\"\\n💡 Try running:\"),\n chalk.cyan(\"baseboards doctor\")\n );\n console.error(\n chalk.yellow(\"📖 Documentation:\"),\n chalk.cyan(\"https://baseboards.dev/docs\")\n );\n\n process.exit(1);\n}\n","/**\n * up command - Scaffold and start Baseboards\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport type { ProjectContext, UpOptions } from \"../types.js\";\nimport {\n assertPrerequisites,\n findAvailablePort,\n generatePassword,\n generateSecret,\n getCliVersion,\n getTemplatesDir,\n isScaffolded,\n parsePortsOption,\n detectMissingProviderKeys,\n waitFor,\n} from \"../utils.js\";\n\nexport async function up(directory: string, options: UpOptions): Promise<void> {\n console.log(chalk.blue.bold(\"\\n🎨 Baseboards CLI\\n\"));\n\n // Step 1: Check prerequisites\n const spinner = ora(\"Checking prerequisites...\").start();\n await assertPrerequisites();\n spinner.succeed(\"Prerequisites OK\");\n\n // Step 2: Resolve project context\n const dir = path.resolve(process.cwd(), directory);\n const name = path.basename(dir);\n const version = getCliVersion();\n const mode = options.prod ? \"prod\" : \"dev\";\n\n // Parse custom ports\n let customPorts = {};\n if (options.ports) {\n customPorts = parsePortsOption(options.ports);\n }\n\n // Default ports\n const defaultPorts = {\n web: 3300,\n api: 8800,\n db: 5432,\n redis: 6379,\n ...customPorts,\n };\n\n const ctx: ProjectContext = {\n dir,\n name,\n isScaffolded: isScaffolded(dir),\n ports: defaultPorts,\n mode,\n version,\n };\n\n // Track if this is a fresh scaffold to prompt for API keys later\n const isFreshScaffold = !ctx.isScaffolded;\n\n // Step 3: Scaffold if needed\n if (!ctx.isScaffolded) {\n console.log(\n chalk.cyan(`\\n📦 Scaffolding new project: ${chalk.bold(name)}`)\n );\n await scaffoldProject(ctx);\n } else {\n console.log(\n chalk.green(`\\n✅ Project already scaffolded: ${chalk.bold(name)}`)\n );\n }\n\n // Step 4: Check ports availability\n spinner.start(\"Checking port availability...\");\n ctx.ports.web = await findAvailablePort(ctx.ports.web);\n ctx.ports.api = await findAvailablePort(ctx.ports.api);\n spinner.succeed(\n `Ports available: web=${ctx.ports.web}, api=${ctx.ports.api}`\n );\n\n // Step 5: Ensure environment files\n await ensureEnvFiles(ctx);\n\n // Step 5.5: Prompt for API keys (only on fresh scaffold)\n if (isFreshScaffold) {\n await promptForApiKeys(ctx);\n }\n\n // Step 6: Detect missing API keys\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n const missingKeys = detectMissingProviderKeys(apiEnvPath);\n\n if (missingKeys.length > 0) {\n console.log(chalk.yellow(\"\\n⚠️ Provider API keys not configured!\"));\n console.log(chalk.gray(\" Add at least one API key to api/.env:\"));\n console.log(\n chalk.cyan(\" • REPLICATE_API_TOKEN\") +\n chalk.gray(\" - https://replicate.com/account/api-tokens\")\n );\n console.log(\n chalk.cyan(\" • FAL_KEY\") +\n chalk.gray(\" - https://fal.ai/dashboard/keys\")\n );\n console.log(\n chalk.cyan(\" • OPENAI_API_KEY\") +\n chalk.gray(\" - https://platform.openai.com/api-keys\")\n );\n console.log(\n chalk.cyan(\" • GOOGLE_API_KEY\") +\n chalk.gray(\" - https://makersuite.google.com/app/apikey\")\n );\n console.log(\n chalk.gray(\n \"\\n The app will start, but image generation won't work without keys.\\n\"\n )\n );\n }\n\n // Step 7: Start Docker Compose (always detached initially)\n await startDockerCompose(ctx, true);\n\n // Step 8: Wait for health checks\n await waitForHealthy(ctx);\n\n // Step 9: Run database migrations\n await runMigrations(ctx);\n\n // Step 10: Print success message\n printSuccessMessage(ctx, options.detached || false, missingKeys.length > 0);\n\n // Step 11: Attach to logs if not detached\n if (!options.detached) {\n try {\n await attachToLogs(ctx);\n } catch (error: any) {\n // Handle Ctrl+C gracefully\n if (error.signal === \"SIGINT\" || error.exitCode === 130) {\n console.log(chalk.yellow(\"\\n\\n⚠️ Interrupted - services stopped\"));\n process.exit(0);\n }\n throw error;\n }\n }\n}\n\n/**\n * Scaffold a new project from templates\n */\nasync function scaffoldProject(ctx: ProjectContext): Promise<void> {\n const templatesDir = getTemplatesDir();\n const spinner = ora(\"Copying templates...\").start();\n\n // Create project directory\n fs.ensureDirSync(ctx.dir);\n\n // Copy web and api directly to root\n fs.copySync(path.join(templatesDir, \"web\"), path.join(ctx.dir, \"web\"));\n fs.copySync(path.join(templatesDir, \"api\"), path.join(ctx.dir, \"api\"));\n\n // Copy root files (compose, docker, README, .gitignore)\n const rootFiles = [\n \"compose.yaml\",\n \"compose.dev.yaml\",\n \"README.md\",\n \".gitignore\",\n ];\n for (const file of rootFiles) {\n const src = path.join(templatesDir, file);\n const dest = path.join(ctx.dir, file);\n if (fs.existsSync(src)) {\n fs.copySync(src, dest);\n }\n }\n\n // Copy docker directory\n fs.copySync(path.join(templatesDir, \"docker\"), path.join(ctx.dir, \"docker\"));\n\n spinner.succeed(\"Templates copied\");\n\n // Create data/storage directory\n spinner.start(\"Creating data directories...\");\n fs.ensureDirSync(path.join(ctx.dir, \"data/storage\"));\n spinner.succeed(\"Data directories created\");\n\n console.log(chalk.green(\" ✨ Project scaffolded successfully!\"));\n}\n\n/**\n * Ensure .env files exist and are populated\n */\nasync function ensureEnvFiles(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Configuring environment...\").start();\n\n // Web .env\n const webEnvPath = path.join(ctx.dir, \"web/.env\");\n const webEnvExamplePath = path.join(ctx.dir, \"web/.env.example\");\n\n if (!fs.existsSync(webEnvPath) && fs.existsSync(webEnvExamplePath)) {\n let webEnv = fs.readFileSync(webEnvExamplePath, \"utf-8\");\n webEnv = webEnv.replace(\n \"http://localhost:8800\",\n `http://localhost:${ctx.ports.api}`\n );\n fs.writeFileSync(webEnvPath, webEnv);\n }\n\n // API .env\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n const apiEnvExamplePath = path.join(ctx.dir, \"api/.env.example\");\n\n if (!fs.existsSync(apiEnvPath) && fs.existsSync(apiEnvExamplePath)) {\n let apiEnv = fs.readFileSync(apiEnvExamplePath, \"utf-8\");\n\n // Generate JWT secret if not present\n if (\n apiEnv.includes(\"BOARDS_JWT_SECRET=\\n\") ||\n apiEnv.includes(\"BOARDS_JWT_SECRET=\\r\\n\")\n ) {\n const jwtSecret = generateSecret(32);\n apiEnv = apiEnv.replace(\n /BOARDS_JWT_SECRET=.*$/m,\n `BOARDS_JWT_SECRET=${jwtSecret}`\n );\n }\n\n fs.writeFileSync(apiEnvPath, apiEnv);\n }\n\n // Docker .env\n const dockerEnvPath = path.join(ctx.dir, \"docker/.env\");\n const dockerEnvExamplePath = path.join(ctx.dir, \"docker/env.example\");\n\n if (!fs.existsSync(dockerEnvPath) && fs.existsSync(dockerEnvExamplePath)) {\n let dockerEnv = fs.readFileSync(dockerEnvExamplePath, \"utf-8\");\n\n // Generate database password\n const dbPassword = generatePassword(24);\n // URL-encode the password for use in database URLs\n const dbPasswordEncoded = encodeURIComponent(dbPassword);\n\n dockerEnv = dockerEnv.replace(\n /POSTGRES_PASSWORD=.*/g,\n `POSTGRES_PASSWORD=${dbPassword}`\n );\n dockerEnv = dockerEnv.replace(\n /REPLACE_WITH_GENERATED_PASSWORD/g,\n dbPasswordEncoded\n );\n\n // Set ports\n dockerEnv = dockerEnv.replace(/WEB_PORT=.*/g, `WEB_PORT=${ctx.ports.web}`);\n dockerEnv = dockerEnv.replace(/API_PORT=.*/g, `API_PORT=${ctx.ports.api}`);\n\n // Set version\n dockerEnv = dockerEnv.replace(/VERSION=.*/g, `VERSION=${ctx.version}`);\n\n // Set project name\n dockerEnv = dockerEnv.replace(\n /PROJECT_NAME=.*/g,\n `PROJECT_NAME=${ctx.name}`\n );\n\n fs.writeFileSync(dockerEnvPath, dockerEnv);\n }\n\n spinner.succeed(\"Environment configured\");\n}\n\n/**\n * Prompt user for API keys during initial scaffold\n */\nasync function promptForApiKeys(ctx: ProjectContext): Promise<void> {\n console.log(chalk.cyan(\"\\n🔑 API Key Configuration\"));\n console.log(chalk.gray(\"Add API keys to enable image generation providers\"));\n console.log(chalk.gray(\"Press Enter to skip any key\\n\"));\n\n const response = await prompts([\n {\n type: \"text\",\n name: \"REPLICATE_API_TOKEN\",\n message: \"Replicate API Key (https://replicate.com/account/api-tokens):\",\n initial: \"\",\n },\n {\n type: \"text\",\n name: \"OPENAI_API_KEY\",\n message: \"OpenAI API Key (https://platform.openai.com/api-keys):\",\n initial: \"\",\n },\n ]);\n\n // Build the API keys dictionary (only include non-empty keys)\n const apiKeys: Record<string, string> = {};\n\n if (response.REPLICATE_API_TOKEN && response.REPLICATE_API_TOKEN.trim()) {\n apiKeys.REPLICATE_API_TOKEN = response.REPLICATE_API_TOKEN.trim();\n }\n\n if (response.OPENAI_API_KEY && response.OPENAI_API_KEY.trim()) {\n apiKeys.OPENAI_API_KEY = response.OPENAI_API_KEY.trim();\n }\n\n // Only write if we have at least one key\n if (Object.keys(apiKeys).length > 0) {\n // Read current api/.env\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n let apiEnv = fs.readFileSync(apiEnvPath, \"utf-8\");\n\n // Format as JSON string for the environment variable\n const jsonKeys = JSON.stringify(apiKeys);\n\n // Update or add BOARDS_GENERATOR_API_KEYS\n if (apiEnv.includes(\"BOARDS_GENERATOR_API_KEYS=\")) {\n apiEnv = apiEnv.replace(\n /BOARDS_GENERATOR_API_KEYS=.*$/m,\n `BOARDS_GENERATOR_API_KEYS=${jsonKeys}`\n );\n } else {\n // Add it after the JWT secret section\n apiEnv = apiEnv.replace(\n /(BOARDS_JWT_SECRET=.*\\n)/,\n `$1\\n# Generator API Keys (JSON format)\\nBOARDS_GENERATOR_API_KEYS=${jsonKeys}\\n`\n );\n }\n\n fs.writeFileSync(apiEnvPath, apiEnv);\n\n console.log(chalk.green(\"\\n✅ API keys saved to api/.env\"));\n console.log(\n chalk.gray(\" You can edit this file anytime to add/update keys\\n\")\n );\n } else {\n console.log(chalk.yellow(\"\\n⚠️ No API keys provided\"));\n console.log(chalk.gray(\" You can add them later by editing api/.env\\n\"));\n }\n}\n\n/**\n * Start Docker Compose (always in detached mode)\n */\nasync function startDockerCompose(\n ctx: ProjectContext,\n detached: boolean\n): Promise<void> {\n const spinner = ora(\"Starting Docker Compose...\").start();\n\n const composeFiles = [\"compose.yaml\"];\n if (ctx.mode === \"dev\") {\n composeFiles.push(\"compose.dev.yaml\");\n }\n\n const composeArgs = [\n \"compose\",\n ...composeFiles.flatMap((f) => [\"-f\", f]),\n \"up\",\n \"-d\",\n \"--remove-orphans\",\n ];\n\n try {\n await execa(\"docker\", composeArgs, {\n cwd: ctx.dir,\n stdio: \"inherit\",\n });\n spinner.succeed(\"Docker Compose started\");\n } catch (error: any) {\n spinner.fail(\"Failed to start Docker Compose\");\n throw error;\n }\n}\n\n/**\n * Attach to Docker Compose logs (foreground)\n */\nasync function attachToLogs(ctx: ProjectContext): Promise<void> {\n console.log(\n chalk.gray(\"\\n─────────────────────────────────────────────────────\")\n );\n console.log(chalk.gray(\"Streaming logs... (Press Ctrl+C to stop)\\n\"));\n\n const composeFiles = [\"compose.yaml\"];\n if (ctx.mode === \"dev\") {\n composeFiles.push(\"compose.dev.yaml\");\n }\n\n const composeArgs = [\n \"compose\",\n ...composeFiles.flatMap((f) => [\"-f\", f]),\n \"logs\",\n \"-f\",\n ];\n\n await execa(\"docker\", composeArgs, {\n cwd: ctx.dir,\n stdio: \"inherit\",\n });\n}\n\n/**\n * Wait for services to become healthy\n */\nasync function waitForHealthy(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Waiting for services to be healthy...\").start();\n\n const services = [\"db\", \"cache\", \"api\", \"worker\", \"web\"];\n const maxWaitMs = 120_000; // 2 minutes\n\n const checkHealth = async (): Promise<boolean> => {\n try {\n const { stdout } = await execa(\n \"docker\",\n [\"compose\", \"ps\", \"--format\", \"json\"],\n {\n cwd: ctx.dir,\n }\n );\n\n const containers = stdout\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => JSON.parse(line));\n\n const allHealthy = services.every((service) => {\n const container = containers.find((c: any) => c.Service === service);\n return (\n container &&\n (container.Health === \"healthy\" || container.State === \"running\")\n );\n });\n\n return allHealthy;\n } catch (e) {\n return false;\n }\n };\n\n const success = await waitFor(checkHealth, {\n timeoutMs: maxWaitMs,\n intervalMs: 2000,\n onProgress: (elapsed) => {\n const seconds = Math.floor(elapsed / 1000);\n spinner.text = `Waiting for services to be healthy... (${seconds}s)`;\n },\n });\n\n if (success) {\n spinner.succeed(\"All services healthy\");\n } else {\n spinner.warn(\"Services taking longer than expected...\");\n console.log(\n chalk.yellow(\n \"\\n⚠️ Health check timeout. Services may still be starting.\"\n )\n );\n console.log(\n chalk.gray(\" Run\"),\n chalk.cyan(\"baseboards logs\"),\n chalk.gray(\"to check progress.\")\n );\n }\n}\n\n/**\n * Run database migrations\n */\nasync function runMigrations(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Running database migrations...\").start();\n\n try {\n await execa(\n \"docker\",\n [\"compose\", \"exec\", \"-T\", \"api\", \"alembic\", \"upgrade\", \"head\"],\n {\n cwd: ctx.dir,\n }\n );\n spinner.succeed(\"Database migrations complete\");\n } catch (error) {\n spinner.fail(\"Database migrations failed\");\n console.log(\n chalk.yellow(\n \"\\n⚠️ Database migrations failed. You may need to run them manually:\"\n )\n );\n console.log(error);\n console.log(chalk.cyan(\" docker compose exec api alembic upgrade head\"));\n // Don't throw - app can still start\n }\n}\n\n/**\n * Print success message with URLs and next steps\n */\nfunction printSuccessMessage(\n ctx: ProjectContext,\n detached: boolean,\n hasKeyWarning: boolean\n): void {\n console.log(chalk.green.bold(\"\\n✨ Baseboards is running!\\n\"));\n console.log(\n chalk.cyan(\" 🌐 Web:\"),\n chalk.underline(`http://localhost:${ctx.ports.web}`)\n );\n console.log(\n chalk.cyan(\" 🔌 API:\"),\n chalk.underline(`http://localhost:${ctx.ports.api}`)\n );\n console.log(\n chalk.cyan(\" 📊 GraphQL:\"),\n chalk.underline(`http://localhost:${ctx.ports.api}/graphql`)\n );\n\n if (hasKeyWarning) {\n console.log(chalk.yellow(\"\\n⚠️ Remember to configure provider API keys!\"));\n console.log(chalk.gray(\" Edit:\"), chalk.cyan(\"api/.env\"));\n console.log(\n chalk.gray(\" Docs:\"),\n chalk.cyan(\"https://baseboards.dev/docs/setup\")\n );\n }\n\n console.log(chalk.gray(\"\\n📖 Commands:\"));\n console.log(chalk.gray(\" Stop:\"), chalk.cyan(\"baseboards down\"));\n console.log(chalk.gray(\" Logs:\"), chalk.cyan(\"baseboards logs\"));\n console.log(chalk.gray(\" Status:\"), chalk.cyan(\"baseboards status\"));\n console.log();\n}\n","/**\n * Utility functions for the Baseboards CLI\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport which from \"which\";\nimport type { Prerequisites, ProjectContext } from \"./types.js\";\nimport chalk from \"chalk\";\nimport crypto from \"crypto\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Get the templates directory (bundled with the CLI package)\n */\nexport function getTemplatesDir(): string {\n // In built package, templates are at root level next to dist/\n return path.join(__dirname, \"../templates\");\n}\n\n/**\n * Check system prerequisites\n */\nexport async function checkPrerequisites(): Promise<Prerequisites> {\n const prereqs: Prerequisites = {\n docker: { installed: false },\n node: { installed: true, satisfies: false },\n platform: { name: process.platform },\n };\n\n // Check Docker\n try {\n const dockerPath = await which(\"docker\");\n prereqs.docker.installed = !!dockerPath;\n\n if (dockerPath) {\n const { stdout } = await execa(\"docker\", [\"--version\"]);\n const match = stdout.match(/Docker version ([\\d.]+)/);\n if (match) {\n prereqs.docker.version = match[1];\n }\n\n // Check Docker Compose\n try {\n const { stdout: composeStdout } = await execa(\"docker\", [\n \"compose\",\n \"version\",\n ]);\n const composeMatch = composeStdout.match(/version v?([\\d.]+)/);\n if (composeMatch) {\n prereqs.docker.composeVersion = composeMatch[1];\n }\n } catch (e) {\n // Compose not available\n }\n }\n } catch (e) {\n // Docker not installed\n }\n\n // Check Node version\n const nodeVersion = process.version.replace(\"v\", \"\");\n prereqs.node.version = nodeVersion;\n\n const majorVersion = parseInt(nodeVersion.split(\".\")[0], 10);\n prereqs.node.satisfies = majorVersion >= 20;\n\n // Check if WSL\n if (process.platform === \"linux\") {\n try {\n const { stdout } = await execa(\"uname\", [\"-r\"]);\n if (\n stdout.toLowerCase().includes(\"microsoft\") ||\n stdout.toLowerCase().includes(\"wsl\")\n ) {\n prereqs.platform.isWSL = true;\n }\n } catch (e) {\n // Not WSL or can't determine\n }\n }\n\n return prereqs;\n}\n\n/**\n * Assert that prerequisites are met, or exit with helpful error\n */\nexport async function assertPrerequisites(): Promise<void> {\n const prereqs = await checkPrerequisites();\n\n const errors: string[] = [];\n\n if (!prereqs.docker.installed) {\n errors.push(\n \"🐳 Docker is not installed.\",\n \" Install from: https://docs.docker.com/get-docker/\"\n );\n } else if (!prereqs.docker.composeVersion) {\n errors.push(\n \"🐳 Docker Compose (v2) is not available.\",\n \" Update Docker to get Compose v2: https://docs.docker.com/compose/install/\"\n );\n }\n\n if (!prereqs.node.satisfies) {\n errors.push(\n `⚠️ Node.js ${prereqs.node.version} is too old (need v20+).`,\n \" Install from: https://nodejs.org/\"\n );\n }\n\n if (errors.length > 0) {\n console.error(chalk.red(\"\\n❌ Prerequisites not met:\\n\"));\n errors.forEach((err) => console.error(chalk.yellow(err)));\n console.error(\"\");\n process.exit(1);\n }\n}\n\n/**\n * Check if a directory is already scaffolded\n */\nexport function isScaffolded(dir: string): boolean {\n // Check for key files that indicate scaffolding\n const keyFiles = [\"compose.yaml\", \"web/package.json\", \"api/pyproject.toml\"];\n\n return keyFiles.every((file) => fs.existsSync(path.join(dir, file)));\n}\n\n/**\n * Find an available port\n */\nexport async function findAvailablePort(\n preferred: number,\n maxAttempts = 50\n): Promise<number> {\n for (let port = preferred; port < preferred + maxAttempts; port++) {\n if (await isPortAvailable(port)) {\n return port;\n }\n }\n throw new Error(`No available port found near ${preferred}`);\n}\n\n/**\n * Check if a port is available\n */\nasync function isPortAvailable(port: number): Promise<boolean> {\n const { createServer } = await import(\"net\");\n return new Promise((resolve) => {\n const server = createServer();\n server.once(\"error\", () => resolve(false));\n server.once(\"listening\", () => {\n server.close();\n resolve(true);\n });\n server.listen(port);\n });\n}\n\n/**\n * Generate a random secure string\n */\nexport function generateSecret(length = 32): string {\n return crypto.randomBytes(length).toString(\"hex\");\n}\n\n/**\n * Generate a random password\n */\nexport function generatePassword(length = 24): string {\n const charset =\n \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*\";\n let password = \"\";\n const bytes = crypto.randomBytes(length);\n\n for (let i = 0; i < length; i++) {\n password += charset[bytes[i] % charset.length];\n }\n\n return password;\n}\n\n/**\n * Parse custom ports string (e.g., \"web=3300 api=8800\")\n */\nexport function parsePortsOption(portsStr: string): Partial<{\n web: number;\n api: number;\n db: number;\n redis: number;\n}> {\n const ports: any = {};\n const pairs = portsStr.split(/\\s+/);\n\n for (const pair of pairs) {\n const [service, port] = pair.split(\"=\");\n const portNum = parseInt(port, 10);\n if (\n service &&\n [\"web\", \"api\", \"db\", \"redis\"].includes(service) &&\n !isNaN(portNum)\n ) {\n ports[service] = portNum;\n }\n }\n\n return ports;\n}\n\n/**\n * Read package.json version\n */\nexport function getCliVersion(): string {\n const packagePath = path.join(__dirname, \"../package.json\");\n const packageJson = JSON.parse(fs.readFileSync(packagePath, \"utf-8\"));\n return packageJson.version;\n}\n\n/**\n * Detect missing provider API keys in .env file\n */\nexport function detectMissingProviderKeys(envPath: string): string[] {\n if (!fs.existsSync(envPath)) {\n return [];\n }\n\n const envContent = fs.readFileSync(envPath, \"utf-8\");\n const providerKeys = [\n \"REPLICATE_API_TOKEN\",\n \"FAL_KEY\",\n \"OPENAI_API_KEY\",\n \"GOOGLE_API_KEY\",\n ];\n\n const missingKeys: string[] = [];\n\n for (const key of providerKeys) {\n const regex = new RegExp(`^${key}=(.*)$`, \"m\");\n const match = envContent.match(regex);\n\n // Key is missing if not found or if value is empty\n if (!match || !match[1] || match[1].trim() === \"\") {\n missingKeys.push(key);\n }\n }\n\n // If all keys are missing, user hasn't configured any\n if (missingKeys.length === providerKeys.length) {\n return missingKeys;\n }\n\n return [];\n}\n\n/**\n * Wait for a condition with timeout\n */\nexport async function waitFor(\n condition: () => Promise<boolean>,\n options: {\n timeoutMs: number;\n intervalMs?: number;\n onProgress?: (elapsed: number) => void;\n }\n): Promise<boolean> {\n const { timeoutMs, intervalMs = 1000, onProgress } = options;\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeoutMs) {\n if (await condition()) {\n return true;\n }\n\n if (onProgress) {\n onProgress(Date.now() - startTime);\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n\n return false;\n}\n","/**\n * down command - Stop Baseboards\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport type { DownOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function down(directory: string, options: DownOptions): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\n❌ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n const spinner = ora('Stopping services...').start();\n\n const args = ['compose', 'down'];\n if (options.volumes) {\n args.push('--volumes');\n }\n\n try {\n await execa('docker', args, {\n cwd: dir,\n });\n\n spinner.succeed('Services stopped');\n\n if (options.volumes) {\n console.log(chalk.yellow('⚠️ Volumes removed (database data deleted)'));\n }\n } catch (error: any) {\n spinner.fail('Failed to stop services');\n throw error;\n }\n}\n","/**\n * logs command - View service logs\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport type { LogsOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function logs(\n directory: string,\n services: string[],\n options: LogsOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\n❌ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n const args = ['compose', 'logs'];\n\n if (options.follow) {\n args.push('--follow');\n }\n\n if (options.since) {\n args.push('--since', options.since);\n }\n\n if (options.tail) {\n args.push('--tail', options.tail);\n }\n\n // Add specific services if provided\n if (services.length > 0) {\n args.push(...services);\n }\n\n try {\n await execa('docker', args, {\n cwd: dir,\n stdio: 'inherit',\n });\n } catch (error: any) {\n // Ctrl+C is expected, don't treat as error\n if (error.signal !== 'SIGINT') {\n throw error;\n }\n }\n}\n","/**\n * status command - Show service status\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport { isScaffolded } from '../utils.js';\n\nexport async function status(directory: string): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\n❌ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n console.log(chalk.blue.bold('\\n📊 Service Status\\n'));\n\n try {\n await execa('docker', ['compose', 'ps'], {\n cwd: dir,\n stdio: 'inherit',\n });\n } catch (error: any) {\n console.error(chalk.red('\\n❌ Failed to get status'));\n throw error;\n }\n}\n","/**\n * clean command - Clean up Docker resources\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport prompts from 'prompts';\nimport type { CleanOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function clean(\n directory: string,\n options: CleanOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\n❌ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n if (options.hard) {\n console.log(chalk.yellow('\\n⚠️ WARNING: This will delete:'));\n console.log(chalk.yellow(' • All containers'));\n console.log(chalk.yellow(' • All volumes (database data will be lost)'));\n console.log(chalk.yellow(' • All images'));\n\n const response = await prompts({\n type: 'confirm',\n name: 'confirmed',\n message: 'Are you sure?',\n initial: false,\n });\n\n if (!response.confirmed) {\n console.log(chalk.gray('\\nCancelled'));\n return;\n }\n }\n\n const spinner = ora('Cleaning up...').start();\n\n try {\n // Stop containers\n await execa('docker', ['compose', 'down', '--volumes', '--remove-orphans'], {\n cwd: dir,\n });\n\n if (options.hard) {\n // Remove images\n try {\n const { stdout } = await execa('docker', ['compose', 'images', '-q'], {\n cwd: dir,\n });\n\n const imageIds = stdout.split('\\n').filter(Boolean);\n if (imageIds.length > 0) {\n await execa('docker', ['rmi', ...imageIds]);\n }\n } catch (e) {\n // Images might not exist or already removed\n }\n }\n\n spinner.succeed('Cleanup complete');\n\n if (options.hard) {\n console.log(chalk.green('\\n✨ All Docker resources removed'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to start fresh.'));\n } else {\n console.log(chalk.green('\\n✨ Containers and volumes removed'));\n }\n } catch (error: any) {\n spinner.fail('Cleanup failed');\n throw error;\n }\n}\n","/**\n * update command - Update Baseboards to latest version\n */\n\nimport path from 'path';\nimport chalk from 'chalk';\nimport type { UpdateOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function update(\n directory: string,\n options: UpdateOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\n❌ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n console.log(chalk.blue.bold('\\n🔄 Update Command\\n'));\n console.log(chalk.yellow('⚠️ This feature is coming soon!'));\n console.log(chalk.gray('\\nFor now, to update:'));\n console.log(chalk.gray('1. Update the CLI:'), chalk.cyan('npm install -g @weirdfingers/baseboards@latest'));\n console.log(chalk.gray('2. Pull new images:'), chalk.cyan('docker compose pull'));\n console.log(chalk.gray('3. Restart:'), chalk.cyan('baseboards down && baseboards up'));\n console.log();\n\n // TODO: Implement update logic:\n // 1. Check for new version on npm\n // 2. Scan for modified source files (git diff or timestamp check)\n // 3. If no modifications:\n // - Copy new templates (preserving config)\n // - Pull new Docker images\n // - Update package.json versions\n // 4. If modifications detected:\n // - Warn user\n // - Offer git-based merge if repo detected\n // - Create backup otherwise\n // 5. Preserve:\n // - All .env files\n // - config/*.yaml files\n // - data/storage/ directory\n}\n","/**\n * doctor command - Run diagnostics\n */\n\nimport path from 'path';\nimport fs from 'fs-extra';\nimport chalk from 'chalk';\nimport { checkPrerequisites, isScaffolded, detectMissingProviderKeys, getCliVersion } from '../utils.js';\n\nexport async function doctor(directory: string): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n console.log(chalk.blue.bold('\\n🩺 Baseboards Diagnostics\\n'));\n\n // CLI Version\n console.log(chalk.cyan('CLI Version:'), getCliVersion());\n\n // Prerequisites\n console.log(chalk.cyan('\\n📋 Prerequisites:'));\n const prereqs = await checkPrerequisites();\n\n console.log(\n chalk.gray(' Node.js:'),\n prereqs.node.installed\n ? prereqs.node.satisfies\n ? chalk.green(`✓ v${prereqs.node.version}`)\n : chalk.yellow(`⚠️ v${prereqs.node.version} (need v20+)`)\n : chalk.red('✗ Not installed')\n );\n\n console.log(\n chalk.gray(' Docker:'),\n prereqs.docker.installed\n ? chalk.green(`✓ v${prereqs.docker.version}`)\n : chalk.red('✗ Not installed')\n );\n\n if (prereqs.docker.composeVersion) {\n console.log(\n chalk.gray(' Docker Compose:'),\n chalk.green(`✓ v${prereqs.docker.composeVersion}`)\n );\n } else if (prereqs.docker.installed) {\n console.log(chalk.gray(' Docker Compose:'), chalk.red('✗ Not available'));\n }\n\n console.log(chalk.gray(' Platform:'), prereqs.platform.name);\n if (prereqs.platform.isWSL) {\n console.log(chalk.gray(' WSL:'), chalk.blue('✓ Detected'));\n }\n\n // Project info\n console.log(chalk.cyan('\\n📂 Project:'));\n const scaffolded = isScaffolded(dir);\n console.log(\n chalk.gray(' Scaffolded:'),\n scaffolded ? chalk.green('✓ Yes') : chalk.yellow('✗ No')\n );\n\n if (scaffolded) {\n console.log(chalk.gray(' Directory:'), dir);\n\n // Check for key files\n const webPkg = path.join(dir, 'web/package.json');\n const apiPkg = path.join(dir, 'api/pyproject.toml');\n const composeFile = path.join(dir, 'compose.yaml');\n\n console.log(\n chalk.gray(' Web package:'),\n fs.existsSync(webPkg) ? chalk.green('✓') : chalk.red('✗')\n );\n console.log(\n chalk.gray(' API package:'),\n fs.existsSync(apiPkg) ? chalk.green('✓') : chalk.red('✗')\n );\n console.log(\n chalk.gray(' Compose file:'),\n fs.existsSync(composeFile) ? chalk.green('✓') : chalk.red('✗')\n );\n\n // Check .env files\n console.log(chalk.cyan('\\n🔐 Environment:'));\n const webEnv = path.join(dir, 'web/.env');\n const apiEnv = path.join(dir, 'api/.env');\n const dockerEnv = path.join(dir, 'docker/.env');\n\n console.log(\n chalk.gray(' Web .env:'),\n fs.existsSync(webEnv) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n console.log(\n chalk.gray(' API .env:'),\n fs.existsSync(apiEnv) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n console.log(\n chalk.gray(' Docker .env:'),\n fs.existsSync(dockerEnv) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n\n // Check provider keys\n if (fs.existsSync(apiEnv)) {\n const missingKeys = detectMissingProviderKeys(apiEnv);\n if (missingKeys.length > 0) {\n console.log(\n chalk.gray(' Provider keys:'),\n chalk.yellow(`⚠️ ${missingKeys.length} missing`)\n );\n console.log(chalk.gray(' Missing:'), missingKeys.map(k => chalk.cyan(k)).join(', '));\n } else {\n console.log(chalk.gray(' Provider keys:'), chalk.green('✓ Configured'));\n }\n }\n\n // Check config files\n console.log(chalk.cyan('\\n⚙️ Configuration:'));\n const generatorsYaml = path.join(dir, 'api/config/generators.yaml');\n const storageYaml = path.join(dir, 'api/config/storage_config.yaml');\n\n console.log(\n chalk.gray(' generators.yaml:'),\n fs.existsSync(generatorsYaml) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n console.log(\n chalk.gray(' storage_config.yaml:'),\n fs.existsSync(storageYaml) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n\n // Check storage directory\n const storageDir = path.join(dir, 'data/storage');\n console.log(\n chalk.gray(' Storage directory:'),\n fs.existsSync(storageDir) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n }\n\n // Recommendations\n console.log(chalk.cyan('\\n💡 Recommendations:'));\n const recommendations: string[] = [];\n\n if (!prereqs.node.satisfies) {\n recommendations.push('Upgrade Node.js to v20 or higher');\n }\n\n if (!prereqs.docker.installed) {\n recommendations.push('Install Docker Desktop: https://docs.docker.com/get-docker/');\n } else if (!prereqs.docker.composeVersion) {\n recommendations.push('Update Docker to get Compose v2');\n }\n\n if (!scaffolded) {\n recommendations.push('Run ' + chalk.cyan('baseboards up') + ' to scaffold a project');\n }\n\n if (recommendations.length === 0) {\n console.log(chalk.green(' ✓ Everything looks good!'));\n } else {\n recommendations.forEach((rec) => console.log(chalk.yellow(' •'), rec));\n }\n\n console.log();\n}\n"],"mappings":"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","names":["fileURLToPath","chalk","execa","fs","path","chalk","packageJson","chalk","path","fs","execa","execa","path","chalk","ora","path","chalk","ora","execa","execa","path","chalk","path","chalk","execa","execa","path","chalk","path","chalk","execa","execa","path","chalk","ora","prompts","path","chalk","prompts","ora","execa","path","chalk","path","chalk","path","fs","chalk","path","chalk","fs","__filename","fileURLToPath","__dirname","chalk"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/up.ts","../src/utils.ts","../src/commands/down.ts","../src/commands/logs.ts","../src/commands/status.ts","../src/commands/clean.ts","../src/commands/update.ts","../src/commands/doctor.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Baseboards CLI\n *\n * Main entry point for the @weirdfingers/baseboards command-line interface.\n * Provides commands to scaffold, run, and manage Baseboards installations.\n */\n\nimport { Command } from \"commander\";\nimport { readFileSync } from \"fs\";\nimport { fileURLToPath } from \"url\";\nimport { dirname, join } from \"path\";\nimport chalk from \"chalk\";\n\n// Import commands\nimport { up } from \"./commands/up.js\";\nimport { down } from \"./commands/down.js\";\nimport { logs } from \"./commands/logs.js\";\nimport { status } from \"./commands/status.js\";\nimport { clean } from \"./commands/clean.js\";\nimport { update } from \"./commands/update.js\";\nimport { doctor } from \"./commands/doctor.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Read package.json for version\nconst packageJson = JSON.parse(\n readFileSync(join(__dirname, \"../package.json\"), \"utf-8\")\n);\n\nconst program = new Command();\n\nprogram\n .name(\"baseboards\")\n .description(\n \"🎨 One-command launcher for the Boards image generation platform\"\n )\n .version(packageJson.version, \"-v, --version\", \"Output the current version\");\n\n// up command\nprogram\n .command(\"up\")\n .description(\"Start Baseboards (scaffolds if needed)\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--dev\", \"Development mode with hot reload (default)\", true)\n .option(\"--prod\", \"Production mode with prebuilt images\")\n .option(\"--attach\", \"Attach to logs (runs in foreground)\")\n .option(\"--ports <ports>\", \"Custom ports (e.g., web=3300 api=8800)\")\n .action(up);\n\n// down command\nprogram\n .command(\"down\")\n .description(\"Stop Baseboards\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--volumes\", \"Also remove volumes\")\n .action(down);\n\n// logs command\nprogram\n .command(\"logs\")\n .description(\"View logs from services\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .argument(\n \"[services...]\",\n \"Services to show logs for (web, api, db, cache)\",\n []\n )\n .option(\"-f, --follow\", \"Follow log output\")\n .option(\"--since <time>\", \"Show logs since timestamp (e.g., 1h, 30m)\")\n .option(\"--tail <lines>\", \"Number of lines to show from end\", \"100\")\n .action(logs);\n\n// status command\nprogram\n .command(\"status\")\n .description(\"Show status of services\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .action(status);\n\n// clean command\nprogram\n .command(\"clean\")\n .description(\"Clean up Docker resources\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--hard\", \"Remove volumes and images (WARNING: deletes data)\")\n .action(clean);\n\n// update command\nprogram\n .command(\"update\")\n .description(\"Update Baseboards to latest version\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--force\", \"Force update without safety checks\")\n .option(\"--version <version>\", \"Update to specific version\")\n .action(update);\n\n// doctor command\nprogram\n .command(\"doctor\")\n .description(\"Run diagnostics and show system info\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .action(doctor);\n\n// Parse without exitOverride - let commander handle exits naturally\ntry {\n await program.parseAsync(process.argv);\n} catch (error: unknown) {\n const err = error as { message?: string; stderr?: string };\n // Actual error (not help/version)\n console.error(chalk.red(\"\\n❌ Error:\"), err.message || \"Unknown error\");\n\n if (err.stderr) {\n console.error(chalk.gray(\"\\nDetails:\"));\n console.error(chalk.gray(err.stderr));\n }\n\n console.error(\n chalk.yellow(\"\\n💡 Try running:\"),\n chalk.cyan(\"baseboards doctor\")\n );\n console.error(\n chalk.yellow(\"📖 Documentation:\"),\n chalk.cyan(\"https://baseboards.dev/docs\")\n );\n\n process.exit(1);\n}\n","/**\n * up command - Scaffold and start Baseboards\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport type { ProjectContext, UpOptions } from \"../types.js\";\nimport {\n assertPrerequisites,\n findAvailablePort,\n generatePassword,\n generateSecret,\n getCliVersion,\n getTemplatesDir,\n isScaffolded,\n parsePortsOption,\n detectMissingProviderKeys,\n waitFor,\n} from \"../utils.js\";\n\nexport async function up(directory: string, options: UpOptions): Promise<void> {\n console.log(chalk.blue.bold(\"\\n🎨 Baseboards CLI\\n\"));\n\n // Step 1: Check prerequisites\n const spinner = ora(\"Checking prerequisites...\").start();\n await assertPrerequisites();\n spinner.succeed(\"Prerequisites OK\");\n\n // Step 2: Resolve project context\n const dir = path.resolve(process.cwd(), directory);\n const name = path.basename(dir);\n const version = getCliVersion();\n const mode = options.prod ? \"prod\" : \"dev\";\n\n // Parse custom ports\n let customPorts = {};\n if (options.ports) {\n customPorts = parsePortsOption(options.ports);\n }\n\n // Default ports\n const defaultPorts = {\n web: 3300,\n api: 8800,\n db: 5432,\n redis: 6379,\n ...customPorts,\n };\n\n const ctx: ProjectContext = {\n dir,\n name,\n isScaffolded: isScaffolded(dir),\n ports: defaultPorts,\n mode,\n version,\n };\n\n // Track if this is a fresh scaffold to prompt for API keys later\n const isFreshScaffold = !ctx.isScaffolded;\n\n // Step 3: Scaffold if needed\n if (!ctx.isScaffolded) {\n console.log(\n chalk.cyan(`\\n📦 Scaffolding new project: ${chalk.bold(name)}`)\n );\n await scaffoldProject(ctx);\n } else {\n console.log(\n chalk.green(`\\n✅ Project already scaffolded: ${chalk.bold(name)}`)\n );\n }\n\n // Step 4: Check ports availability\n spinner.start(\"Checking port availability...\");\n ctx.ports.web = await findAvailablePort(ctx.ports.web);\n ctx.ports.api = await findAvailablePort(ctx.ports.api);\n spinner.succeed(\n `Ports available: web=${ctx.ports.web}, api=${ctx.ports.api}`\n );\n\n // Step 5: Ensure environment files\n await ensureEnvFiles(ctx);\n\n // Step 5.5: Prompt for API keys (only on fresh scaffold)\n if (isFreshScaffold) {\n await promptForApiKeys(ctx);\n }\n\n // Step 6: Detect missing API keys\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n const missingKeys = detectMissingProviderKeys(apiEnvPath);\n\n if (missingKeys.length > 0) {\n console.log(chalk.yellow(\"\\n⚠️ Provider API keys not configured!\"));\n console.log(chalk.gray(\" Add at least one API key to api/.env:\"));\n console.log(\n chalk.cyan(\" • REPLICATE_API_TOKEN\") +\n chalk.gray(\" - https://replicate.com/account/api-tokens\")\n );\n console.log(\n chalk.cyan(\" • FAL_KEY\") +\n chalk.gray(\" - https://fal.ai/dashboard/keys\")\n );\n console.log(\n chalk.cyan(\" • OPENAI_API_KEY\") +\n chalk.gray(\" - https://platform.openai.com/api-keys\")\n );\n console.log(\n chalk.cyan(\" • GOOGLE_API_KEY\") +\n chalk.gray(\" - https://makersuite.google.com/app/apikey\")\n );\n console.log(\n chalk.gray(\n \"\\n The app will start, but image generation won't work without keys.\\n\"\n )\n );\n }\n\n // Step 7: Start Docker Compose (always detached initially)\n await startDockerCompose(ctx, true);\n\n // Step 8: Wait for health checks\n await waitForHealthy(ctx);\n\n // Step 9: Run database migrations\n await runMigrations(ctx);\n\n // Step 10: Print success message\n printSuccessMessage(ctx, !options.attach, missingKeys.length > 0);\n\n // Step 11: Attach to logs if --attach flag is provided\n if (options.attach) {\n try {\n await attachToLogs(ctx);\n } catch (error: any) {\n // Handle Ctrl+C gracefully\n if (error.signal === \"SIGINT\" || error.exitCode === 130) {\n console.log(chalk.yellow(\"\\n\\n⚠️ Interrupted - services stopped\"));\n process.exit(0);\n }\n throw error;\n }\n }\n}\n\n/**\n * Scaffold a new project from templates\n */\nasync function scaffoldProject(ctx: ProjectContext): Promise<void> {\n const templatesDir = getTemplatesDir();\n const spinner = ora(\"Copying templates...\").start();\n\n // Create project directory\n fs.ensureDirSync(ctx.dir);\n\n // Copy web and api directly to root\n fs.copySync(path.join(templatesDir, \"web\"), path.join(ctx.dir, \"web\"));\n fs.copySync(path.join(templatesDir, \"api\"), path.join(ctx.dir, \"api\"));\n\n // Copy root files (compose, docker, README, .gitignore)\n const rootFiles = [\n \"compose.yaml\",\n \"compose.dev.yaml\",\n \"README.md\",\n \".gitignore\",\n ];\n for (const file of rootFiles) {\n const src = path.join(templatesDir, file);\n const dest = path.join(ctx.dir, file);\n if (fs.existsSync(src)) {\n fs.copySync(src, dest);\n }\n }\n\n // Copy docker directory\n fs.copySync(path.join(templatesDir, \"docker\"), path.join(ctx.dir, \"docker\"));\n\n spinner.succeed(\"Templates copied\");\n\n // Create data/storage directory\n spinner.start(\"Creating data directories...\");\n fs.ensureDirSync(path.join(ctx.dir, \"data/storage\"));\n spinner.succeed(\"Data directories created\");\n\n console.log(chalk.green(\" ✨ Project scaffolded successfully!\"));\n}\n\n/**\n * Ensure .env files exist and are populated\n */\nasync function ensureEnvFiles(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Configuring environment...\").start();\n\n // Web .env\n const webEnvPath = path.join(ctx.dir, \"web/.env\");\n const webEnvExamplePath = path.join(ctx.dir, \"web/.env.example\");\n\n if (!fs.existsSync(webEnvPath) && fs.existsSync(webEnvExamplePath)) {\n let webEnv = fs.readFileSync(webEnvExamplePath, \"utf-8\");\n webEnv = webEnv.replace(\n \"http://localhost:8800\",\n `http://localhost:${ctx.ports.api}`\n );\n fs.writeFileSync(webEnvPath, webEnv);\n }\n\n // API .env\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n const apiEnvExamplePath = path.join(ctx.dir, \"api/.env.example\");\n\n if (!fs.existsSync(apiEnvPath) && fs.existsSync(apiEnvExamplePath)) {\n let apiEnv = fs.readFileSync(apiEnvExamplePath, \"utf-8\");\n\n // Generate JWT secret if not present\n if (\n apiEnv.includes(\"BOARDS_JWT_SECRET=\\n\") ||\n apiEnv.includes(\"BOARDS_JWT_SECRET=\\r\\n\")\n ) {\n const jwtSecret = generateSecret(32);\n apiEnv = apiEnv.replace(\n /BOARDS_JWT_SECRET=.*$/m,\n `BOARDS_JWT_SECRET=${jwtSecret}`\n );\n }\n\n fs.writeFileSync(apiEnvPath, apiEnv);\n }\n\n // Docker .env\n const dockerEnvPath = path.join(ctx.dir, \"docker/.env\");\n const dockerEnvExamplePath = path.join(ctx.dir, \"docker/env.example\");\n\n if (!fs.existsSync(dockerEnvPath) && fs.existsSync(dockerEnvExamplePath)) {\n let dockerEnv = fs.readFileSync(dockerEnvExamplePath, \"utf-8\");\n\n // Generate database password\n const dbPassword = generatePassword(24);\n // URL-encode the password for use in database URLs\n const dbPasswordEncoded = encodeURIComponent(dbPassword);\n\n dockerEnv = dockerEnv.replace(\n /POSTGRES_PASSWORD=.*/g,\n `POSTGRES_PASSWORD=${dbPassword}`\n );\n dockerEnv = dockerEnv.replace(\n /REPLACE_WITH_GENERATED_PASSWORD/g,\n dbPasswordEncoded\n );\n\n // Set ports\n dockerEnv = dockerEnv.replace(/WEB_PORT=.*/g, `WEB_PORT=${ctx.ports.web}`);\n dockerEnv = dockerEnv.replace(/API_PORT=.*/g, `API_PORT=${ctx.ports.api}`);\n\n // Set version\n dockerEnv = dockerEnv.replace(/VERSION=.*/g, `VERSION=${ctx.version}`);\n\n // Set project name\n dockerEnv = dockerEnv.replace(\n /PROJECT_NAME=.*/g,\n `PROJECT_NAME=${ctx.name}`\n );\n\n fs.writeFileSync(dockerEnvPath, dockerEnv);\n }\n\n spinner.succeed(\"Environment configured\");\n}\n\n/**\n * Prompt user for API keys during initial scaffold\n */\nasync function promptForApiKeys(ctx: ProjectContext): Promise<void> {\n console.log(chalk.cyan(\"\\n🔑 API Key Configuration\"));\n console.log(chalk.gray(\"Add API keys to enable image generation providers\"));\n console.log(chalk.gray(\"Press Enter to skip any key\\n\"));\n\n const response = await prompts([\n {\n type: \"text\",\n name: \"REPLICATE_API_TOKEN\",\n message: \"Replicate API Key (https://replicate.com/account/api-tokens):\",\n initial: \"\",\n },\n {\n type: \"text\",\n name: \"FAL_KEY\",\n message: \"Fal AI API Key (https://fal.ai/dashboard/keys):\",\n initial: \"\",\n },\n {\n type: \"text\",\n name: \"OPENAI_API_KEY\",\n message: \"OpenAI API Key (https://platform.openai.com/api-keys):\",\n initial: \"\",\n },\n ]);\n\n // Build the API keys dictionary (only include non-empty keys)\n const apiKeys: Record<string, string> = {};\n\n if (response.REPLICATE_API_TOKEN && response.REPLICATE_API_TOKEN.trim()) {\n apiKeys.REPLICATE_API_TOKEN = response.REPLICATE_API_TOKEN.trim();\n }\n\n if (response.FAL_KEY && response.FAL_KEY.trim()) {\n apiKeys.FAL_KEY = response.FAL_KEY.trim();\n }\n\n if (response.OPENAI_API_KEY && response.OPENAI_API_KEY.trim()) {\n apiKeys.OPENAI_API_KEY = response.OPENAI_API_KEY.trim();\n }\n\n // Only write if we have at least one key\n if (Object.keys(apiKeys).length > 0) {\n // Read current api/.env\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n let apiEnv = fs.readFileSync(apiEnvPath, \"utf-8\");\n\n // Format as JSON string for the environment variable\n const jsonKeys = JSON.stringify(apiKeys);\n\n // Update or add BOARDS_GENERATOR_API_KEYS\n if (apiEnv.includes(\"BOARDS_GENERATOR_API_KEYS=\")) {\n apiEnv = apiEnv.replace(\n /BOARDS_GENERATOR_API_KEYS=.*$/m,\n `BOARDS_GENERATOR_API_KEYS=${jsonKeys}`\n );\n } else {\n // Add it after the JWT secret section\n apiEnv = apiEnv.replace(\n /(BOARDS_JWT_SECRET=.*\\n)/,\n `$1\\n# Generator API Keys (JSON format)\\nBOARDS_GENERATOR_API_KEYS=${jsonKeys}\\n`\n );\n }\n\n fs.writeFileSync(apiEnvPath, apiEnv);\n\n console.log(chalk.green(\"\\n✅ API keys saved to api/.env\"));\n console.log(\n chalk.gray(\" You can edit this file anytime to add/update keys\\n\")\n );\n } else {\n console.log(chalk.yellow(\"\\n⚠️ No API keys provided\"));\n console.log(chalk.gray(\" You can add them later by editing api/.env\\n\"));\n }\n}\n\n/**\n * Start Docker Compose (always in detached mode)\n */\nasync function startDockerCompose(\n ctx: ProjectContext,\n detached: boolean\n): Promise<void> {\n const spinner = ora(\"Starting Docker Compose...\").start();\n\n const composeFiles = [\"compose.yaml\"];\n if (ctx.mode === \"dev\") {\n composeFiles.push(\"compose.dev.yaml\");\n }\n\n const composeArgs = [\n \"compose\",\n ...composeFiles.flatMap((f) => [\"-f\", f]),\n \"up\",\n \"-d\",\n \"--remove-orphans\",\n ];\n\n try {\n await execa(\"docker\", composeArgs, {\n cwd: ctx.dir,\n stdio: \"inherit\",\n });\n spinner.succeed(\"Docker Compose started\");\n } catch (error: any) {\n spinner.fail(\"Failed to start Docker Compose\");\n throw error;\n }\n}\n\n/**\n * Attach to Docker Compose logs (foreground)\n */\nasync function attachToLogs(ctx: ProjectContext): Promise<void> {\n console.log(\n chalk.gray(\"\\n─────────────────────────────────────────────────────\")\n );\n console.log(chalk.gray(\"Streaming logs... (Press Ctrl+C to stop)\\n\"));\n\n const composeFiles = [\"compose.yaml\"];\n if (ctx.mode === \"dev\") {\n composeFiles.push(\"compose.dev.yaml\");\n }\n\n const composeArgs = [\n \"compose\",\n ...composeFiles.flatMap((f) => [\"-f\", f]),\n \"logs\",\n \"-f\",\n ];\n\n await execa(\"docker\", composeArgs, {\n cwd: ctx.dir,\n stdio: \"inherit\",\n });\n}\n\n/**\n * Wait for services to become healthy\n */\nasync function waitForHealthy(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Waiting for services to be healthy...\").start();\n\n const services = [\"db\", \"cache\", \"api\", \"worker\", \"web\"];\n const maxWaitMs = 120_000; // 2 minutes\n\n const checkHealth = async (): Promise<boolean> => {\n try {\n const { stdout } = await execa(\n \"docker\",\n [\"compose\", \"ps\", \"--format\", \"json\"],\n {\n cwd: ctx.dir,\n }\n );\n\n const containers = stdout\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => JSON.parse(line));\n\n const allHealthy = services.every((service) => {\n const container = containers.find((c: any) => c.Service === service);\n return (\n container &&\n (container.Health === \"healthy\" || container.State === \"running\")\n );\n });\n\n return allHealthy;\n } catch (e) {\n return false;\n }\n };\n\n const success = await waitFor(checkHealth, {\n timeoutMs: maxWaitMs,\n intervalMs: 2000,\n onProgress: (elapsed) => {\n const seconds = Math.floor(elapsed / 1000);\n spinner.text = `Waiting for services to be healthy... (${seconds}s)`;\n },\n });\n\n if (success) {\n spinner.succeed(\"All services healthy\");\n } else {\n spinner.warn(\"Services taking longer than expected...\");\n console.log(\n chalk.yellow(\n \"\\n⚠️ Health check timeout. Services may still be starting.\"\n )\n );\n console.log(\n chalk.gray(\" Run\"),\n chalk.cyan(\"baseboards logs\"),\n chalk.gray(\"to check progress.\")\n );\n }\n}\n\n/**\n * Run database migrations\n */\nasync function runMigrations(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Running database migrations...\").start();\n\n try {\n await execa(\n \"docker\",\n [\"compose\", \"exec\", \"-T\", \"api\", \"alembic\", \"upgrade\", \"head\"],\n {\n cwd: ctx.dir,\n }\n );\n spinner.succeed(\"Database migrations complete\");\n } catch (error) {\n spinner.fail(\"Database migrations failed\");\n console.log(\n chalk.yellow(\n \"\\n⚠️ Database migrations failed. You may need to run them manually:\"\n )\n );\n console.log(error);\n console.log(chalk.cyan(\" docker compose exec api alembic upgrade head\"));\n // Don't throw - app can still start\n }\n}\n\n/**\n * Print success message with URLs and next steps\n */\nfunction printSuccessMessage(\n ctx: ProjectContext,\n detached: boolean,\n hasKeyWarning: boolean\n): void {\n console.log(chalk.green.bold(\"\\n✨ Baseboards is running!\\n\"));\n console.log(\n chalk.cyan(\" 🌐 Web:\"),\n chalk.underline(`http://localhost:${ctx.ports.web}`)\n );\n console.log(\n chalk.cyan(\" 🔌 API:\"),\n chalk.underline(`http://localhost:${ctx.ports.api}`)\n );\n console.log(\n chalk.cyan(\" 📊 GraphQL:\"),\n chalk.underline(`http://localhost:${ctx.ports.api}/graphql`)\n );\n\n if (hasKeyWarning) {\n console.log(chalk.yellow(\"\\n⚠️ Remember to configure provider API keys!\"));\n console.log(chalk.gray(\" Edit:\"), chalk.cyan(\"api/.env\"));\n console.log(\n chalk.gray(\" Docs:\"),\n chalk.cyan(\"https://baseboards.dev/docs/setup\")\n );\n }\n\n console.log(chalk.gray(\"\\n📖 Commands:\"));\n console.log(chalk.gray(\" Stop:\"), chalk.cyan(\"baseboards down\"));\n console.log(chalk.gray(\" Logs:\"), chalk.cyan(\"baseboards logs\"));\n console.log(chalk.gray(\" Status:\"), chalk.cyan(\"baseboards status\"));\n console.log();\n}\n","/**\n * Utility functions for the Baseboards CLI\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport which from \"which\";\nimport type { Prerequisites, ProjectContext } from \"./types.js\";\nimport chalk from \"chalk\";\nimport crypto from \"crypto\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Get the templates directory (bundled with the CLI package)\n */\nexport function getTemplatesDir(): string {\n // In built package, templates are at root level next to dist/\n return path.join(__dirname, \"../templates\");\n}\n\n/**\n * Check system prerequisites\n */\nexport async function checkPrerequisites(): Promise<Prerequisites> {\n const prereqs: Prerequisites = {\n docker: { installed: false },\n node: { installed: true, satisfies: false },\n platform: { name: process.platform },\n };\n\n // Check Docker\n try {\n const dockerPath = await which(\"docker\");\n prereqs.docker.installed = !!dockerPath;\n\n if (dockerPath) {\n const { stdout } = await execa(\"docker\", [\"--version\"]);\n const match = stdout.match(/Docker version ([\\d.]+)/);\n if (match) {\n prereqs.docker.version = match[1];\n }\n\n // Check Docker Compose\n try {\n const { stdout: composeStdout } = await execa(\"docker\", [\n \"compose\",\n \"version\",\n ]);\n const composeMatch = composeStdout.match(/version v?([\\d.]+)/);\n if (composeMatch) {\n prereqs.docker.composeVersion = composeMatch[1];\n }\n } catch (e) {\n // Compose not available\n }\n }\n } catch (e) {\n // Docker not installed\n }\n\n // Check Node version\n const nodeVersion = process.version.replace(\"v\", \"\");\n prereqs.node.version = nodeVersion;\n\n const majorVersion = parseInt(nodeVersion.split(\".\")[0], 10);\n prereqs.node.satisfies = majorVersion >= 20;\n\n // Check if WSL\n if (process.platform === \"linux\") {\n try {\n const { stdout } = await execa(\"uname\", [\"-r\"]);\n if (\n stdout.toLowerCase().includes(\"microsoft\") ||\n stdout.toLowerCase().includes(\"wsl\")\n ) {\n prereqs.platform.isWSL = true;\n }\n } catch (e) {\n // Not WSL or can't determine\n }\n }\n\n return prereqs;\n}\n\n/**\n * Assert that prerequisites are met, or exit with helpful error\n */\nexport async function assertPrerequisites(): Promise<void> {\n const prereqs = await checkPrerequisites();\n\n const errors: string[] = [];\n\n if (!prereqs.docker.installed) {\n errors.push(\n \"🐳 Docker is not installed.\",\n \" Install from: https://docs.docker.com/get-docker/\"\n );\n } else if (!prereqs.docker.composeVersion) {\n errors.push(\n \"🐳 Docker Compose (v2) is not available.\",\n \" Update Docker to get Compose v2: https://docs.docker.com/compose/install/\"\n );\n }\n\n if (!prereqs.node.satisfies) {\n errors.push(\n `⚠️ Node.js ${prereqs.node.version} is too old (need v20+).`,\n \" Install from: https://nodejs.org/\"\n );\n }\n\n if (errors.length > 0) {\n console.error(chalk.red(\"\\n❌ Prerequisites not met:\\n\"));\n errors.forEach((err) => console.error(chalk.yellow(err)));\n console.error(\"\");\n process.exit(1);\n }\n}\n\n/**\n * Check if a directory is already scaffolded\n */\nexport function isScaffolded(dir: string): boolean {\n // Check for key files that indicate scaffolding\n const keyFiles = [\"compose.yaml\", \"web/package.json\", \"api/pyproject.toml\"];\n\n return keyFiles.every((file) => fs.existsSync(path.join(dir, file)));\n}\n\n/**\n * Find an available port\n */\nexport async function findAvailablePort(\n preferred: number,\n maxAttempts = 50\n): Promise<number> {\n for (let port = preferred; port < preferred + maxAttempts; port++) {\n if (await isPortAvailable(port)) {\n return port;\n }\n }\n throw new Error(`No available port found near ${preferred}`);\n}\n\n/**\n * Check if a port is available\n */\nasync function isPortAvailable(port: number): Promise<boolean> {\n const { createServer } = await import(\"net\");\n return new Promise((resolve) => {\n const server = createServer();\n server.once(\"error\", () => resolve(false));\n server.once(\"listening\", () => {\n server.close();\n resolve(true);\n });\n server.listen(port);\n });\n}\n\n/**\n * Generate a random secure string\n */\nexport function generateSecret(length = 32): string {\n return crypto.randomBytes(length).toString(\"hex\");\n}\n\n/**\n * Generate a random password\n */\nexport function generatePassword(length = 24): string {\n const charset =\n \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#%^&*\";\n let password = \"\";\n const bytes = crypto.randomBytes(length);\n\n for (let i = 0; i < length; i++) {\n password += charset[bytes[i] % charset.length];\n }\n\n return password;\n}\n\n/**\n * Parse custom ports string (e.g., \"web=3300 api=8800\")\n */\nexport function parsePortsOption(portsStr: string): Partial<{\n web: number;\n api: number;\n db: number;\n redis: number;\n}> {\n const ports: any = {};\n const pairs = portsStr.split(/\\s+/);\n\n for (const pair of pairs) {\n const [service, port] = pair.split(\"=\");\n const portNum = parseInt(port, 10);\n if (\n service &&\n [\"web\", \"api\", \"db\", \"redis\"].includes(service) &&\n !isNaN(portNum)\n ) {\n ports[service] = portNum;\n }\n }\n\n return ports;\n}\n\n/**\n * Read package.json version\n */\nexport function getCliVersion(): string {\n const packagePath = path.join(__dirname, \"../package.json\");\n const packageJson = JSON.parse(fs.readFileSync(packagePath, \"utf-8\"));\n return packageJson.version;\n}\n\n/**\n * Detect missing provider API keys in .env file\n */\nexport function detectMissingProviderKeys(envPath: string): string[] {\n if (!fs.existsSync(envPath)) {\n return [];\n }\n\n const envContent = fs.readFileSync(envPath, \"utf-8\");\n const providerKeys = [\n \"REPLICATE_API_TOKEN\",\n \"FAL_KEY\",\n \"OPENAI_API_KEY\",\n \"GOOGLE_API_KEY\",\n ];\n\n const missingKeys: string[] = [];\n\n for (const key of providerKeys) {\n const regex = new RegExp(`^${key}=(.*)$`, \"m\");\n const match = envContent.match(regex);\n\n // Key is missing if not found or if value is empty\n if (!match || !match[1] || match[1].trim() === \"\") {\n missingKeys.push(key);\n }\n }\n\n // If all keys are missing, user hasn't configured any\n if (missingKeys.length === providerKeys.length) {\n return missingKeys;\n }\n\n return [];\n}\n\n/**\n * Wait for a condition with timeout\n */\nexport async function waitFor(\n condition: () => Promise<boolean>,\n options: {\n timeoutMs: number;\n intervalMs?: number;\n onProgress?: (elapsed: number) => void;\n }\n): Promise<boolean> {\n const { timeoutMs, intervalMs = 1000, onProgress } = options;\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeoutMs) {\n if (await condition()) {\n return true;\n }\n\n if (onProgress) {\n onProgress(Date.now() - startTime);\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n\n return false;\n}\n","/**\n * down command - Stop Baseboards\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport type { DownOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function down(directory: string, options: DownOptions): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\n❌ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n const spinner = ora('Stopping services...').start();\n\n const args = ['compose', 'down'];\n if (options.volumes) {\n args.push('--volumes');\n }\n\n try {\n await execa('docker', args, {\n cwd: dir,\n });\n\n spinner.succeed('Services stopped');\n\n if (options.volumes) {\n console.log(chalk.yellow('⚠️ Volumes removed (database data deleted)'));\n }\n } catch (error: any) {\n spinner.fail('Failed to stop services');\n throw error;\n }\n}\n","/**\n * logs command - View service logs\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport type { LogsOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function logs(\n directory: string,\n services: string[],\n options: LogsOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\n❌ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n const args = ['compose', 'logs'];\n\n if (options.follow) {\n args.push('--follow');\n }\n\n if (options.since) {\n args.push('--since', options.since);\n }\n\n if (options.tail) {\n args.push('--tail', options.tail);\n }\n\n // Add specific services if provided\n if (services.length > 0) {\n args.push(...services);\n }\n\n try {\n await execa('docker', args, {\n cwd: dir,\n stdio: 'inherit',\n });\n } catch (error: any) {\n // Ctrl+C is expected, don't treat as error\n if (error.signal !== 'SIGINT') {\n throw error;\n }\n }\n}\n","/**\n * status command - Show service status\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport { isScaffolded } from '../utils.js';\n\nexport async function status(directory: string): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\n❌ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n console.log(chalk.blue.bold('\\n📊 Service Status\\n'));\n\n try {\n await execa('docker', ['compose', 'ps'], {\n cwd: dir,\n stdio: 'inherit',\n });\n } catch (error: any) {\n console.error(chalk.red('\\n❌ Failed to get status'));\n throw error;\n }\n}\n","/**\n * clean command - Clean up Docker resources\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport prompts from 'prompts';\nimport type { CleanOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function clean(\n directory: string,\n options: CleanOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\n❌ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n if (options.hard) {\n console.log(chalk.yellow('\\n⚠️ WARNING: This will delete:'));\n console.log(chalk.yellow(' • All containers'));\n console.log(chalk.yellow(' • All volumes (database data will be lost)'));\n console.log(chalk.yellow(' • All images'));\n\n const response = await prompts({\n type: 'confirm',\n name: 'confirmed',\n message: 'Are you sure?',\n initial: false,\n });\n\n if (!response.confirmed) {\n console.log(chalk.gray('\\nCancelled'));\n return;\n }\n }\n\n const spinner = ora('Cleaning up...').start();\n\n try {\n // Stop containers\n await execa('docker', ['compose', 'down', '--volumes', '--remove-orphans'], {\n cwd: dir,\n });\n\n if (options.hard) {\n // Remove images\n try {\n const { stdout } = await execa('docker', ['compose', 'images', '-q'], {\n cwd: dir,\n });\n\n const imageIds = stdout.split('\\n').filter(Boolean);\n if (imageIds.length > 0) {\n await execa('docker', ['rmi', ...imageIds]);\n }\n } catch (e) {\n // Images might not exist or already removed\n }\n }\n\n spinner.succeed('Cleanup complete');\n\n if (options.hard) {\n console.log(chalk.green('\\n✨ All Docker resources removed'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to start fresh.'));\n } else {\n console.log(chalk.green('\\n✨ Containers and volumes removed'));\n }\n } catch (error: any) {\n spinner.fail('Cleanup failed');\n throw error;\n }\n}\n","/**\n * update command - Update Baseboards to latest version\n */\n\nimport path from 'path';\nimport chalk from 'chalk';\nimport type { UpdateOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function update(\n directory: string,\n options: UpdateOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\n❌ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n console.log(chalk.blue.bold('\\n🔄 Update Command\\n'));\n console.log(chalk.yellow('⚠️ This feature is coming soon!'));\n console.log(chalk.gray('\\nFor now, to update:'));\n console.log(chalk.gray('1. Update the CLI:'), chalk.cyan('npm install -g @weirdfingers/baseboards@latest'));\n console.log(chalk.gray('2. Pull new images:'), chalk.cyan('docker compose pull'));\n console.log(chalk.gray('3. Restart:'), chalk.cyan('baseboards down && baseboards up'));\n console.log();\n\n // TODO: Implement update logic:\n // 1. Check for new version on npm\n // 2. Scan for modified source files (git diff or timestamp check)\n // 3. If no modifications:\n // - Copy new templates (preserving config)\n // - Pull new Docker images\n // - Update package.json versions\n // 4. If modifications detected:\n // - Warn user\n // - Offer git-based merge if repo detected\n // - Create backup otherwise\n // 5. Preserve:\n // - All .env files\n // - config/*.yaml files\n // - data/storage/ directory\n}\n","/**\n * doctor command - Run diagnostics\n */\n\nimport path from 'path';\nimport fs from 'fs-extra';\nimport chalk from 'chalk';\nimport { checkPrerequisites, isScaffolded, detectMissingProviderKeys, getCliVersion } from '../utils.js';\n\nexport async function doctor(directory: string): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n console.log(chalk.blue.bold('\\n🩺 Baseboards Diagnostics\\n'));\n\n // CLI Version\n console.log(chalk.cyan('CLI Version:'), getCliVersion());\n\n // Prerequisites\n console.log(chalk.cyan('\\n📋 Prerequisites:'));\n const prereqs = await checkPrerequisites();\n\n console.log(\n chalk.gray(' Node.js:'),\n prereqs.node.installed\n ? prereqs.node.satisfies\n ? chalk.green(`✓ v${prereqs.node.version}`)\n : chalk.yellow(`⚠️ v${prereqs.node.version} (need v20+)`)\n : chalk.red('✗ Not installed')\n );\n\n console.log(\n chalk.gray(' Docker:'),\n prereqs.docker.installed\n ? chalk.green(`✓ v${prereqs.docker.version}`)\n : chalk.red('✗ Not installed')\n );\n\n if (prereqs.docker.composeVersion) {\n console.log(\n chalk.gray(' Docker Compose:'),\n chalk.green(`✓ v${prereqs.docker.composeVersion}`)\n );\n } else if (prereqs.docker.installed) {\n console.log(chalk.gray(' Docker Compose:'), chalk.red('✗ Not available'));\n }\n\n console.log(chalk.gray(' Platform:'), prereqs.platform.name);\n if (prereqs.platform.isWSL) {\n console.log(chalk.gray(' WSL:'), chalk.blue('✓ Detected'));\n }\n\n // Project info\n console.log(chalk.cyan('\\n📂 Project:'));\n const scaffolded = isScaffolded(dir);\n console.log(\n chalk.gray(' Scaffolded:'),\n scaffolded ? chalk.green('✓ Yes') : chalk.yellow('✗ No')\n );\n\n if (scaffolded) {\n console.log(chalk.gray(' Directory:'), dir);\n\n // Check for key files\n const webPkg = path.join(dir, 'web/package.json');\n const apiPkg = path.join(dir, 'api/pyproject.toml');\n const composeFile = path.join(dir, 'compose.yaml');\n\n console.log(\n chalk.gray(' Web package:'),\n fs.existsSync(webPkg) ? chalk.green('✓') : chalk.red('✗')\n );\n console.log(\n chalk.gray(' API package:'),\n fs.existsSync(apiPkg) ? chalk.green('✓') : chalk.red('✗')\n );\n console.log(\n chalk.gray(' Compose file:'),\n fs.existsSync(composeFile) ? chalk.green('✓') : chalk.red('✗')\n );\n\n // Check .env files\n console.log(chalk.cyan('\\n🔐 Environment:'));\n const webEnv = path.join(dir, 'web/.env');\n const apiEnv = path.join(dir, 'api/.env');\n const dockerEnv = path.join(dir, 'docker/.env');\n\n console.log(\n chalk.gray(' Web .env:'),\n fs.existsSync(webEnv) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n console.log(\n chalk.gray(' API .env:'),\n fs.existsSync(apiEnv) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n console.log(\n chalk.gray(' Docker .env:'),\n fs.existsSync(dockerEnv) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n\n // Check provider keys\n if (fs.existsSync(apiEnv)) {\n const missingKeys = detectMissingProviderKeys(apiEnv);\n if (missingKeys.length > 0) {\n console.log(\n chalk.gray(' Provider keys:'),\n chalk.yellow(`⚠️ ${missingKeys.length} missing`)\n );\n console.log(chalk.gray(' Missing:'), missingKeys.map(k => chalk.cyan(k)).join(', '));\n } else {\n console.log(chalk.gray(' Provider keys:'), chalk.green('✓ Configured'));\n }\n }\n\n // Check config files\n console.log(chalk.cyan('\\n⚙️ Configuration:'));\n const generatorsYaml = path.join(dir, 'api/config/generators.yaml');\n const storageYaml = path.join(dir, 'api/config/storage_config.yaml');\n\n console.log(\n chalk.gray(' generators.yaml:'),\n fs.existsSync(generatorsYaml) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n console.log(\n chalk.gray(' storage_config.yaml:'),\n fs.existsSync(storageYaml) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n\n // Check storage directory\n const storageDir = path.join(dir, 'data/storage');\n console.log(\n chalk.gray(' Storage directory:'),\n fs.existsSync(storageDir) ? chalk.green('✓') : chalk.yellow('✗ Missing')\n );\n }\n\n // Recommendations\n console.log(chalk.cyan('\\n💡 Recommendations:'));\n const recommendations: string[] = [];\n\n if (!prereqs.node.satisfies) {\n recommendations.push('Upgrade Node.js to v20 or higher');\n }\n\n if (!prereqs.docker.installed) {\n recommendations.push('Install Docker Desktop: https://docs.docker.com/get-docker/');\n } else if (!prereqs.docker.composeVersion) {\n recommendations.push('Update Docker to get Compose v2');\n }\n\n if (!scaffolded) {\n recommendations.push('Run ' + chalk.cyan('baseboards up') + ' to scaffold a project');\n }\n\n if (recommendations.length === 0) {\n console.log(chalk.green(' ✓ Everything looks good!'));\n } else {\n recommendations.forEach((rec) => console.log(chalk.yellow(' •'), rec));\n }\n\n console.log();\n}\n"],"mappings":"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","names":["fileURLToPath","chalk","execa","fs","path","chalk","packageJson","chalk","path","fs","execa","execa","path","chalk","ora","path","chalk","ora","execa","execa","path","chalk","path","chalk","execa","execa","path","chalk","path","chalk","execa","execa","path","chalk","ora","prompts","path","chalk","prompts","ora","execa","path","chalk","path","chalk","path","fs","chalk","path","chalk","fs","__filename","fileURLToPath","__dirname","chalk"]}
|
package/package.json
CHANGED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Artifact Resolution Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how artifact resolution works in the Boards generator system.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Generators can accept artifacts (images, videos, audio, etc.) from previous generations as inputs. The artifact resolution system automatically converts generation ID strings (UUIDs) into typed artifact objects with proper validation.
|
|
8
|
+
|
|
9
|
+
## How It Works
|
|
10
|
+
|
|
11
|
+
### Automatic Type Introspection
|
|
12
|
+
|
|
13
|
+
The system uses Pydantic type introspection to automatically detect which fields are artifacts. **No manual configuration needed!**
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from pydantic import BaseModel, Field
|
|
17
|
+
from boards.generators.artifacts import ImageArtifact, VideoArtifact
|
|
18
|
+
|
|
19
|
+
class MyGeneratorInput(BaseModel):
|
|
20
|
+
"""Input schema for my generator."""
|
|
21
|
+
|
|
22
|
+
prompt: str = Field(description="Text prompt")
|
|
23
|
+
|
|
24
|
+
# These are automatically detected as artifact fields
|
|
25
|
+
image_source: ImageArtifact = Field(description="Input image")
|
|
26
|
+
video_sources: list[VideoArtifact] = Field(description="Input videos")
|
|
27
|
+
|
|
28
|
+
# Regular fields are ignored
|
|
29
|
+
num_outputs: int = Field(default=1, ge=1, le=10)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
That's it! No decorators, no class variables, no manual registration needed.
|
|
33
|
+
|
|
34
|
+
### What Happens Automatically
|
|
35
|
+
|
|
36
|
+
When a generation is submitted with input parameters like:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"prompt": "enhance the colors",
|
|
41
|
+
"image_source": "550e8400-e29b-41d4-a716-446655440000",
|
|
42
|
+
"video_sources": [
|
|
43
|
+
"7c9e6679-7425-40de-944b-e07fc1f90ae7",
|
|
44
|
+
"8f9e6679-7425-40de-944b-e07fc1f90ae8"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The system:
|
|
50
|
+
|
|
51
|
+
1. **Detects** that `image_source` and `video_sources` are artifact fields (via type introspection)
|
|
52
|
+
2. **Queries** the database to fetch the generations by ID
|
|
53
|
+
3. **Validates** each generation:
|
|
54
|
+
- Belongs to the same tenant (access control)
|
|
55
|
+
- Has status="completed" (can't use pending generations)
|
|
56
|
+
- Has the correct artifact type (can't use a video where an image is expected)
|
|
57
|
+
4. **Converts** generation records to typed artifact objects
|
|
58
|
+
5. **Validates** the input with Pydantic
|
|
59
|
+
|
|
60
|
+
All of this happens automatically in [actors.py](src/boards/workers/actors.py).
|
|
61
|
+
|
|
62
|
+
## Supported Patterns
|
|
63
|
+
|
|
64
|
+
### Single Artifact
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
image_source: ImageArtifact = Field(...)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Input**: Single generation ID string (e.g., `"550e8400-e29b-41d4-a716-446655440000"`)
|
|
71
|
+
**Output**: Single `ImageArtifact` object
|
|
72
|
+
|
|
73
|
+
### List of Artifacts
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
image_sources: list[ImageArtifact] = Field(min_length=1)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Input**: List of generation ID strings (e.g., `["id1", "id2"]`)
|
|
80
|
+
**Output**: List of `ImageArtifact` objects (always a list, even if input has one ID)
|
|
81
|
+
|
|
82
|
+
**Important**: If the field type is `list[ImageArtifact]`, the resolved value will ALWAYS be a list, even if only one generation ID is provided. The system respects the type annotation.
|
|
83
|
+
|
|
84
|
+
### Optional Artifacts
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
reference_image: ImageArtifact | None = Field(default=None)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Field can be omitted or set to `null`.
|
|
91
|
+
|
|
92
|
+
## Supported Artifact Types
|
|
93
|
+
|
|
94
|
+
- `ImageArtifact` - Images (PNG, JPEG, WebP, etc.)
|
|
95
|
+
- `VideoArtifact` - Videos (MP4, WebM, etc.)
|
|
96
|
+
- `AudioArtifact` - Audio files (MP3, WAV, etc.)
|
|
97
|
+
- `TextArtifact` - Text content
|
|
98
|
+
|
|
99
|
+
## Error Handling
|
|
100
|
+
|
|
101
|
+
The system provides clear error messages when validation fails:
|
|
102
|
+
|
|
103
|
+
- `"Generation {id} not found"` - Invalid generation ID
|
|
104
|
+
- `"Access denied to generation {id} - tenant mismatch"` - User doesn't own this generation
|
|
105
|
+
- `"Generation {id} is not completed (status: pending)"` - Can't use incomplete generations
|
|
106
|
+
- `"Generation {id} has wrong artifact type: expected image, got video"` - Type mismatch
|
|
107
|
+
|
|
108
|
+
## Examples
|
|
109
|
+
|
|
110
|
+
### Image-to-Image Editor
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
class ImageEditInput(BaseModel):
|
|
114
|
+
prompt: str = Field(description="Editing instructions")
|
|
115
|
+
image_source: ImageArtifact = Field(description="Image to edit")
|
|
116
|
+
strength: float = Field(default=0.8, ge=0.0, le=1.0)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Multi-Image Collage
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
class CollageInput(BaseModel):
|
|
123
|
+
images: list[ImageArtifact] = Field(
|
|
124
|
+
description="Images to combine",
|
|
125
|
+
min_length=2,
|
|
126
|
+
max_length=9
|
|
127
|
+
)
|
|
128
|
+
layout: Literal["grid", "freeform"] = Field(default="grid")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Video + Audio Mixer
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
class VideoAudioMixInput(BaseModel):
|
|
135
|
+
video_source: VideoArtifact = Field(description="Video track")
|
|
136
|
+
audio_source: AudioArtifact = Field(description="Audio track")
|
|
137
|
+
volume: float = Field(default=1.0, ge=0.0, le=2.0)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Implementation Details
|
|
141
|
+
|
|
142
|
+
The automatic detection is handled by:
|
|
143
|
+
|
|
144
|
+
- [`extract_artifact_fields()`](src/boards/generators/artifact_resolution.py) - Introspects schema to find artifact fields
|
|
145
|
+
- [`resolve_input_artifacts()`](src/boards/generators/artifact_resolution.py) - Resolves generation IDs to artifacts
|
|
146
|
+
- [`resolve_generation_ids_to_artifacts()`](src/boards/generators/artifact_resolution.py) - Database queries and validation
|
|
147
|
+
|
|
148
|
+
See [artifact_resolution.py](src/boards/generators/artifact_resolution.py) for the full implementation.
|
package/templates/api/Dockerfile
CHANGED
|
@@ -14,8 +14,8 @@ RUN pip install --no-cache-dir uv
|
|
|
14
14
|
# Copy application code first (needed for version detection in pyproject.toml)
|
|
15
15
|
COPY . .
|
|
16
16
|
|
|
17
|
-
# Install Python dependencies with
|
|
18
|
-
RUN uv pip install --system --no-cache-dir .[
|
|
17
|
+
# Install Python dependencies with all generator providers
|
|
18
|
+
RUN uv pip install --system --no-cache-dir .[generators-all]
|
|
19
19
|
|
|
20
20
|
# Create non-root user
|
|
21
21
|
RUN useradd -m -u 1001 apiuser && chown -R apiuser:apiuser /app
|