From 502b26882e0625dc2bb5271923229009306d4fe6 Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Fri, 18 Jul 2025 22:24:27 +0530 Subject: [PATCH] Feat: cli for init, start and stop --- .../app/components/logs/log-text-collapse.tsx | 6 +- apps/webapp/app/routes/home.logs.activity.tsx | 82 ++--- apps/webapp/app/routes/home.logs.all.tsx | 94 +++--- packages/core-cli/README.md | 193 ++++++++++++ packages/core-cli/package.json | 2 +- packages/core-cli/src/cli/index.ts | 17 +- packages/core-cli/src/commands/init.ts | 293 ++++++++++++------ packages/core-cli/src/commands/start.ts | 72 +++++ packages/core-cli/src/commands/stop.ts | 72 +++++ .../core-cli/src/utils/docker-interactive.ts | 108 +++++++ packages/core-cli/src/utils/env-checker.ts | 23 ++ trigger/docker-compose.yaml | 2 +- 12 files changed, 778 insertions(+), 186 deletions(-) create mode 100644 packages/core-cli/README.md create mode 100644 packages/core-cli/src/commands/start.ts create mode 100644 packages/core-cli/src/commands/stop.ts create mode 100644 packages/core-cli/src/utils/docker-interactive.ts create mode 100644 packages/core-cli/src/utils/env-checker.ts diff --git a/apps/webapp/app/components/logs/log-text-collapse.tsx b/apps/webapp/app/components/logs/log-text-collapse.tsx index 3880799..dfc6123 100644 --- a/apps/webapp/app/components/logs/log-text-collapse.tsx +++ b/apps/webapp/app/components/logs/log-text-collapse.tsx @@ -83,9 +83,9 @@ export function LogTextCollapse({ isLong ? "max-h-16 overflow-hidden" : "", )} style={{ lineHeight: "1.5" }} - > - {displayText} -

+ dangerouslySetInnerHTML={{ __html: text }} + /> + {isLong && ( <> diff --git a/apps/webapp/app/routes/home.logs.activity.tsx b/apps/webapp/app/routes/home.logs.activity.tsx index f7c3bb1..0570a80 100644 --- a/apps/webapp/app/routes/home.logs.activity.tsx +++ b/apps/webapp/app/routes/home.logs.activity.tsx @@ -58,44 +58,52 @@ export default function LogsActivity() { ]} />
- {logs.length > 0 && ( - - )} + {isInitialLoad ? ( + <> + {" "} + + ) : ( + <> + {logs.length > 0 && ( + + )} - {/* Logs List */} -
- {logs.length === 0 ? ( - - -
- -

- No activity logs found -

-

- {selectedSource || selectedStatus - ? "Try adjusting your filters to see more results." - : "No activity ingestion logs are available yet."} -

-
-
-
- ) : ( - - )} -
+ {/* Logs List */} +
+ {logs.length === 0 ? ( + + +
+ +

+ No activity logs found +

+

+ {selectedSource || selectedStatus + ? "Try adjusting your filters to see more results." + : "No activity ingestion logs are available yet."} +

+
+
+
+ ) : ( + + )} +
+ + )}
); diff --git a/apps/webapp/app/routes/home.logs.all.tsx b/apps/webapp/app/routes/home.logs.all.tsx index 6d09992..2797967 100644 --- a/apps/webapp/app/routes/home.logs.all.tsx +++ b/apps/webapp/app/routes/home.logs.all.tsx @@ -26,18 +26,6 @@ export default function LogsAll() { status: selectedStatus, }); - if (isInitialLoad) { - return ( - - -
- -
-
-
- ); - } - return ( <>
- {/* Filters */} - {logs.length > 0 && ( - + {isInitialLoad ? ( + <> + {" "} + + ) : ( + <> + {" "} + {/* Filters */} + {logs.length > 0 && ( + + )} + {/* Logs List */} +
+ {logs.length === 0 ? ( + + +
+ +

+ No logs found +

+

+ {selectedSource || selectedStatus + ? "Try adjusting your filters to see more results." + : "No ingestion logs are available yet."} +

+
+
+
+ ) : ( + + )} +
+ )} - - {/* Logs List */} -
- {logs.length === 0 ? ( - - -
- -

No logs found

-

- {selectedSource || selectedStatus - ? "Try adjusting your filters to see more results." - : "No ingestion logs are available yet."} -

-
-
-
- ) : ( - - )} -
); diff --git a/packages/core-cli/README.md b/packages/core-cli/README.md new file mode 100644 index 0000000..21451fd --- /dev/null +++ b/packages/core-cli/README.md @@ -0,0 +1,193 @@ +# Core CLI + +🧠 **CORE - Contextual Observation & Recall Engine** + +A Command-Line Interface for setting up and managing the Core development environment. + +## Installation + +```bash +npm install -g @redplanethq/core +``` + +## Commands + +### `core init` + +**One-time setup command** - Initializes the Core development environment with full configuration. + +### `core start` + +**Daily usage command** - Starts all Core services (Docker containers). + +### `core stop` + +**Daily usage command** - Stops all Core services (Docker containers). + +## Getting Started + +### Prerequisites + +- **Node.js** (v18.20.0 or higher) +- **Docker** and **Docker Compose** +- **Git** +- **pnpm** package manager + +### Initial Setup + +1. **Run the initialization command:** + + ```bash + core init + ``` + +2. **The CLI will guide you through the complete setup process:** + +#### Step 1: Repository Validation + +- The CLI checks if you're in the Core repository +- If not, it offers to clone the repository for you +- Choose **Yes** to clone automatically, or **No** to clone manually + +#### Step 2: Environment Configuration + +- Copies `.env.example` to `.env` in the root directory +- Copies `trigger/.env.example` to `trigger/.env` +- Skips copying if `.env` files already exist + +#### Step 3: Docker Services Startup + +- Starts main Core services: `docker compose up -d` +- Starts Trigger.dev services: `docker compose up -d` (in trigger/ directory) +- Shows real-time output with progress indicators + +#### Step 4: Database Health Check + +- Verifies PostgreSQL is running on `localhost:5432` +- Retries for up to 60 seconds if needed + +#### Step 5: Trigger.dev Setup (Interactive) + +- **If Trigger.dev is not configured:** + + 1. Prompts you to open http://localhost:8030 + 2. Asks you to login to Trigger.dev + 3. Guides you to create an organization and project + 4. Collects your Project ID and Secret Key + 5. Updates `.env` with your Trigger.dev configuration + 6. Restarts Core services with new configuration + +- **If Trigger.dev is already configured:** + - Skips setup and shows "Configuration already exists" message + +#### Step 6: Docker Registry Login + +- Displays docker login command with credentials from `.env` +- Waits for you to complete the login process + +#### Step 7: Trigger.dev Task Deployment + +- Automatically runs: `npx trigger.dev@v4-beta login -a http://localhost:8030` +- Deploys tasks with: `pnpm trigger:deploy` +- Shows manual deployment instructions if automatic deployment fails + +#### Step 8: Setup Complete! + +- Confirms all services are running +- Shows service URLs and connection information + +## Daily Usage + +After initial setup, use these commands for daily development: + +### Start Services + +```bash +core start +``` + +Starts all Docker containers for Core development. + +### Stop Services + +```bash +core stop +``` + +Stops all Docker containers. + +## Service URLs + +After setup, these services will be available: + +- **Core Application**: http://localhost:3033 +- **Trigger.dev**: http://localhost:8030 +- **PostgreSQL**: localhost:5432 + +## Troubleshooting + +### Repository Not Found + +If you run commands outside the Core repository: + +- The CLI will offer to clone the repository automatically +- Choose **Yes** to clone in the current directory +- Or navigate to the Core repository manually + +### Docker Issues + +- Ensure Docker is running +- Check Docker Compose is installed +- Verify you have sufficient system resources + +### Trigger.dev Setup Issues + +- Check container logs: `docker logs trigger-webapp --tail 50` +- Ensure you can access http://localhost:8030 +- Verify your network allows connections to localhost + +### Environment Variables + +The CLI automatically manages these environment variables: + +- `TRIGGER_PROJECT_ID` - Your Trigger.dev project ID +- `TRIGGER_SECRET_KEY` - Your Trigger.dev secret key +- Docker registry credentials for deployment + +### Manual Trigger.dev Deployment + +If automatic deployment fails, run manually: + +```bash +npx trigger.dev@v4-beta login -a http://localhost:8030 +pnpm trigger:deploy +``` + +## Development Workflow + +1. **First time setup:** `core init` +2. **Daily development:** + - `core start` - Start your development environment + - Do your development work + - `core stop` - Stop services when done + +## Support + +For issues and questions: + +- Check the main Core repository: https://github.com/redplanethq/core +- Review Docker container logs for troubleshooting +- Ensure all prerequisites are properly installed + +## Features + +- šŸš€ **One-command setup** - Complete environment initialization +- šŸ”„ **Smart configuration** - Skips already configured components +- šŸ“± **Real-time feedback** - Live progress indicators and output +- 🐳 **Docker integration** - Full container lifecycle management +- šŸ”§ **Interactive setup** - Guided configuration process +- šŸŽÆ **Error handling** - Graceful failure with recovery instructions + +--- + +**Happy coding with Core!** šŸŽ‰ diff --git a/packages/core-cli/package.json b/packages/core-cli/package.json index bdf4585..f2c905e 100644 --- a/packages/core-cli/package.json +++ b/packages/core-cli/package.json @@ -1,6 +1,6 @@ { "name": "@redplanethq/core", - "version": "0.1.0", + "version": "0.1.1", "description": "A Command-Line Interface for Core", "type": "module", "license": "MIT", diff --git a/packages/core-cli/src/cli/index.ts b/packages/core-cli/src/cli/index.ts index 554f06c..5b145cf 100644 --- a/packages/core-cli/src/cli/index.ts +++ b/packages/core-cli/src/cli/index.ts @@ -1,10 +1,25 @@ import { Command } from "commander"; import { initCommand } from "../commands/init.js"; +import { startCommand } from "../commands/start.js"; +import { stopCommand } from "../commands/stop.js"; const program = new Command(); program.name("core").description("Core CLI - A Command-Line Interface for Core").version("0.1.0"); -program.command("init").description("Initialize Core development environment").action(initCommand); +program + .command("init") + .description("Initialize Core development environment (run once)") + .action(initCommand); + +program + .command("start") + .description("Start Core development environment") + .action(startCommand); + +program + .command("stop") + .description("Stop Core development environment") + .action(stopCommand); program.parse(process.argv); diff --git a/packages/core-cli/src/commands/init.ts b/packages/core-cli/src/commands/init.ts index 9128496..5c790b3 100644 --- a/packages/core-cli/src/commands/init.ts +++ b/packages/core-cli/src/commands/init.ts @@ -1,10 +1,11 @@ -import { intro, outro, text, confirm, spinner } from "@clack/prompts"; +import { intro, outro, text, confirm, spinner, note, log } from "@clack/prompts"; import { isValidCoreRepo } from "../utils/git.js"; import { fileExists, updateEnvFile } from "../utils/file.js"; -import { checkPostgresHealth, executeDockerCommand } from "../utils/docker.js"; +import { checkPostgresHealth } from "../utils/docker.js"; +import { executeDockerCommandInteractive } from "../utils/docker-interactive.js"; import { printCoreBrainLogo } from "../utils/ascii.js"; import { setupEnvFile } from "../utils/env.js"; -import { execSync } from "child_process"; +import { hasTriggerConfig } from "../utils/env-checker.js"; import path from "path"; export async function initCommand() { @@ -15,10 +16,40 @@ export async function initCommand() { // Step 1: Validate repository if (!isValidCoreRepo()) { - outro( - "L Error: This command must be run in the https://github.com/redplanethq/core repository" + log.warning("This directory is not a Core repository"); + note( + "The Core repository is required to run the development environment.\nWould you like to clone it in the current directory?", + "šŸ” Repository Not Found" ); - process.exit(1); + + const shouldClone = await confirm({ + message: "Clone the Core repository here?", + }); + + if (!shouldClone) { + outro("āŒ Setup cancelled. Please navigate to the Core repository or clone it first."); + process.exit(1); + } + + // Clone the repository + try { + await executeDockerCommandInteractive("git clone https://github.com/redplanethq/core.git .", { + cwd: process.cwd(), + message: "Cloning Core repository...", + showOutput: true, + }); + + log.success("Core repository cloned successfully!"); + note( + 'Please run "core init" again to initialize the development environment.', + "āœ… Repository Ready" + ); + outro("šŸŽ‰ Core repository is now available!"); + process.exit(0); + } catch (error: any) { + outro(`āŒ Failed to clone repository: ${error.message}`); + process.exit(1); + } } const rootDir = process.cwd(); @@ -45,14 +76,13 @@ export async function initCommand() { } // Step 3: Docker compose up -d in root - const s2 = spinner(); - s2.start("Starting Docker containers in root..."); - try { - await executeDockerCommand("docker compose up -d", rootDir); - s2.stop("Docker containers started"); + await executeDockerCommandInteractive("docker compose up -d", { + cwd: rootDir, + message: "Starting Docker containers in root...", + showOutput: true, + }); } catch (error: any) { - s2.stop("L Failed to start Docker containers"); throw error; } @@ -98,93 +128,118 @@ export async function initCommand() { } // Step 6: Docker compose up for trigger - const s5 = spinner(); - s5.start("Starting Trigger.dev containers..."); - try { - await executeDockerCommand("docker compose up -d", triggerDir); - s5.stop("Trigger.dev containers started"); + await executeDockerCommandInteractive("docker compose up -d", { + cwd: triggerDir, + message: "Starting Trigger.dev containers...", + showOutput: true, + }); } catch (error: any) { - s5.stop("L Failed to start Trigger.dev containers"); throw error; } - // Step 7: Show login instructions - outro("< Docker containers are now running!"); - console.log("\n= Next steps:"); - console.log("1. Open http://localhost:8030 in your browser"); - console.log( - "2. Login to Trigger.dev (check container logs with: docker logs trigger-webapp --tail 50)" - ); - console.log("3. Press Enter when ready to continue..."); + // Step 7: Check if Trigger.dev configuration already exists + const triggerConfigExists = await hasTriggerConfig(envPath); - await confirm({ - message: "Have you logged in to Trigger.dev and ready to continue?", - }); + if (triggerConfigExists) { + note( + "āœ… Trigger.dev configuration already exists in .env file\n Skipping Trigger.dev setup steps...", + "Configuration Found" + ); + } else { + // Step 8: Show login instructions + outro("šŸŽ‰ Docker containers are now running!"); + note( + "1. Open http://localhost:8030 in your browser\n2. Login to Trigger.dev (check container logs with: docker logs trigger-webapp --tail 50)", + "Next Steps" + ); - // Step 8: Get project details - console.log("\n= In Trigger.dev (http://localhost:8030):"); - console.log("1. Create a new organization and project"); - console.log("2. Go to project settings"); - console.log("3. Copy the Project ID and Secret Key"); + const loginConfirmed = await confirm({ + message: "Have you logged in to Trigger.dev successfully?", + }); - await confirm({ - message: "Press Enter to continue after creating org and project...", - }); + if (!loginConfirmed) { + outro("āŒ Setup cancelled. Please login to Trigger.dev first and run the command again."); + process.exit(1); + } - // Step 9: Get project ID and secret - const projectId = await text({ - message: "Enter your Trigger.dev Project ID:", - validate: (value) => { - if (!value || value.length === 0) { - return "Project ID is required"; - } - return; - }, - }); + // Step 9: Get project details + note( + "1. Create a new organization and project\n2. Go to project settings\n3. Copy the Project ID and Secret Key", + "In Trigger.dev (http://localhost:8030)" + ); - const secretKey = await text({ - message: "Enter your Trigger.dev Secret Key for production:", - validate: (value) => { - if (!value || value.length === 0) { - return "Secret Key is required"; - } - return; - }, - }); + const projectCreated = await confirm({ + message: "Have you created an organization and project in Trigger.dev?", + }); - // Step 10: Update .env with project details - const s6 = spinner(); - s6.start("Updating .env with Trigger.dev configuration..."); + if (!projectCreated) { + outro( + "āŒ Setup cancelled. Please create an organization and project first and run the command again." + ); + process.exit(1); + } - try { - await updateEnvFile(envPath, "TRIGGER_PROJECT_ID", projectId as string); - await updateEnvFile(envPath, "TRIGGER_SECRET_KEY", secretKey as string); - s6.stop("Updated .env with Trigger.dev configuration"); - } catch (error: any) { - s6.stop("L Failed to update .env file"); - throw error; + // Step 10: Get project ID and secret + const projectId = await text({ + message: "Enter your Trigger.dev Project ID:", + validate: (value) => { + if (!value || value.length === 0) { + return "Project ID is required"; + } + return; + }, + }); + + const secretKey = await text({ + message: "Enter your Trigger.dev Secret Key for production:", + validate: (value) => { + if (!value || value.length === 0) { + return "Secret Key is required"; + } + return; + }, + }); + + // Step 11: Update .env with project details + const s6 = spinner(); + s6.start("Updating .env with Trigger.dev configuration..."); + + try { + await updateEnvFile(envPath, "TRIGGER_PROJECT_ID", projectId as string); + await updateEnvFile(envPath, "TRIGGER_SECRET_KEY", secretKey as string); + s6.stop("āœ… Updated .env with Trigger.dev configuration"); + } catch (error: any) { + s6.stop("āŒ Failed to update .env file"); + throw error; + } + + // Step 12: Restart root docker-compose with new configuration + try { + await executeDockerCommandInteractive("docker compose down", { + cwd: rootDir, + message: "Stopping Core services...", + showOutput: true, + }); + + await executeDockerCommandInteractive("docker compose up -d", { + cwd: rootDir, + message: "Starting Core services with new Trigger.dev configuration...", + showOutput: true, + }); + } catch (error: any) { + throw error; + } } - // Step 11: Restart docker-compose in root - const s7 = spinner(); - s7.start("Restarting Docker containers with new configuration..."); - - try { - await executeDockerCommand("docker compose down && docker compose up -d", rootDir); - s7.stop("Docker containers restarted"); - } catch (error: any) { - s7.stop("L Failed to restart Docker containers"); - throw error; - } - - // Step 12: Show docker login instructions - console.log("\n=3 Docker Registry Login:"); - console.log("Run the following command to login to Docker registry:"); + // Step 13: Show docker login instructions + note("Run the following command to login to Docker registry:", "🐳 Docker Registry Login"); try { // Read env file to get docker registry details - const envContent = await import("fs").then((fs) => fs.promises.readFile(envPath, "utf8")); + const envContent = await import("fs").then((fs) => + fs.promises.readFile(triggerEnvPath, "utf8") + ); const envLines = envContent.split("\n"); const getEnvValue = (key: string) => { @@ -196,26 +251,74 @@ export async function initCommand() { const dockerRegistryUsername = getEnvValue("DOCKER_REGISTRY_USERNAME"); const dockerRegistryPassword = getEnvValue("DOCKER_REGISTRY_PASSWORD"); - console.log( - `\ndocker login ${dockerRegistryUrl} -u ${dockerRegistryUsername} -p ${dockerRegistryPassword}` + log.info( + `docker login ${dockerRegistryUrl} -u ${dockerRegistryUsername} -p ${dockerRegistryPassword}` ); } catch (error) { - console.log("docker login -u -p "); + log.info("docker login -u -p "); } - await confirm({ - message: "Press Enter after completing Docker login...", + const dockerLoginConfirmed = await confirm({ + message: "Have you completed the Docker login successfully?", }); - // Step 13: Final instructions - outro("< Setup Complete!"); - console.log("\n< Your services are now running:"); - console.log('" Core Application: http://localhost:3033'); - console.log('" Trigger.dev: http://localhost:8030'); - console.log('" PostgreSQL: localhost:5432'); - console.log("\n( You can now start developing with Core!"); + if (!dockerLoginConfirmed) { + outro("āŒ Setup cancelled. Please complete Docker login first and run the command again."); + process.exit(1); + } + + // Step 14: Deploy Trigger.dev tasks + note( + "We'll now deploy the trigger tasks to your Trigger.dev instance.", + "šŸš€ Deploying Trigger.dev tasks" + ); + + try { + // Login to trigger.dev CLI + await executeDockerCommandInteractive( + "npx -y trigger.dev@v4-beta login -a http://localhost:8030", + { + cwd: rootDir, + message: "Logging in to Trigger.dev CLI...", + showOutput: true, + } + ); + + // Deploy trigger tasks + await executeDockerCommandInteractive("pnpm trigger:deploy", { + cwd: rootDir, + message: "Deploying Trigger.dev tasks...", + showOutput: true, + }); + + log.success("Trigger.dev tasks deployed successfully!"); + } catch (error: any) { + log.warning("Failed to deploy Trigger.dev tasks:"); + note( + `${error.message}\n\nYou can deploy them manually later with:\n1. npx trigger.dev@v4-beta login -a http://localhost:8030\n2. pnpm trigger:deploy`, + "Manual Deployment" + ); + } + + // Step 15: Final instructions + outro("šŸŽ‰ Setup Complete!"); + note( + [ + "Your services are now running:", + "", + "• Core Application: http://localhost:3033", + "• Trigger.dev: http://localhost:8030", + "• PostgreSQL: localhost:5432", + "", + "You can now start developing with Core!", + "", + "ā„¹ļø When logging in to the Core Application, you can find the login URL in the Docker container logs:", + " docker logs core-app --tail 50", + ].join("\n"), + "šŸš€ Services Running" + ); } catch (error: any) { - outro(`L Setup failed: ${error.message}`); + outro(`āŒ Setup failed: ${error.message}`); process.exit(1); } } diff --git a/packages/core-cli/src/commands/start.ts b/packages/core-cli/src/commands/start.ts new file mode 100644 index 0000000..f1378d8 --- /dev/null +++ b/packages/core-cli/src/commands/start.ts @@ -0,0 +1,72 @@ +import { intro, outro, note, log, confirm } from '@clack/prompts'; +import { isValidCoreRepo } from '../utils/git.js'; +import { executeDockerCommandInteractive } from '../utils/docker-interactive.js'; +import { printCoreBrainLogo } from '../utils/ascii.js'; +import path from 'path'; + +export async function startCommand() { + // Display the CORE brain logo + printCoreBrainLogo(); + + intro('šŸš€ Starting Core Development Environment'); + + // Step 1: Validate repository + if (!isValidCoreRepo()) { + log.warning('This directory is not a Core repository'); + note('The Core repository is required to run the development environment.\nWould you like to clone it in the current directory?', 'šŸ” Repository Not Found'); + + const shouldClone = await confirm({ + message: 'Clone the Core repository here?', + }); + + if (!shouldClone) { + outro('āŒ Setup cancelled. Please navigate to the Core repository or clone it first.'); + process.exit(1); + } + + // Clone the repository + try { + await executeDockerCommandInteractive('git clone https://github.com/redplanethq/core.git .', { + cwd: process.cwd(), + message: 'Cloning Core repository...', + showOutput: true + }); + + log.success('Core repository cloned successfully!'); + note('You can now run "core start" to start the development environment.', 'āœ… Repository Ready'); + outro('šŸŽ‰ Core repository is now available!'); + process.exit(0); + } catch (error: any) { + outro(`āŒ Failed to clone repository: ${error.message}`); + process.exit(1); + } + } + + const rootDir = process.cwd(); + const triggerDir = path.join(rootDir, 'trigger'); + + try { + // Start main services + await executeDockerCommandInteractive('docker compose up -d', { + cwd: rootDir, + message: 'Starting Core services...', + showOutput: true + }); + + // Start trigger services + await executeDockerCommandInteractive('docker compose up -d', { + cwd: triggerDir, + message: 'Starting Trigger.dev services...', + showOutput: true + }); + + // Final success message + outro('šŸŽ‰ Core Development Environment Started!'); + note('• Core Application: http://localhost:3033\n• Trigger.dev: http://localhost:8030\n• PostgreSQL: localhost:5432', '🌐 Your services are now running'); + log.success('Happy coding!'); + + } catch (error: any) { + outro(`āŒ Failed to start services: ${error.message}`); + process.exit(1); + } +} \ No newline at end of file diff --git a/packages/core-cli/src/commands/stop.ts b/packages/core-cli/src/commands/stop.ts new file mode 100644 index 0000000..5fef1d1 --- /dev/null +++ b/packages/core-cli/src/commands/stop.ts @@ -0,0 +1,72 @@ +import { intro, outro, log, confirm, note } from '@clack/prompts'; +import { isValidCoreRepo } from '../utils/git.js'; +import { executeDockerCommandInteractive } from '../utils/docker-interactive.js'; +import { printCoreBrainLogo } from '../utils/ascii.js'; +import path from 'path'; + +export async function stopCommand() { + // Display the CORE brain logo + printCoreBrainLogo(); + + intro('šŸ›‘ Stopping Core Development Environment'); + + // Step 1: Validate repository + if (!isValidCoreRepo()) { + log.warning('This directory is not a Core repository'); + note('The Core repository is required to stop the development environment.\nWould you like to clone it in the current directory?', 'šŸ” Repository Not Found'); + + const shouldClone = await confirm({ + message: 'Clone the Core repository here?', + }); + + if (!shouldClone) { + outro('āŒ Setup cancelled. Please navigate to the Core repository or clone it first.'); + process.exit(1); + } + + // Clone the repository + try { + await executeDockerCommandInteractive('git clone https://github.com/redplanethq/core.git .', { + cwd: process.cwd(), + message: 'Cloning Core repository...', + showOutput: true + }); + + log.success('Core repository cloned successfully!'); + note('You can now run "core stop" to stop the development environment.', 'āœ… Repository Ready'); + outro('šŸŽ‰ Core repository is now available!'); + process.exit(0); + } catch (error: any) { + outro(`āŒ Failed to clone repository: ${error.message}`); + process.exit(1); + } + } + + const rootDir = process.cwd(); + const triggerDir = path.join(rootDir, 'trigger'); + + try { + // Stop trigger services first + await executeDockerCommandInteractive('docker compose down', { + cwd: triggerDir, + message: 'Stopping Trigger.dev services...', + showOutput: true + }); + + // Stop main services + await executeDockerCommandInteractive('docker compose down', { + cwd: rootDir, + message: 'Stopping Core services...', + showOutput: true + }); + + // Final success message + outro('šŸŽ‰ Core Development Environment Stopped!'); + log.success('All services have been stopped.'); + log.info('Run "core start" to start services again.'); + + } catch (error: any) { + outro(`āŒ Failed to stop services: ${error.message}`); + process.exit(1); + } +} \ No newline at end of file diff --git a/packages/core-cli/src/utils/docker-interactive.ts b/packages/core-cli/src/utils/docker-interactive.ts new file mode 100644 index 0000000..e589561 --- /dev/null +++ b/packages/core-cli/src/utils/docker-interactive.ts @@ -0,0 +1,108 @@ +import { spawn, ChildProcess } from 'child_process'; +import { spinner } from '@clack/prompts'; + +export interface DockerCommandOptions { + cwd: string; + message: string; + showOutput?: boolean; +} + +export function executeDockerCommandInteractive( + command: string, + options: DockerCommandOptions +): Promise { + return new Promise((resolve, reject) => { + const s = spinner(); + s.start(options.message); + + // Split command into parts + const parts = command.split(' '); + const cmd = parts[0]; + const args = parts.slice(1); + + if (!cmd) { + reject(new Error('Invalid command')); + return; + } + + const child: ChildProcess = spawn(cmd, args, { + cwd: options.cwd, + stdio: options.showOutput ? ['ignore', 'pipe', 'pipe'] : 'ignore', + detached: false + }); + + let output = ''; + + // Handle stdout + if (child.stdout && options.showOutput) { + child.stdout.on('data', (data: Buffer) => { + const text = data.toString(); + output += text; + + // Update spinner with latest output line + const lines = text.trim().split('\n'); + const lastLine = lines[lines.length - 1]; + if (lastLine && lastLine.trim()) { + s.message(`${options.message}\n${lastLine.trim()}`); + } + }); + } + + // Handle stderr + if (child.stderr && options.showOutput) { + child.stderr.on('data', (data: Buffer) => { + const text = data.toString(); + output += text; + + // Update spinner with error output + const lines = text.trim().split('\n'); + const lastLine = lines[lines.length - 1]; + if (lastLine && lastLine.trim()) { + s.message(`${options.message}\nāŒ ${lastLine.trim()}`); + } + }); + } + + // Handle process exit + child.on('exit', (code: number | null) => { + if (code === 0) { + s.stop(`āœ… ${options.message.replace(/\.\.\.$/, '')} completed`); + resolve(); + } else { + s.stop(`āŒ ${options.message.replace(/\.\.\.$/, '')} failed (exit code: ${code})`); + if (options.showOutput && output) { + console.log('\nOutput:'); + console.log(output); + } + reject(new Error(`Command failed with exit code ${code}`)); + } + }); + + // Handle errors + child.on('error', (error: Error) => { + s.stop(`āŒ ${options.message.replace(/\.\.\.$/, '')} failed`); + reject(error); + }); + + // Handle Ctrl+C + const handleSigint = () => { + s.stop(`ā¹ļø ${options.message.replace(/\.\.\.$/, '')} interrupted`); + child.kill('SIGTERM'); + + // Give the process time to clean up + setTimeout(() => { + if (child.killed === false) { + child.kill('SIGKILL'); + } + process.exit(130); // Standard exit code for SIGINT + }, 5000); + }; + + process.on('SIGINT', handleSigint); + + // Clean up event listener when done + child.on('exit', () => { + process.off('SIGINT', handleSigint); + }); + }); +} \ No newline at end of file diff --git a/packages/core-cli/src/utils/env-checker.ts b/packages/core-cli/src/utils/env-checker.ts new file mode 100644 index 0000000..806db60 --- /dev/null +++ b/packages/core-cli/src/utils/env-checker.ts @@ -0,0 +1,23 @@ +import fs from 'fs/promises'; + +export async function checkEnvValue(filePath: string, key: string): Promise { + try { + const content = await fs.readFile(filePath, 'utf8'); + const lines = content.split('\n'); + const line = lines.find(l => l.startsWith(`${key}=`)); + if (line) { + const value = line.split('=')[1]?.trim(); + return value && value.length > 0 ? value : null; + } + return null; + } catch { + return null; + } +} + +export async function hasTriggerConfig(envPath: string): Promise { + const projectId = await checkEnvValue(envPath, 'TRIGGER_PROJECT_ID'); + const secretKey = await checkEnvValue(envPath, 'TRIGGER_SECRET_KEY'); + + return !!(projectId && secretKey); +} \ No newline at end of file diff --git a/trigger/docker-compose.yaml b/trigger/docker-compose.yaml index f86af26..558edb0 100644 --- a/trigger/docker-compose.yaml +++ b/trigger/docker-compose.yaml @@ -47,7 +47,7 @@ services: MAGIC_LINK_SECRET: ${MAGIC_LINK_SECRET} ENCRYPTION_KEY: ${ENCRYPTION_KEY} MANAGED_WORKER_SECRET: ${MANAGED_WORKER_SECRET} - REDIS_HOST: trigger-redis + REDIS_HOST: host.docker.internal REDIS_PORT: 6379 REDIS_TLS_DISABLED: true APP_LOG_LEVEL: info