Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

372 lines
11 KiB
TypeScript

#!/usr/bin/env bun
/**
* Pre-Development Setup
*
* Sets up the development environment:
* - Kills any processes on port 3000
* - Starts PostgreSQL, Redis, MinIO via Docker Compose
* - Runs database migrations and seeds data
*/
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
import { join } from "node:path";
// @ts-expect-error - bun global is available in bun runtime
import { $ } from "bun";
const POSTGRES_CONTAINER = "feed-postgres";
const REDIS_CONTAINER = "feed-redis";
const MINIO_CONTAINER = "feed-minio";
const STEWARD_CONTAINER = "feed-steward";
type DockerService = "postgres" | "redis" | "minio" | "steward";
// Detect docker compose command (docker compose vs docker-compose)
let useDockerComposePlugin = false;
const dockerComposeCheck = await $`docker compose version`.quiet().nothrow();
if (dockerComposeCheck.exitCode === 0) {
useDockerComposePlugin = true;
} else {
const dockerComposeStandalone = await $`docker-compose version`
.quiet()
.nothrow();
if (dockerComposeStandalone.exitCode !== 0) {
console.error(
'❌ Neither "docker compose" nor "docker-compose" is available',
);
process.exit(1);
}
}
async function dockerComposeUp(service: DockerService) {
if (useDockerComposePlugin) {
return $`docker compose up -d ${service}`;
} else {
return $`docker-compose up -d ${service}`;
}
}
async function killPort(port: number): Promise<number> {
const pids = await $`lsof -t -i:${port}`.quiet().nothrow().text();
const pidList = pids.trim().split("\n").filter(Boolean);
if (pidList.length === 0) return 0;
for (const pid of pidList) {
await $`kill -9 ${pid}`.quiet().nothrow();
}
return pidList.length;
}
function ensureEnvDefaults(
envPath: string,
defaults: Record<string, string>,
): void {
let envContent = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
for (const [key, value] of Object.entries(defaults)) {
const regex = new RegExp(`^${key}=.*$`, "m");
if (!regex.test(envContent)) {
envContent += `\n${key}=${value}`;
}
}
writeFileSync(envPath, envContent);
}
// Load .env file into process.env
const envPath = join(process.cwd(), ".env");
if (existsSync(envPath)) {
const envContent = readFileSync(envPath, "utf-8");
for (const line of envContent.split("\n")) {
const trimmed = line.trim();
if (trimmed && !trimmed.startsWith("#")) {
const [key, ...valueParts] = trimmed.split("=");
if (key && valueParts.length > 0) {
const value = valueParts.join("=").replace(/^["']|["']$/g, "");
if (!process.env[key]) {
process.env[key] = value;
}
}
}
}
}
console.info("[Script] Setting up development environment...");
console.info("=".repeat(60));
// 1. Kill any processes on port 3000
console.info("[Script] Checking for processes on port 3000...");
const killedCount = await killPort(3000);
if (killedCount > 0) {
console.info(`[Script] ✅ Killed ${killedCount} process(es) on port 3000`);
} else {
console.info("[Script] ✅ Port 3000 is free");
}
// 2. Clean up stale Next.js lock files
const nextLockPath = join(process.cwd(), "apps", "web", ".next", "dev", "lock");
try {
if (existsSync(nextLockPath)) {
console.info("[Script] Cleaning up Next.js lock file...");
unlinkSync(nextLockPath);
console.info("[Script] ✅ Next.js lock file removed");
}
} catch (_error) {
console.warn("[Script] Could not remove Next.js lock file (may not exist)");
}
await $`pkill -f "next dev" || true`.quiet().nothrow();
await $`pkill -f "next-server" || true`.quiet().nothrow();
// 3. Create .env from .env.example if missing
if (!existsSync(envPath)) {
console.info("Creating .env file...");
const envExamplePath = join(process.cwd(), ".env.example");
let envContent = "";
if (existsSync(envExamplePath)) {
envContent = readFileSync(envExamplePath, "utf-8");
envContent = envContent.replace(
/DATABASE_URL=.*/,
'DATABASE_URL="postgresql://feed:feed_dev_password@localhost:5433/feed"',
);
envContent = envContent.replace(
/REDIS_URL=.*/,
'REDIS_URL="redis://localhost:6380"',
);
} else {
envContent = `DATABASE_URL="postgresql://feed:feed_dev_password@localhost:5433/feed"
REDIS_URL="redis://localhost:6380"
STEWARD_JWT_SECRET="dev-jwt-secret-change-in-prod"
`;
}
writeFileSync(envPath, envContent);
console.info("✅ .env created from template");
}
ensureEnvDefaults(envPath, {
NEXT_PUBLIC_PERP_SETTLEMENT_MODE: "simulation",
PERP_SETTLEMENT_MODE: "simulation",
});
// 4. Check Docker
await $`docker --version`.quiet();
await $`docker info`.quiet().catch(() => {
console.error("❌ Docker is not running");
process.exit(1);
});
console.info("✅ Docker is running");
// 5. Start PostgreSQL
const postgresRunning =
await $`docker ps --filter name=${POSTGRES_CONTAINER} --format "{{.Names}}"`
.quiet()
.text();
if (postgresRunning.trim() !== POSTGRES_CONTAINER) {
console.info("Starting PostgreSQL...");
await dockerComposeUp("postgres");
let attempts = 0;
while (attempts < 30) {
const health =
await $`docker inspect --format='{{.State.Health.Status}}' ${POSTGRES_CONTAINER}`
.quiet()
.text()
.catch(() => "");
if (health.trim() === "healthy") {
console.info("✅ PostgreSQL is ready");
break;
}
await new Promise((resolve) => setTimeout(resolve, 1000));
attempts++;
}
if (attempts === 30) {
console.error("❌ PostgreSQL health check timeout");
process.exit(1);
}
} else {
console.info("✅ PostgreSQL is running");
}
// 6. Start Redis
const redisRunning =
await $`docker ps --filter name=${REDIS_CONTAINER} --format "{{.Names}}"`
.quiet()
.text();
if (redisRunning.trim() !== REDIS_CONTAINER) {
console.info("Starting Redis...");
await dockerComposeUp("redis")
.then(async () => {
await new Promise((resolve) => setTimeout(resolve, 2000));
console.info("✅ Redis started");
})
.catch(() => {
console.warn("⚠️ Redis start failed (optional, continuing)");
});
} else {
console.info("✅ Redis is running");
}
// 7. Start MinIO
const minioRunning =
await $`docker ps --filter name=${MINIO_CONTAINER} --format "{{.Names}}"`
.quiet()
.text();
if (minioRunning.trim() !== MINIO_CONTAINER) {
console.info("Starting MinIO...");
await dockerComposeUp("minio")
.then(async () => {
await new Promise((resolve) => setTimeout(resolve, 2000));
console.info("✅ MinIO started");
})
.catch(() => {
console.warn("⚠️ MinIO start failed (optional, continuing)");
});
} else {
console.info("✅ MinIO is running");
}
const LOCAL_DATABASE_URL =
"postgresql://feed:feed_dev_password@localhost:5433/feed";
process.env.DATABASE_URL = LOCAL_DATABASE_URL;
process.env.DIRECT_DATABASE_URL = LOCAL_DATABASE_URL;
async function runMigrations(): Promise<void> {
const MIGRATION_TIMEOUT_MS = 120_000;
console.info("Running database migrations (drizzle-kit push --force)...");
const migrationPromise = (async () => {
const result =
await $`DATABASE_URL=${LOCAL_DATABASE_URL} DIRECT_DATABASE_URL=${LOCAL_DATABASE_URL} bun run db:push -- --force`
.cwd("packages/db")
.nothrow();
if (result.exitCode !== 0) {
throw new Error(
`drizzle-kit push failed with exit code ${result.exitCode}`,
);
}
console.info("✅ Migrations completed");
})();
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
reject(
new Error(
`Migration timed out after ${MIGRATION_TIMEOUT_MS / 1000} seconds`,
),
);
}, MIGRATION_TIMEOUT_MS);
});
await Promise.race([migrationPromise, timeoutPromise]);
}
let userCount = 0;
let needsMigrations = false;
let needsSeed = false;
try {
const countResult =
await $`docker exec feed-postgres psql -U feed -d feed -t -c "SELECT count(*) FROM \"User\";"`.quiet();
userCount = parseInt(countResult.text().trim(), 10);
if (Number.isNaN(userCount)) userCount = 0;
console.info(`✅ Database connected (${userCount} users)`);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (
errorMessage.includes("does not exist") ||
errorMessage.includes("relation")
) {
console.info("Database tables not found, running migrations...");
needsMigrations = true;
needsSeed = true;
} else {
console.info("Database not ready, running migrations...");
needsMigrations = true;
}
}
if (needsMigrations) {
await runMigrations();
}
if (needsSeed || userCount === 0) {
console.info("Running database seed...");
await $`DATABASE_URL=${LOCAL_DATABASE_URL} DIRECT_DATABASE_URL=${LOCAL_DATABASE_URL} bun run db:seed`;
console.info("✅ Database seeded");
}
// 9. Start Steward auth service (optional — requires ../steward sibling directory)
const stewardSiblingExists = existsSync(
join(process.cwd(), "..", "steward", "Dockerfile"),
);
if (!stewardSiblingExists) {
console.warn(
"⚠️ ../steward not found — Steward auth service will not start.",
);
console.warn(
" Clone https://github.com/Steward-Fi/steward as a sibling to enable auth.",
);
} else {
const stewardRunning =
await $`docker ps --filter name=${STEWARD_CONTAINER} --format "{{.Names}}"`
.quiet()
.text();
if (stewardRunning.trim() !== STEWARD_CONTAINER) {
console.info("Starting Steward auth service...");
await dockerComposeUp("steward")
.then(() =>
console.info("✅ Steward container started, waiting for health..."),
)
.catch(() => console.warn("⚠️ Steward start failed (auth may not work)"));
// Wait up to 60s for Steward's /health endpoint
let stewardReady = false;
for (let i = 0; i < 30; i++) {
const ok = await fetch("http://localhost:3200/health")
.then((r) => r.ok)
.catch(() => false);
if (ok) {
stewardReady = true;
break;
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
if (stewardReady) {
console.info("✅ Steward is ready at http://localhost:3200");
console.info(
' Run "bun run steward:init" once to provision the feed tenant.',
);
} else {
console.warn(
"⚠️ Steward did not become healthy within 60s — check logs with:",
);
console.warn(` docker logs ${STEWARD_CONTAINER}`);
}
} else {
console.info("✅ Steward is running at http://localhost:3200");
}
}
console.info("");
console.info("=".repeat(60));
console.info("✅ Development environment ready!");
console.info("");
console.info("Services:");
console.info(" PostgreSQL: localhost:5433");
console.info(" Redis: localhost:6380");
console.info(" MinIO: http://localhost:9000 (console: :9001)");
console.info(" Steward: http://localhost:3200");
console.info("");
console.info("App Routes:");
console.info(" Main: http://localhost:3000");
console.info("=".repeat(60));
process.exit(0);