mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
feat(den): simplify local dev and sandbox dashboard (#1181)
Run Den controller, web, and worker proxy through a root dev:den-local workflow backed by Turbo and auto-managed local MySQL. Restore the background agents tab to show real sandboxes, add manual sandbox creation, stop auto-launching sandboxes on signup, and move Billing lower in the sidebar. Co-authored-by: src-opn <src-opn@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "npm run build:den-db && OPENWORK_DEV_MODE=1 tsx watch src/index.ts",
|
||||
"dev:local": "sh -lc 'pnpm run build:den-db && OPENWORK_DEV_MODE=1 PORT=${DEN_CONTROLLER_PORT:-8788} WORKER_PROXY_PORT=${DEN_WORKER_PROXY_PORT:-8789} tsx watch src/index.ts'",
|
||||
"build": "npm run build:den-db && tsc -p tsconfig.json",
|
||||
"build:den-db": "pnpm --filter @openwork-ee/den-db build",
|
||||
"start": "node dist/index.js",
|
||||
|
||||
@@ -1949,7 +1949,7 @@ export function DenFlowProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
|
||||
onboardingAutoLaunchKeyRef.current = autoLaunchKey;
|
||||
void launchWorker({ source: "signup_auto", workerNameOverride: onboardingIntent?.workerName ?? DEFAULT_WORKER_NAME });
|
||||
markOnboardingComplete();
|
||||
}, [billingSummary?.featureGateEnabled, billingSummary?.hasActivePlan, launchBusy, onboardingIntent?.workerName, onboardingPending, ownedWorkerCount, user?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { getWorkerStatusMeta } from "../../../../_lib/den-flow";
|
||||
import { useDenFlow } from "../../../../_providers/den-flow-provider";
|
||||
import { getSharedSetupsRoute } from "../../../../_lib/den-org";
|
||||
import { useOrgDashboard } from "../_providers/org-dashboard-provider";
|
||||
|
||||
@@ -12,13 +15,35 @@ const EXAMPLE_AGENTS = [
|
||||
},
|
||||
{
|
||||
name: "Renewal reminder agent",
|
||||
status: "Paused",
|
||||
status: "Active",
|
||||
detail: "Source: Customer success setup",
|
||||
},
|
||||
];
|
||||
|
||||
function statusClass(bucket: ReturnType<typeof getWorkerStatusMeta>["bucket"]) {
|
||||
switch (bucket) {
|
||||
case "ready":
|
||||
return "is-success";
|
||||
case "starting":
|
||||
return "is-neutral";
|
||||
case "attention":
|
||||
return "is-warning";
|
||||
default:
|
||||
return "is-neutral";
|
||||
}
|
||||
}
|
||||
|
||||
export function BackgroundAgentsScreen() {
|
||||
const router = useRouter();
|
||||
const { orgSlug } = useOrgDashboard();
|
||||
const { workers, workersBusy, workersError, launchBusy, launchWorker } = useDenFlow();
|
||||
|
||||
async function handleAddSandbox() {
|
||||
const result = await launchWorker({ source: "manual" });
|
||||
if (result === "checkout") {
|
||||
router.push("/checkout");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="den-page flex max-w-6xl flex-col gap-6 py-4 md:py-8">
|
||||
@@ -35,9 +60,19 @@ export function BackgroundAgentsScreen() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Link href={getSharedSetupsRoute(orgSlug)} className="den-button-secondary">
|
||||
Open shared setups
|
||||
</Link>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="den-button-primary"
|
||||
onClick={() => void handleAddSandbox()}
|
||||
disabled={launchBusy}
|
||||
>
|
||||
{launchBusy ? "Adding..." : "+ Add sandbox"}
|
||||
</button>
|
||||
<Link href={getSharedSetupsRoute(orgSlug)} className="den-button-secondary">
|
||||
Open shared setups
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
@@ -55,23 +90,44 @@ export function BackgroundAgentsScreen() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{workersError ? <div className="den-notice is-error">{workersError}</div> : null}
|
||||
|
||||
<div className="den-list-shell">
|
||||
<div className="px-5 py-5">
|
||||
<p className="den-eyebrow">Example workflows</p>
|
||||
<p className="den-eyebrow">{workers.length > 0 ? "Current sandboxes" : "Example workflows"}</p>
|
||||
<h2 className="mt-2 text-2xl font-semibold tracking-tight text-[var(--dls-text-primary)]">
|
||||
Background workflows
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{EXAMPLE_AGENTS.map((agent) => (
|
||||
<article key={agent.name} className="den-list-row">
|
||||
<div className="grid gap-1">
|
||||
<h3 className="text-base font-semibold text-[var(--dls-text-primary)]">{agent.name}</h3>
|
||||
<p className="text-sm text-[var(--dls-text-secondary)]">{agent.detail}</p>
|
||||
</div>
|
||||
<span className="den-status-pill is-neutral">{agent.status}</span>
|
||||
</article>
|
||||
))}
|
||||
{workersBusy ? (
|
||||
<div className="den-list-row text-sm text-[var(--dls-text-secondary)]">Loading sandboxes...</div>
|
||||
) : workers.length > 0 ? (
|
||||
workers.map((worker) => {
|
||||
const meta = getWorkerStatusMeta(worker.status);
|
||||
return (
|
||||
<article key={worker.workerId} className="den-list-row">
|
||||
<div className="grid gap-1">
|
||||
<h3 className="text-base font-semibold text-[var(--dls-text-primary)]">{worker.workerName}</h3>
|
||||
<p className="text-sm text-[var(--dls-text-secondary)]">
|
||||
Source: {worker.provider ? `${worker.provider} sandbox` : "Cloud sandbox"}
|
||||
</p>
|
||||
</div>
|
||||
<span className={`den-status-pill ${statusClass(meta.bucket)}`}>{meta.label}</span>
|
||||
</article>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
EXAMPLE_AGENTS.map((agent) => (
|
||||
<article key={agent.name} className="den-list-row">
|
||||
<div className="grid gap-1">
|
||||
<h3 className="text-base font-semibold text-[var(--dls-text-primary)]">{agent.name}</h3>
|
||||
<p className="text-sm text-[var(--dls-text-secondary)]">{agent.detail}</p>
|
||||
</div>
|
||||
<span className="den-status-pill is-neutral">{agent.status}</span>
|
||||
</article>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -66,8 +66,8 @@ export function OrgDashboardShell({ children }: { children: ReactNode }) {
|
||||
{ href: activeOrg ? getMembersRoute(activeOrg.slug) : "#", label: "Members" },
|
||||
{ href: activeOrg ? getBackgroundAgentsRoute(activeOrg.slug) : "#", label: "Background agents", badge: "Alpha" },
|
||||
{ href: activeOrg ? getCustomLlmProvidersRoute(activeOrg.slug) : "#", label: "Custom LLM providers", badge: "Soon" },
|
||||
{ href: "/checkout", label: "Billing" },
|
||||
];
|
||||
const billingNavItem = { href: "/checkout", label: "Billing" };
|
||||
const dashboardHref = activeOrg ? getOrgDashboardRoute(activeOrg.slug) : "#";
|
||||
|
||||
return (
|
||||
@@ -192,6 +192,19 @@ export function OrgDashboardShell({ children }: { children: ReactNode }) {
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="pt-2">
|
||||
<Link
|
||||
href={billingNavItem.href}
|
||||
className={`flex items-center justify-between gap-3 rounded-[1.2rem] px-4 py-3 text-sm transition-colors ${
|
||||
pathname === billingNavItem.href
|
||||
? "bg-[#f0f1f3] font-medium text-[var(--dls-text-primary)]"
|
||||
: "text-[var(--dls-text-secondary)] hover:bg-[#f6f7f8] hover:text-[var(--dls-text-primary)]"
|
||||
}`}
|
||||
>
|
||||
<span>{billingNavItem.label}</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-auto grid gap-3">
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "OPENWORK_DEV_MODE=1 NEXT_PUBLIC_POSTHOG_KEY= NEXT_PUBLIC_POSTHOG_API_KEY= next dev --hostname 0.0.0.0 --port 3005",
|
||||
"dev:local": "sh -lc 'OPENWORK_DEV_MODE=1 NEXT_PUBLIC_POSTHOG_KEY= NEXT_PUBLIC_POSTHOG_API_KEY= next dev --hostname 0.0.0.0 --port ${DEN_WEB_PORT:-3005}'",
|
||||
"build": "next build",
|
||||
"start": "next start --hostname 0.0.0.0 --port 3005",
|
||||
"lint": "next lint"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "npm run build:den-db && OPENWORK_DEV_MODE=1 tsx watch src/server.ts",
|
||||
"dev:local": "sh -lc 'pnpm run build:den-db && OPENWORK_DEV_MODE=1 PORT=${DEN_WORKER_PROXY_PORT:-8789} tsx watch src/server.ts'",
|
||||
"build": "npm run build:den-db && tsc -p tsconfig.json",
|
||||
"build:den-db": "pnpm --filter @openwork-ee/den-db build",
|
||||
"start": "node dist/server.js"
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"dev:ui": "OPENWORK_DEV_MODE=1 pnpm --filter @openwork/app dev",
|
||||
"dev:story": "OPENWORK_DEV_MODE=1 pnpm --filter @openwork/story-book dev",
|
||||
"dev:web": "OPENWORK_DEV_MODE=1 pnpm --filter @openwork-ee/den-web dev",
|
||||
"dev:web-local": "OPENWORK_DEV_MODE=1 bash scripts/dev-web-local.sh",
|
||||
"dev:den-local": "OPENWORK_DEV_MODE=1 bash scripts/dev-den-local.sh",
|
||||
"dev:web-local": "pnpm dev:den-local",
|
||||
"dev:den-local": "node scripts/dev-local.mjs",
|
||||
"dev:den-docker": "bash packaging/docker/den-dev-up.sh",
|
||||
"dev:headless-web": "OPENWORK_DEV_MODE=1 bun scripts/dev-headless-web.ts",
|
||||
"build": "node scripts/build.mjs",
|
||||
@@ -39,6 +39,9 @@
|
||||
"release:ship:watch": "node scripts/release/ship.mjs --watch",
|
||||
"tauri": "pnpm --filter @openwork/desktop exec tauri"
|
||||
},
|
||||
"devDependencies": {
|
||||
"turbo": "^2.5.5"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@whiskeysockets/baileys",
|
||||
|
||||
67
pnpm-lock.yaml
generated
67
pnpm-lock.yaml
generated
@@ -11,7 +11,11 @@ patchedDependencies:
|
||||
|
||||
importers:
|
||||
|
||||
.: {}
|
||||
.:
|
||||
devDependencies:
|
||||
turbo:
|
||||
specifier: ^2.5.5
|
||||
version: 2.8.20
|
||||
|
||||
apps/app:
|
||||
dependencies:
|
||||
@@ -2724,6 +2728,36 @@ packages:
|
||||
'@tokenizer/token@0.3.0':
|
||||
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
|
||||
|
||||
'@turbo/darwin-64@2.8.20':
|
||||
resolution: {integrity: sha512-FQ9EX1xMU5nbwjxXxM3yU88AQQ6Sqc6S44exPRroMcx9XZHqqppl5ymJF0Ig/z3nvQNwDmz1Gsnvxubo+nXWjQ==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@turbo/darwin-arm64@2.8.20':
|
||||
resolution: {integrity: sha512-Gpyh9ATFGThD6/s9L95YWY54cizg/VRWl2B67h0yofG8BpHf67DFAh9nuJVKG7bY0+SBJDAo5cMur+wOl9YOYw==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@turbo/linux-64@2.8.20':
|
||||
resolution: {integrity: sha512-p2QxWUYyYUgUFG0b0kR+pPi8t7c9uaVlRtjTTI1AbCvVqkpjUfCcReBn6DgG/Hu8xrWdKLuyQFaLYFzQskZbcA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@turbo/linux-arm64@2.8.20':
|
||||
resolution: {integrity: sha512-Gn5yjlZGLRZWarLWqdQzv0wMqyBNIdq1QLi48F1oY5Lo9kiohuf7BPQWtWxeNVS2NgJ1+nb/DzK1JduYC4AWOA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@turbo/windows-64@2.8.20':
|
||||
resolution: {integrity: sha512-vyaDpYk/8T6Qz5V/X+ihKvKFEZFUoC0oxYpC1sZanK6gaESJlmV3cMRT3Qhcg4D2VxvtC2Jjs9IRkrZGL+exLw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@turbo/windows-arm64@2.8.20':
|
||||
resolution: {integrity: sha512-voicVULvUV5yaGXo0Iue13BcHGYW3u0VgqSbfQwBaHbpj1zLjYV4KIe+7fYIo6DO8FVUJzxFps3ODCQG/Wy2Qw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@@ -4555,6 +4589,10 @@ packages:
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
turbo@2.8.20:
|
||||
resolution: {integrity: sha512-Rb4qk5YT8RUwwdXtkLpkVhNEe/lor6+WV7S5tTlLpxSz6MjV5Qi8jGNn4gS6NAvrYGA/rNrE6YUQM85sCZUDbQ==}
|
||||
hasBin: true
|
||||
|
||||
type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -7258,6 +7296,24 @@ snapshots:
|
||||
|
||||
'@tokenizer/token@0.3.0': {}
|
||||
|
||||
'@turbo/darwin-64@2.8.20':
|
||||
optional: true
|
||||
|
||||
'@turbo/darwin-arm64@2.8.20':
|
||||
optional: true
|
||||
|
||||
'@turbo/linux-64@2.8.20':
|
||||
optional: true
|
||||
|
||||
'@turbo/linux-arm64@2.8.20':
|
||||
optional: true
|
||||
|
||||
'@turbo/windows-64@2.8.20':
|
||||
optional: true
|
||||
|
||||
'@turbo/windows-arm64@2.8.20':
|
||||
optional: true
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.6
|
||||
@@ -9077,6 +9133,15 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
turbo@2.8.20:
|
||||
optionalDependencies:
|
||||
'@turbo/darwin-64': 2.8.20
|
||||
'@turbo/darwin-arm64': 2.8.20
|
||||
'@turbo/linux-64': 2.8.20
|
||||
'@turbo/linux-arm64': 2.8.20
|
||||
'@turbo/windows-64': 2.8.20
|
||||
'@turbo/windows-arm64': 2.8.20
|
||||
|
||||
type-is@1.6.18:
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
|
||||
222
scripts/dev-local.mjs
Normal file
222
scripts/dev-local.mjs
Normal file
@@ -0,0 +1,222 @@
|
||||
import { spawn } from "node:child_process"
|
||||
import net from "node:net"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import process from "node:process"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const rootDir = path.resolve(__dirname, "..")
|
||||
const composeFile = path.join(rootDir, "packaging", "docker", "docker-compose.web-local.yml")
|
||||
const composeProject = "openwork-den-local"
|
||||
|
||||
const controllerPort = process.env.DEN_CONTROLLER_PORT?.trim() || "8788"
|
||||
const workerProxyPort = process.env.DEN_WORKER_PROXY_PORT?.trim() || "8789"
|
||||
const webPort = process.env.DEN_WEB_PORT?.trim() || "3005"
|
||||
const databaseUrl = process.env.DATABASE_URL?.trim() || "mysql://root:password@127.0.0.1:3306/openwork_den"
|
||||
|
||||
function detectWebOrigins() {
|
||||
const origins = new Set([
|
||||
`http://localhost:${webPort}`,
|
||||
`http://127.0.0.1:${webPort}`,
|
||||
])
|
||||
|
||||
for (const entries of Object.values(os.networkInterfaces())) {
|
||||
for (const entry of entries || []) {
|
||||
if (!entry || entry.internal || entry.family !== "IPv4") {
|
||||
continue
|
||||
}
|
||||
|
||||
origins.add(`http://${entry.address}:${webPort}`)
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(origins).join(",")
|
||||
}
|
||||
|
||||
function parseDatabaseEndpoint(value) {
|
||||
const parsed = new URL(value)
|
||||
return {
|
||||
host: parsed.hostname,
|
||||
port: Number(parsed.port || "3306"),
|
||||
}
|
||||
}
|
||||
|
||||
function canReachMysql(host, port) {
|
||||
return new Promise((resolve) => {
|
||||
const socket = net.createConnection({ host, port })
|
||||
|
||||
const finalize = (result) => {
|
||||
socket.destroy()
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
socket.setTimeout(1500)
|
||||
socket.once("connect", () => finalize(true))
|
||||
socket.once("error", () => finalize(false))
|
||||
socket.once("timeout", () => finalize(false))
|
||||
})
|
||||
}
|
||||
|
||||
function canListenOnPort(port) {
|
||||
return new Promise((resolve) => {
|
||||
const server = net.createServer()
|
||||
|
||||
const finalize = (result) => {
|
||||
server.close(() => resolve(result))
|
||||
}
|
||||
|
||||
server.once("error", () => resolve(false))
|
||||
server.once("listening", () => finalize(true))
|
||||
server.listen(port, "0.0.0.0")
|
||||
})
|
||||
}
|
||||
|
||||
function run(command, args, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
cwd: rootDir,
|
||||
stdio: "inherit",
|
||||
...options,
|
||||
})
|
||||
|
||||
child.once("error", reject)
|
||||
child.once("exit", (code, signal) => {
|
||||
if (code === 0) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
const detail = signal ? `signal ${signal}` : `exit code ${code ?? 1}`
|
||||
reject(new Error(`${command} ${args.join(" ")} failed with ${detail}`))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
let startedMysql = false
|
||||
let turboChild = null
|
||||
let cleaningUp = false
|
||||
|
||||
function stopTurboChild() {
|
||||
if (!turboChild || turboChild.exitCode !== null) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
turboChild.once("exit", resolve)
|
||||
|
||||
try {
|
||||
if (process.platform !== "win32") {
|
||||
process.kill(-turboChild.pid, "SIGINT")
|
||||
} else {
|
||||
turboChild.kill("SIGINT")
|
||||
}
|
||||
} catch {
|
||||
turboChild.kill("SIGINT")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function cleanup(exitCode = 0) {
|
||||
if (cleaningUp) {
|
||||
return
|
||||
}
|
||||
|
||||
cleaningUp = true
|
||||
|
||||
await stopTurboChild()
|
||||
|
||||
if (startedMysql) {
|
||||
await run("docker", ["compose", "-p", composeProject, "-f", composeFile, "down"], {
|
||||
stdio: "inherit",
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
process.exit(exitCode)
|
||||
}
|
||||
|
||||
for (const signal of ["SIGINT", "SIGTERM"]) {
|
||||
process.on(signal, () => {
|
||||
void cleanup(0)
|
||||
})
|
||||
}
|
||||
|
||||
async function main() {
|
||||
for (const [name, port] of [["den-web", webPort], ["den-controller", controllerPort], ["den-worker-proxy", workerProxyPort]]) {
|
||||
const available = await canListenOnPort(Number(port))
|
||||
if (!available) {
|
||||
throw new Error(`${name} local port ${port} is already in use. Stop the existing process or rerun with a different port env override.`)
|
||||
}
|
||||
}
|
||||
|
||||
const { host, port } = parseDatabaseEndpoint(databaseUrl)
|
||||
const mysqlAvailable = await canReachMysql(host, port)
|
||||
|
||||
if (!mysqlAvailable) {
|
||||
if (!(host === "127.0.0.1" || host === "localhost")) {
|
||||
throw new Error(`MySQL at ${host}:${port} is not reachable, and auto-start only supports localhost`)
|
||||
}
|
||||
|
||||
console.log(`[den] MySQL not reachable at ${host}:${port}; starting Docker MySQL...`)
|
||||
await run("docker", ["compose", "-p", composeProject, "-f", composeFile, "up", "-d", "--wait", "mysql"])
|
||||
startedMysql = true
|
||||
} else {
|
||||
console.log(`[den] Using existing MySQL at ${host}:${port}`)
|
||||
}
|
||||
|
||||
console.log("[den] Syncing Den schema...")
|
||||
await run("bash", ["-lc", "pnpm --filter @openwork-ee/den-db build && pnpm --filter @openwork-ee/den-db exec node --import tsx ./node_modules/drizzle-kit/bin.cjs push --config drizzle.config.ts --force"], {
|
||||
env: {
|
||||
...process.env,
|
||||
DATABASE_URL: databaseUrl,
|
||||
},
|
||||
})
|
||||
|
||||
const webOrigins = detectWebOrigins()
|
||||
console.log(`[den] Allowed local web origins: ${webOrigins}`)
|
||||
|
||||
turboChild = spawn(
|
||||
"pnpm",
|
||||
[
|
||||
"exec",
|
||||
"turbo",
|
||||
"run",
|
||||
"dev:local",
|
||||
"--output-logs=full",
|
||||
"--filter=@openwork-ee/den-controller",
|
||||
"--filter=@openwork-ee/den-worker-proxy",
|
||||
"--filter=@openwork-ee/den-web",
|
||||
],
|
||||
{
|
||||
cwd: rootDir,
|
||||
stdio: "inherit",
|
||||
detached: process.platform !== "win32",
|
||||
env: {
|
||||
...process.env,
|
||||
OPENWORK_DEV_MODE: process.env.OPENWORK_DEV_MODE?.trim() || "1",
|
||||
DATABASE_URL: databaseUrl,
|
||||
BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET?.trim() || "local-dev-secret-not-for-production-use!!",
|
||||
BETTER_AUTH_URL: process.env.BETTER_AUTH_URL?.trim() || `http://localhost:${webPort}`,
|
||||
DEN_BETTER_AUTH_TRUSTED_ORIGINS: process.env.DEN_BETTER_AUTH_TRUSTED_ORIGINS?.trim() || webOrigins,
|
||||
CORS_ORIGINS: process.env.CORS_ORIGINS?.trim() || webOrigins,
|
||||
DEN_CONTROLLER_PORT: controllerPort,
|
||||
DEN_WORKER_PROXY_PORT: workerProxyPort,
|
||||
DEN_WEB_PORT: webPort,
|
||||
DEN_API_BASE: process.env.DEN_API_BASE?.trim() || `http://127.0.0.1:${controllerPort}`,
|
||||
DEN_AUTH_ORIGIN: process.env.DEN_AUTH_ORIGIN?.trim() || `http://localhost:${webPort}`,
|
||||
DEN_AUTH_FALLBACK_BASE: process.env.DEN_AUTH_FALLBACK_BASE?.trim() || `http://127.0.0.1:${controllerPort}`,
|
||||
PROVISIONER_MODE: process.env.PROVISIONER_MODE?.trim() || "stub",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
turboChild.once("exit", (code, signal) => {
|
||||
const exitCode = code ?? (signal ? 1 : 0)
|
||||
void cleanup(exitCode)
|
||||
})
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error))
|
||||
void cleanup(1)
|
||||
})
|
||||
32
turbo.json
Normal file
32
turbo.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"globalEnv": [
|
||||
"OPENWORK_DEV_MODE",
|
||||
"DATABASE_URL",
|
||||
"BETTER_AUTH_SECRET",
|
||||
"BETTER_AUTH_URL",
|
||||
"DEN_BETTER_AUTH_TRUSTED_ORIGINS",
|
||||
"CORS_ORIGINS",
|
||||
"DEN_CONTROLLER_PORT",
|
||||
"DEN_WORKER_PROXY_PORT",
|
||||
"DEN_WEB_PORT",
|
||||
"DEN_API_BASE",
|
||||
"DEN_AUTH_ORIGIN",
|
||||
"DEN_AUTH_FALLBACK_BASE",
|
||||
"PROVISIONER_MODE"
|
||||
],
|
||||
"tasks": {
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"dev:local": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**", ".next/**"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user