feat(refactor): live zed-style codebase files autocomplete and context attachment
This commit is contained in:
@@ -30,6 +30,7 @@ import {
|
|||||||
} from "@/lib/dev-container-git";
|
} from "@/lib/dev-container-git";
|
||||||
import { buildDesignKitPromptSection } from "@/lib/design-kits/for-ai";
|
import { buildDesignKitPromptSection } from "@/lib/design-kits/for-ai";
|
||||||
import { buildCodebaseSummary } from "@/lib/ai/codebase-summary";
|
import { buildCodebaseSummary } from "@/lib/ai/codebase-summary";
|
||||||
|
import { execInDevContainer } from "@/lib/dev-container";
|
||||||
import type { ChatMessage, ToolCall } from "@/lib/ai/gemini-chat";
|
import type { ChatMessage, ToolCall } from "@/lib/ai/gemini-chat";
|
||||||
|
|
||||||
// C-01: Lowered from 15 → 8. Real workflows (scaffold → install →
|
// C-01: Lowered from 15 → 8. Real workflows (scaffold → install →
|
||||||
@@ -404,6 +405,7 @@ export async function POST(request: Request) {
|
|||||||
workspace: string;
|
workspace: string;
|
||||||
mcp_token?: string;
|
mcp_token?: string;
|
||||||
chatMode?: "vibe" | "collaborate" | "delegate";
|
chatMode?: "vibe" | "collaborate" | "delegate";
|
||||||
|
attachedFiles?: string[];
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
body = await request.json();
|
body = await request.json();
|
||||||
@@ -411,7 +413,14 @@ export async function POST(request: Request) {
|
|||||||
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
|
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { thread_id, message, workspace, mcp_token, chatMode = "vibe" } = body;
|
const {
|
||||||
|
thread_id,
|
||||||
|
message,
|
||||||
|
workspace,
|
||||||
|
mcp_token,
|
||||||
|
chatMode = "vibe",
|
||||||
|
attachedFiles = [],
|
||||||
|
} = body;
|
||||||
if (!thread_id || !message?.trim()) {
|
if (!thread_id || !message?.trim()) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "thread_id and message are required" },
|
{ error: "thread_id and message are required" },
|
||||||
@@ -529,6 +538,32 @@ export async function POST(request: Request) {
|
|||||||
chatMode,
|
chatMode,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let fileContextsBlock = "";
|
||||||
|
if (
|
||||||
|
Array.isArray(attachedFiles) &&
|
||||||
|
attachedFiles.length > 0 &&
|
||||||
|
activeProject?.slug
|
||||||
|
) {
|
||||||
|
fileContextsBlock =
|
||||||
|
"\n\n=== USER-ATTACHED CODE CONTEXT ===\nThe user has explicitly attached the following files to this conversation turn as active context. You MUST refer to these file states when writing your response or deciding edits:\n";
|
||||||
|
for (const f of attachedFiles) {
|
||||||
|
const safePath = String(f).replace(/\.\./g, "").replace(/^\//, "");
|
||||||
|
try {
|
||||||
|
const res = (await execInDevContainer({
|
||||||
|
projectId: activeProject.id,
|
||||||
|
command: `cat "/workspace/${activeProject.slug}/${safePath}" 2>/dev/null || echo "[File not found]"`,
|
||||||
|
})) as unknown as { exitCode: number; stdout: string };
|
||||||
|
fileContextsBlock += `\nFile: \`${safePath}\`\n\`\`\`\n${res.stdout}\n\`\`\`\n`;
|
||||||
|
} catch {
|
||||||
|
fileContextsBlock += `\nFile: \`${safePath}\`\n[Error reading file]\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileContextsBlock) {
|
||||||
|
systemPrompt += fileContextsBlock;
|
||||||
|
}
|
||||||
|
|
||||||
// Sentry-as-product Stage 4: auto-surface unresolved errors at
|
// Sentry-as-product Stage 4: auto-surface unresolved errors at
|
||||||
// chat-turn start. We pull the last 6 hours' unresolved issues
|
// chat-turn start. We pull the last 6 hours' unresolved issues
|
||||||
// for the active project; if anything has fired ≥2 times, we
|
// for the active project; if anything has fired ≥2 times, we
|
||||||
|
|||||||
63
vibn-frontend/app/api/projects/[projectId]/files/route.ts
Normal file
63
vibn-frontend/app/api/projects/[projectId]/files/route.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import {
|
||||||
|
execInDevContainer,
|
||||||
|
ensureDevContainer,
|
||||||
|
} from "../../../../../lib/dev-container";
|
||||||
|
import { authSession } from "../../../../../lib/auth/session-server";
|
||||||
|
import { queryOne } from "../../../../../lib/db-postgres";
|
||||||
|
import type { VibnWorkspace } from "../../../../../lib/workspaces";
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
req: Request,
|
||||||
|
ctx: { params: Promise<{ projectId: string }> },
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { projectId } = await ctx.params;
|
||||||
|
const { searchParams } = new URL(req.url);
|
||||||
|
const workspace = searchParams.get("workspace") || "";
|
||||||
|
|
||||||
|
const session = await authSession();
|
||||||
|
if (!session?.user?.email || !workspace) {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch project details to get the slug
|
||||||
|
const r = await queryOne<{ data: { slug: string } }>(
|
||||||
|
`SELECT data FROM fs_projects WHERE id = $1`,
|
||||||
|
[projectId],
|
||||||
|
);
|
||||||
|
if (!r?.data?.slug) {
|
||||||
|
return NextResponse.json({ error: "Project not found" }, { status: 404 });
|
||||||
|
}
|
||||||
|
const projectSlug = r.data.slug;
|
||||||
|
|
||||||
|
// Ensure the container is active
|
||||||
|
await ensureDevContainer({
|
||||||
|
projectId,
|
||||||
|
projectSlug,
|
||||||
|
projectName: projectSlug,
|
||||||
|
workspace: { slug: workspace } as unknown as VibnWorkspace,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run a fast find inside the dev container, excluding build/node_modules/git artifacts
|
||||||
|
const result = (await execInDevContainer({
|
||||||
|
projectId,
|
||||||
|
command: `find . -maxdepth 4 -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/.next/*" -not -path "*/dist/*" -not -path "*/build/*" | sort | sed 's|^\\./||'`,
|
||||||
|
})) as unknown as { exitCode: number; stdout: string };
|
||||||
|
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
return NextResponse.json({ files: [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = result.stdout
|
||||||
|
.split("\n")
|
||||||
|
.map((f: string) => f.trim())
|
||||||
|
.filter((f: string) => f && f !== "." && f !== "..");
|
||||||
|
|
||||||
|
return NextResponse.json({ files });
|
||||||
|
} catch (err) {
|
||||||
|
return NextResponse.json({ error: String(err) }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -882,6 +882,24 @@ export function ChatPanel({
|
|||||||
const [chatMode, setChatMode] = useState<"collaborate" | "vibe" | "delegate">(
|
const [chatMode, setChatMode] = useState<"collaborate" | "vibe" | "delegate">(
|
||||||
"vibe",
|
"vibe",
|
||||||
);
|
);
|
||||||
|
const [projectFiles, setProjectFiles] = useState<string[]>([]);
|
||||||
|
const [attachedFiles, setAttachedFiles] = useState<string[]>([]);
|
||||||
|
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||||
|
const [suggestionsFilter, setSuggestionsFilter] = useState("");
|
||||||
|
const [suggestionIndex, setSuggestionIndex] = useState(0);
|
||||||
|
|
||||||
|
// Fetch codebase files list inside the scoped project
|
||||||
|
useEffect(() => {
|
||||||
|
if (!projectId || !workspace || status !== "authenticated") return;
|
||||||
|
fetch(`/api/projects/${projectId}/files?workspace=${workspace}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (Array.isArray(data.files)) {
|
||||||
|
setProjectFiles(data.files);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}, [projectId, workspace, status]);
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
const [showThreads, setShowThreads] = useState(false);
|
const [showThreads, setShowThreads] = useState(false);
|
||||||
const [mcpToken, setMcpToken] = useState<string | null>(null);
|
const [mcpToken, setMcpToken] = useState<string | null>(null);
|
||||||
@@ -1149,6 +1167,7 @@ export function ChatPanel({
|
|||||||
throw new Error(err.error || `HTTP ${r.status}`);
|
throw new Error(err.error || `HTTP ${r.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAttachedFiles([]);
|
||||||
setMessages((prev) => [
|
setMessages((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
@@ -1170,10 +1189,13 @@ export function ChatPanel({
|
|||||||
workspace,
|
workspace,
|
||||||
mcp_token: mcpToken,
|
mcp_token: mcpToken,
|
||||||
chatMode,
|
chatMode,
|
||||||
|
attachedFiles,
|
||||||
}),
|
}),
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setAttachedFiles([]);
|
||||||
|
|
||||||
if (!res.ok || !res.body) throw new Error("Stream failed");
|
if (!res.ok || !res.body) throw new Error("Stream failed");
|
||||||
|
|
||||||
const reader = res.body.getReader();
|
const reader = res.body.getReader();
|
||||||
@@ -1389,6 +1411,7 @@ export function ChatPanel({
|
|||||||
unifiedProjectShell,
|
unifiedProjectShell,
|
||||||
chatMode,
|
chatMode,
|
||||||
projectId,
|
projectId,
|
||||||
|
attachedFiles,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1419,13 +1442,57 @@ export function ChatPanel({
|
|||||||
window.removeEventListener("vibn:chat-prompt", onPrompt as EventListener);
|
window.removeEventListener("vibn:chat-prompt", onPrompt as EventListener);
|
||||||
}, [sendMessage, projectId]);
|
}, [sendMessage, projectId]);
|
||||||
|
|
||||||
|
const handleInputChange = (val: string) => {
|
||||||
|
setInput(val);
|
||||||
|
const match = val.match(/\/(\S*)$/);
|
||||||
|
if (match) {
|
||||||
|
setShowSuggestions(true);
|
||||||
|
setSuggestionsFilter(match[1] || "");
|
||||||
|
setSuggestionIndex(0);
|
||||||
|
} else {
|
||||||
|
setShowSuggestions(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
if (e.key === "Enter" && !e.shiftKey) {
|
if (showSuggestions) {
|
||||||
e.preventDefault();
|
const filtered = projectFiles
|
||||||
sendMessage();
|
.filter((f) =>
|
||||||
} else if (e.key === "Escape" && sending) {
|
f.toLowerCase().includes(suggestionsFilter.toLowerCase()),
|
||||||
e.preventDefault();
|
)
|
||||||
cancelMessage();
|
.slice(0, 8);
|
||||||
|
|
||||||
|
if (e.key === "ArrowDown") {
|
||||||
|
e.preventDefault();
|
||||||
|
setSuggestionIndex((prev) => (prev + 1) % Math.max(1, filtered.length));
|
||||||
|
} else if (e.key === "ArrowUp") {
|
||||||
|
e.preventDefault();
|
||||||
|
setSuggestionIndex(
|
||||||
|
(prev) => (prev - 1 + filtered.length) % Math.max(1, filtered.length),
|
||||||
|
);
|
||||||
|
} else if (e.key === "Enter" || e.key === "Tab") {
|
||||||
|
e.preventDefault();
|
||||||
|
if (filtered[suggestionIndex]) {
|
||||||
|
const selectedFile = filtered[suggestionIndex];
|
||||||
|
const before = input.slice(0, input.lastIndexOf("/"));
|
||||||
|
setInput(before + " ");
|
||||||
|
if (!attachedFiles.includes(selectedFile)) {
|
||||||
|
setAttachedFiles((prev) => [...prev, selectedFile]);
|
||||||
|
}
|
||||||
|
setShowSuggestions(false);
|
||||||
|
}
|
||||||
|
} else if (e.key === "Escape") {
|
||||||
|
e.preventDefault();
|
||||||
|
setShowSuggestions(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (e.key === "Enter" && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
sendMessage();
|
||||||
|
} else if (e.key === "Escape" && sending) {
|
||||||
|
e.preventDefault();
|
||||||
|
cancelMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1676,6 +1743,7 @@ export function ChatPanel({
|
|||||||
borderTop: "1px solid #e8e4dc",
|
borderTop: "1px solid #e8e4dc",
|
||||||
background: "#faf8f5",
|
background: "#faf8f5",
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Chat Mode Toggle */}
|
{/* Chat Mode Toggle */}
|
||||||
@@ -1806,6 +1874,149 @@ export function ChatPanel({
|
|||||||
Read-only mode — add your MCP token in Settings to enable actions.
|
Read-only mode — add your MCP token in Settings to enable actions.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* File Autocomplete Suggestions */}
|
||||||
|
{showSuggestions &&
|
||||||
|
(() => {
|
||||||
|
const filtered = projectFiles
|
||||||
|
.filter((f) =>
|
||||||
|
f.toLowerCase().includes(suggestionsFilter.toLowerCase()),
|
||||||
|
)
|
||||||
|
.slice(0, 8);
|
||||||
|
if (filtered.length === 0) return null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "calc(100% - 10px)",
|
||||||
|
left: 14,
|
||||||
|
right: 14,
|
||||||
|
background: "#fff",
|
||||||
|
border: "1px solid #e8e4dc",
|
||||||
|
borderRadius: 8,
|
||||||
|
boxShadow:
|
||||||
|
"0 -4px 12px rgba(0,0,0,0.08), 0 2px 4px rgba(0,0,0,0.02)",
|
||||||
|
zIndex: 1000,
|
||||||
|
maxHeight: 200,
|
||||||
|
overflowY: "auto",
|
||||||
|
padding: "4px 0",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "0.65rem",
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "#a09a90",
|
||||||
|
padding: "6px 12px 4px",
|
||||||
|
borderBottom: "1px solid #f0ede8",
|
||||||
|
marginBottom: 4,
|
||||||
|
textTransform: "uppercase",
|
||||||
|
letterSpacing: "0.05em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Codebase Files
|
||||||
|
</div>
|
||||||
|
{filtered.map((file, idx) => (
|
||||||
|
<div
|
||||||
|
key={file}
|
||||||
|
onClick={() => {
|
||||||
|
const before = input.slice(0, input.lastIndexOf("/"));
|
||||||
|
setInput(before + " ");
|
||||||
|
if (!attachedFiles.includes(file)) {
|
||||||
|
setAttachedFiles((prev) => [...prev, file]);
|
||||||
|
}
|
||||||
|
setShowSuggestions(false);
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setSuggestionIndex(idx)}
|
||||||
|
style={{
|
||||||
|
padding: "6px 12px",
|
||||||
|
fontSize: "0.76rem",
|
||||||
|
fontFamily: "var(--font-mono), monospace",
|
||||||
|
color: idx === suggestionIndex ? "#3d5afe" : "#1a1a1a",
|
||||||
|
background:
|
||||||
|
idx === suggestionIndex ? "#3d5afe0c" : "transparent",
|
||||||
|
cursor: "pointer",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
📄 {file}
|
||||||
|
</span>
|
||||||
|
{idx === suggestionIndex && (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: "0.6rem",
|
||||||
|
color: "#3d5afe",
|
||||||
|
opacity: 0.7,
|
||||||
|
fontFamily: "var(--font-inter), sans-serif",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
press Enter
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
|
{/* Attached Files Chips */}
|
||||||
|
{attachedFiles.length > 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: 6,
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{attachedFiles.map((file) => (
|
||||||
|
<div
|
||||||
|
key={file}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 6,
|
||||||
|
background: "#eef2ff",
|
||||||
|
border: "1px solid #c7d2fe",
|
||||||
|
borderRadius: 6,
|
||||||
|
padding: "4px 8px",
|
||||||
|
fontSize: "0.7rem",
|
||||||
|
color: "#312e81",
|
||||||
|
fontFamily: "var(--font-mono), monospace",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>📄 {file.split("/").pop()}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setAttachedFiles((prev) => prev.filter((f) => f !== file))
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
background: "none",
|
||||||
|
border: "none",
|
||||||
|
padding: 0,
|
||||||
|
cursor: "pointer",
|
||||||
|
color: "#4338ca",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
title="Remove file"
|
||||||
|
>
|
||||||
|
<X style={{ width: 12, height: 12 }} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<ProjectPreviewChatInputWrap unifiedShell={unifiedProjectShell}>
|
<ProjectPreviewChatInputWrap unifiedShell={unifiedProjectShell}>
|
||||||
{(selectToggle) => (
|
{(selectToggle) => (
|
||||||
<div
|
<div
|
||||||
@@ -1823,7 +2034,7 @@ export function ChatPanel({
|
|||||||
<textarea
|
<textarea
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => handleInputChange(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder={
|
placeholder={
|
||||||
sending ? "Esc to stop generating…" : "Ask Vibn AI anything…"
|
sending ? "Esc to stop generating…" : "Ask Vibn AI anything…"
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { ensureDevContainer, execInDevContainer } from "@/lib/dev-container";
|
||||||
import { query } from "@/lib/db-postgres";
|
|
||||||
import {
|
|
||||||
ensureDevContainer,
|
|
||||||
execInDevContainer,
|
|
||||||
getDevContainerStatus,
|
|
||||||
} from "@/lib/dev-container";
|
|
||||||
import { authSession } from "@/lib/auth/session-server";
|
import { authSession } from "@/lib/auth/session-server";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,7 +13,9 @@ export async function buildCodebaseSummary(
|
|||||||
if (!projectId || !projectSlug) return "";
|
if (!projectId || !projectSlug) return "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const session = await authSession();
|
const session = (await authSession()) as unknown as {
|
||||||
|
workspace?: import("@/lib/workspaces").VibnWorkspace;
|
||||||
|
};
|
||||||
if (!session?.workspace) return "";
|
if (!session?.workspace) return "";
|
||||||
|
|
||||||
// Ensure the container is actually running before we try to exec inside it
|
// Ensure the container is actually running before we try to exec inside it
|
||||||
@@ -61,7 +57,7 @@ export async function buildCodebaseSummary(
|
|||||||
command: bashScript,
|
command: bashScript,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.exitCode !== 0 || !result.stdout.trim()) {
|
if (result.code !== 0 || !result.stdout.trim()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -747,7 +747,7 @@ export async function probeDevServerReadiness(
|
|||||||
command: probeCmd,
|
command: probeCmd,
|
||||||
timeoutMs: 310_000,
|
timeoutMs: 310_000,
|
||||||
});
|
});
|
||||||
if (r.exitCode === 0) {
|
if (r.code === 0) {
|
||||||
await query(
|
await query(
|
||||||
`UPDATE fs_dev_servers SET state = 'running' WHERE id = $1 AND project_id = $2 AND state != 'stopped'`,
|
`UPDATE fs_dev_servers SET state = 'running' WHERE id = $1 AND project_id = $2 AND state != 'stopped'`,
|
||||||
[serverId, projectId],
|
[serverId, projectId],
|
||||||
@@ -759,7 +759,7 @@ export async function probeDevServerReadiness(
|
|||||||
projectId,
|
projectId,
|
||||||
serverId,
|
serverId,
|
||||||
port,
|
port,
|
||||||
exitCode: r.exitCode,
|
exitCode: r.code,
|
||||||
stdout: (r.stdout || "").slice(0, 600),
|
stdout: (r.stdout || "").slice(0, 600),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -767,7 +767,7 @@ export async function probeDevServerReadiness(
|
|||||||
`UPDATE fs_dev_servers SET state = 'failed' WHERE id = $1 AND project_id = $2 AND state != 'stopped'`,
|
`UPDATE fs_dev_servers SET state = 'failed' WHERE id = $1 AND project_id = $2 AND state != 'stopped'`,
|
||||||
[serverId, projectId],
|
[serverId, projectId],
|
||||||
);
|
);
|
||||||
throw new Error(`Probe failed with exit code ${r.exitCode}: ${r.stdout}`);
|
throw new Error(`Probe failed with exit code ${r.code}: ${r.stdout}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
Reference in New Issue
Block a user