mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-11 16:58:28 +00:00
Feat: cli for init, start and stop
This commit is contained in:
parent
f026f09fea
commit
502b26882e
@ -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}>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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
193
packages/core-cli/README.md
Normal 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!** 🎉
|
||||
@ -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",
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
72
packages/core-cli/src/commands/start.ts
Normal file
72
packages/core-cli/src/commands/start.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
72
packages/core-cli/src/commands/stop.ts
Normal file
72
packages/core-cli/src/commands/stop.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
108
packages/core-cli/src/utils/docker-interactive.ts
Normal file
108
packages/core-cli/src/utils/docker-interactive.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
23
packages/core-cli/src/utils/env-checker.ts
Normal file
23
packages/core-cli/src/utils/env-checker.ts
Normal 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);
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user