Feat: cli for init, start and stop

This commit is contained in:
Harshith Mullapudi 2025-07-18 22:24:27 +05:30
parent f026f09fea
commit 502b26882e
12 changed files with 778 additions and 186 deletions

View File

@ -83,9 +83,9 @@ export function LogTextCollapse({
isLong ? "max-h-16 overflow-hidden" : "",
)}
style={{ lineHeight: "1.5" }}
>
{displayText}
</p>
dangerouslySetInnerHTML={{ __html: text }}
/>
{isLong && (
<>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>

View File

@ -58,44 +58,52 @@ export default function LogsActivity() {
]}
/>
<div className="flex h-[calc(100vh_-_56px)] flex-col space-y-6 p-4 px-5">
{logs.length > 0 && (
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
/>
)}
{isInitialLoad ? (
<>
<LoaderCircle className="text-primary h-4 w-4 animate-spin" />{" "}
</>
) : (
<>
{logs.length > 0 && (
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
/>
)}
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Activity className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">
No activity logs found
</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No activity ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
/>
)}
</div>
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Activity className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">
No activity logs found
</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No activity ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
/>
)}
</div>
</>
)}
</div>
</div>
);

View File

@ -26,18 +26,6 @@ export default function LogsAll() {
status: selectedStatus,
});
if (isInitialLoad) {
return (
<AppContainer>
<PageContainer>
<div className="flex h-[calc(100vh_-_16px)] items-center justify-center">
<LoaderCircle className="text-primary h-4 w-4 animate-spin" />
</div>
</PageContainer>
</AppContainer>
);
}
return (
<>
<PageHeader
@ -58,43 +46,53 @@ export default function LogsAll() {
]}
/>
<div className="h-[calc(100vh_-_56px)] space-y-6 p-4 px-5">
{/* Filters */}
{logs.length > 0 && (
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
/>
{isInitialLoad ? (
<>
<LoaderCircle className="text-primary h-4 w-4 animate-spin" />{" "}
</>
) : (
<>
{" "}
{/* Filters */}
{logs.length > 0 && (
<LogsFilters
availableSources={availableSources}
selectedSource={selectedSource}
selectedStatus={selectedStatus}
onSourceChange={setSelectedSource}
onStatusChange={setSelectedStatus}
/>
)}
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Database className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">
No logs found
</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
/>
)}
</div>
</>
)}
{/* Logs List */}
<div className="space-y-4">
{logs.length === 0 ? (
<Card>
<CardContent className="bg-background-2 flex items-center justify-center py-16">
<div className="text-center">
<Database className="text-muted-foreground mx-auto mb-4 h-12 w-12" />
<h3 className="mb-2 text-lg font-semibold">No logs found</h3>
<p className="text-muted-foreground">
{selectedSource || selectedStatus
? "Try adjusting your filters to see more results."
: "No ingestion logs are available yet."}
</p>
</div>
</CardContent>
</Card>
) : (
<VirtualLogsList
logs={logs}
hasMore={hasMore}
loadMore={loadMore}
isLoading={isLoading}
height={600}
/>
)}
</div>
</div>
</>
);

193
packages/core-cli/README.md Normal file
View File

@ -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!** 🎉

View File

@ -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",

View File

@ -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);

View File

@ -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 <REGISTRY_URL> -u <USERNAME> -p <PASSWORD>");
log.info("docker login <REGISTRY_URL> -u <USERNAME> -p <PASSWORD>");
}
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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<void> {
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);
});
});
}

View File

@ -0,0 +1,23 @@
import fs from 'fs/promises';
export async function checkEnvValue(filePath: string, key: string): Promise<string | null> {
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<boolean> {
const projectId = await checkEnvValue(envPath, 'TRIGGER_PROJECT_ID');
const secretKey = await checkEnvValue(envPath, 'TRIGGER_SECRET_KEY');
return !!(projectId && secretKey);
}

View File

@ -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