Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
9869fefb
Unverified
提交
9869fefb
authored
8月 26, 2025
作者:
Will Chen
提交者:
GitHub
8月 26, 2025
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Support dyad docker (#674)
TODOs: - [ ] clean-up docker images
https://claude.ai/chat/13b2c5d3-0d46-49e3-a771-d10edf1e29f4
上级
e6c92a24
显示空白字符变更
内嵌
并排
正在显示
6 个修改的文件
包含
437 行增加
和
29 行删除
+437
-29
RuntimeModeSelector.tsx
src/components/RuntimeModeSelector.tsx
+74
-0
PreviewIframe.tsx
src/components/preview_panel/PreviewIframe.tsx
+8
-3
app_handlers.ts
src/ipc/handlers/app_handlers.ts
+300
-25
process_manager.ts
src/ipc/utils/process_manager.ts
+46
-1
schemas.ts
src/lib/schemas.ts
+4
-0
settings.tsx
src/pages/settings.tsx
+5
-0
没有找到文件。
src/components/RuntimeModeSelector.tsx
0 → 100644
浏览文件 @
9869fefb
import
{
Label
}
from
"@/components/ui/label"
;
import
{
Select
,
SelectContent
,
SelectItem
,
SelectTrigger
,
SelectValue
,
}
from
"@/components/ui/select"
;
import
{
useSettings
}
from
"@/hooks/useSettings"
;
import
{
showError
}
from
"@/lib/toast"
;
import
{
IpcClient
}
from
"@/ipc/ipc_client"
;
export
function
RuntimeModeSelector
()
{
const
{
settings
,
updateSettings
}
=
useSettings
();
if
(
!
settings
)
{
return
null
;
}
const
isDockerMode
=
settings
?.
runtimeMode2
===
"docker"
;
const
handleRuntimeModeChange
=
async
(
value
:
"host"
|
"docker"
)
=>
{
try
{
await
updateSettings
({
runtimeMode2
:
value
});
}
catch
(
error
:
any
)
{
showError
(
`Failed to update runtime mode:
${
error
.
message
}
`
);
}
};
return
(
<
div
className=
"space-y-2"
>
<
div
className=
"space-y-1"
>
<
div
className=
"flex items-center space-x-2"
>
<
Label
className=
"text-sm font-medium"
htmlFor=
"runtime-mode"
>
Runtime Mode
</
Label
>
<
Select
value=
{
settings
.
runtimeMode2
??
"host"
}
onValueChange=
{
handleRuntimeModeChange
}
>
<
SelectTrigger
className=
"w-48"
id=
"runtime-mode"
>
<
SelectValue
/>
</
SelectTrigger
>
<
SelectContent
>
<
SelectItem
value=
"host"
>
Local (default)
</
SelectItem
>
<
SelectItem
value=
"docker"
>
Docker (experimental)
</
SelectItem
>
</
SelectContent
>
</
Select
>
</
div
>
<
div
className=
"text-sm text-gray-500 dark:text-gray-400"
>
Choose whether to run apps directly on the local machine or in Docker
containers
</
div
>
</
div
>
{
isDockerMode
&&
(
<
div
className=
"text-sm text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/20 p-2 rounded"
>
⚠️ Docker mode is
<
b
>
experimental
</
b
>
and requires
{
" "
}
<
button
type=
"button"
className=
"underline font-medium cursor-pointer"
onClick=
{
()
=>
IpcClient
.
getInstance
().
openExternalUrl
(
"https://www.docker.com/products/docker-desktop/"
,
)
}
>
Docker Desktop
</
button
>
{
" "
}
to be installed and running
</
div
>
)
}
</
div
>
);
}
src/components/preview_panel/PreviewIframe.tsx
浏览文件 @
9869fefb
...
...
@@ -51,10 +51,11 @@ const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => {
const
[
isCollapsed
,
setIsCollapsed
]
=
useState
(
true
);
const
{
isStreaming
}
=
useStreamChat
();
if
(
!
error
)
return
null
;
const
isDockerError
=
error
.
includes
(
"Cannot connect to the Docker"
);
const
getTruncatedError
=
()
=>
{
const
firstLine
=
error
.
split
(
"
\
n"
)[
0
];
const
snippetLength
=
2
0
0
;
const
snippetLength
=
2
5
0
;
const
snippet
=
error
.
substring
(
0
,
snippetLength
);
return
firstLine
.
length
<
snippet
.
length
?
firstLine
...
...
@@ -97,13 +98,16 @@ const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => {
<
Lightbulb
size=
{
16
}
className=
" text-red-800 dark:text-red-300"
/>
</
div
>
<
span
className=
"text-sm text-red-700 dark:text-red-200"
>
<
span
className=
"font-medium"
>
Tip:
</
span
>
Check if restarting the
app fixes the error.
<
span
className=
"font-medium"
>
Tip:
</
span
>
{
isDockerError
?
"Make sure Docker Desktop is running and try restarting the app."
:
"Check if restarting the app fixes the error."
}
</
span
>
</
div
>
</
div
>
{
/* AI Fix button at the bottom */
}
{
!
isDockerError
&&
(
<
div
className=
"mt-2 flex justify-end"
>
<
button
disabled=
{
isStreaming
}
...
...
@@ -114,6 +118,7 @@ const ErrorBanner = ({ error, onDismiss, onAIFix }: ErrorBannerProps) => {
<
span
>
Fix error with AI
</
span
>
</
button
>
</
div
>
)
}
</
div
>
);
};
...
...
src/ipc/handlers/app_handlers.ts
浏览文件 @
9869fefb
...
...
@@ -13,7 +13,7 @@ import type {
import
fs
from
"node:fs"
;
import
path
from
"node:path"
;
import
{
getDyadAppPath
,
getUserDataPath
}
from
"../../paths/paths"
;
import
{
spawn
}
from
"node:child_process"
;
import
{
ChildProcess
,
spawn
}
from
"node:child_process"
;
import
git
from
"isomorphic-git"
;
import
{
promises
as
fsPromises
}
from
"node:fs"
;
...
...
@@ -23,8 +23,9 @@ import { getFilesRecursively } from "../utils/file_utils";
import
{
runningApps
,
processCounter
,
killProcess
,
removeAppIfCurrentProcess
,
stopAppByInfo
,
removeDockerVolumesForApp
,
}
from
"../utils/process_manager"
;
import
{
getEnvVar
}
from
"../utils/read_env"
;
import
{
readSettings
}
from
"../../main/settings"
;
...
...
@@ -50,6 +51,8 @@ import { isServerFunction } from "@/supabase_admin/supabase_utils";
import
{
getVercelTeamSlug
}
from
"../utils/vercel_utils"
;
import
{
storeDbTimestampAtCurrentVersion
}
from
"../utils/neon_timestamp_utils"
;
const
DEFAULT_COMMAND
=
"(pnpm install && pnpm run dev --port 32100) || (npm install --legacy-peer-deps && npm run dev -- --port 32100)"
;
async
function
copyDir
(
source
:
string
,
destination
:
string
,
...
...
@@ -97,6 +100,19 @@ async function executeApp({
proxyWorker
.
terminate
();
proxyWorker
=
null
;
}
const
settings
=
readSettings
();
const
runtimeMode
=
settings
.
runtimeMode2
??
"host"
;
if
(
runtimeMode
===
"docker"
)
{
await
executeAppInDocker
({
appPath
,
appId
,
event
,
isNeon
,
installCommand
,
startCommand
,
});
}
else
{
await
executeAppLocalNode
({
appPath
,
appId
,
...
...
@@ -105,6 +121,7 @@ async function executeApp({
installCommand
,
startCommand
,
});
}
}
async
function
executeAppLocalNode
({
...
...
@@ -122,12 +139,7 @@ async function executeAppLocalNode({
installCommand
?:
string
|
null
;
startCommand
?:
string
|
null
;
}):
Promise
<
void
>
{
const
defaultCommand
=
"(pnpm install && pnpm run dev --port 32100) || (npm install --legacy-peer-deps && npm run dev -- --port 32100)"
;
const
hasCustomCommands
=
!!
installCommand
?.
trim
()
&&
!!
startCommand
?.
trim
();
const
command
=
hasCustomCommands
?
`
${
installCommand
!
.
trim
()}
&&
${
startCommand
!
.
trim
()}
`
:
defaultCommand
;
const
command
=
getCommand
({
installCommand
,
startCommand
});
const
spawnedProcess
=
spawn
(
command
,
[],
{
cwd
:
appPath
,
shell
:
true
,
...
...
@@ -153,8 +165,28 @@ async function executeAppLocalNode({
runningApps
.
set
(
appId
,
{
process
:
spawnedProcess
,
processId
:
currentProcessId
,
isDocker
:
false
,
});
listenToProcess
({
process
:
spawnedProcess
,
appId
,
isNeon
,
event
,
});
}
function
listenToProcess
({
process
:
spawnedProcess
,
appId
,
isNeon
,
event
,
}:
{
process
:
ChildProcess
;
appId
:
number
;
isNeon
:
boolean
;
event
:
Electron
.
IpcMainInvokeEvent
;
})
{
// Log output
spawnedProcess
.
stdout
?.
on
(
"data"
,
async
(
data
)
=>
{
const
message
=
util
.
stripVTControlCharacters
(
data
.
toString
());
...
...
@@ -169,7 +201,7 @@ async function executeAppLocalNode({
// get this template and 2) it's safer to do this with Neon apps because
// their databases have point in time restore built-in.
if
(
isNeon
&&
message
.
includes
(
"created or renamed from another"
))
{
spawnedProcess
.
stdin
.
write
(
`\r\n`
);
spawnedProcess
.
stdin
?
.
write
(
`\r\n`
);
logger
.
info
(
`App
${
appId
}
(PID:
${
spawnedProcess
.
pid
}
) wrote enter to stdin to automatically respond to drizzle push input`
,
);
...
...
@@ -239,6 +271,169 @@ async function executeAppLocalNode({
});
}
async
function
executeAppInDocker
({
appPath
,
appId
,
event
,
isNeon
,
installCommand
,
startCommand
,
}:
{
appPath
:
string
;
appId
:
number
;
event
:
Electron
.
IpcMainInvokeEvent
;
isNeon
:
boolean
;
installCommand
?:
string
|
null
;
startCommand
?:
string
|
null
;
}):
Promise
<
void
>
{
const
containerName
=
`dyad-app-
${
appId
}
`
;
// First, check if Docker is available
try
{
await
new
Promise
<
void
>
((
resolve
,
reject
)
=>
{
const
checkDocker
=
spawn
(
"docker"
,
[
"--version"
],
{
stdio
:
"pipe"
});
checkDocker
.
on
(
"close"
,
(
code
)
=>
{
if
(
code
===
0
)
{
resolve
();
}
else
{
reject
(
new
Error
(
"Docker is not available"
));
}
});
checkDocker
.
on
(
"error"
,
()
=>
{
reject
(
new
Error
(
"Docker is not available"
));
});
});
}
catch
{
throw
new
Error
(
"Docker is required but not available. Please install Docker Desktop and ensure it's running."
,
);
}
// Stop and remove any existing container with the same name
try
{
await
new
Promise
<
void
>
((
resolve
)
=>
{
const
stopContainer
=
spawn
(
"docker"
,
[
"stop"
,
containerName
],
{
stdio
:
"pipe"
,
});
stopContainer
.
on
(
"close"
,
()
=>
{
const
removeContainer
=
spawn
(
"docker"
,
[
"rm"
,
containerName
],
{
stdio
:
"pipe"
,
});
removeContainer
.
on
(
"close"
,
()
=>
resolve
());
removeContainer
.
on
(
"error"
,
()
=>
resolve
());
// Container might not exist
});
stopContainer
.
on
(
"error"
,
()
=>
resolve
());
// Container might not exist
});
}
catch
(
error
)
{
logger
.
info
(
`Docker container
${
containerName
}
not found. Ignoring error:
${
error
}
`
,
);
}
// Create a Dockerfile in the app directory if it doesn't exist
const
dockerfilePath
=
path
.
join
(
appPath
,
"Dockerfile.dyad"
);
if
(
!
fs
.
existsSync
(
dockerfilePath
))
{
const
dockerfileContent
=
`FROM node:22-alpine
# Install pnpm
RUN npm install -g pnpm
`
;
try
{
await
fsPromises
.
writeFile
(
dockerfilePath
,
dockerfileContent
,
"utf-8"
);
}
catch
(
error
)
{
logger
.
error
(
`Failed to create Dockerfile for app
${
appId
}
:`
,
error
);
throw
new
Error
(
`Failed to create Dockerfile:
${
error
}
`
);
}
}
// Build the Docker image
const
buildProcess
=
spawn
(
"docker"
,
[
"build"
,
"-f"
,
"Dockerfile.dyad"
,
"-t"
,
`dyad-app-
${
appId
}
`
,
"."
],
{
cwd
:
appPath
,
stdio
:
"pipe"
,
},
);
let
buildError
=
""
;
buildProcess
.
stderr
?.
on
(
"data"
,
(
data
)
=>
{
buildError
+=
data
.
toString
();
});
await
new
Promise
<
void
>
((
resolve
,
reject
)
=>
{
buildProcess
.
on
(
"close"
,
(
code
)
=>
{
if
(
code
===
0
)
{
resolve
();
}
else
{
reject
(
new
Error
(
`Docker build failed:
${
buildError
}
`
));
}
});
buildProcess
.
on
(
"error"
,
(
err
)
=>
{
reject
(
new
Error
(
`Docker build process error:
${
err
.
message
}
`
));
});
});
// Run the Docker container
const
process
=
spawn
(
"docker"
,
[
"run"
,
"--rm"
,
"--name"
,
containerName
,
"-p"
,
"32100:32100"
,
"-v"
,
`
${
appPath
}
:/app`
,
"-v"
,
`dyad-pnpm-
${
appId
}
:/app/.pnpm-store`
,
"-e"
,
"PNPM_STORE_PATH=/app/.pnpm-store"
,
"-w"
,
"/app"
,
`dyad-app-
${
appId
}
`
,
"sh"
,
"-c"
,
getCommand
({
installCommand
,
startCommand
}),
],
{
stdio
:
"pipe"
,
detached
:
false
,
},
);
// Check if process spawned correctly
if
(
!
process
.
pid
)
{
// Attempt to capture any immediate errors if possible
let
errorOutput
=
""
;
process
.
stderr
?.
on
(
"data"
,
(
data
)
=>
(
errorOutput
+=
data
));
await
new
Promise
((
resolve
)
=>
process
.
on
(
"error"
,
resolve
));
// Wait for error event
throw
new
Error
(
`Failed to spawn Docker container for app
${
appId
}
. Error:
${
errorOutput
||
"Unknown spawn error"
}
`
,
);
}
// Increment the counter and store the process reference with its ID
const
currentProcessId
=
processCounter
.
increment
();
runningApps
.
set
(
appId
,
{
process
,
processId
:
currentProcessId
,
isDocker
:
true
,
containerName
,
});
listenToProcess
({
process
,
appId
,
isNeon
,
event
,
});
}
// Helper to kill process on a specific port (cross-platform, using kill-port)
async
function
killProcessOnPort
(
port
:
number
):
Promise
<
void
>
{
try
{
...
...
@@ -248,6 +443,49 @@ async function killProcessOnPort(port: number): Promise<void> {
}
}
// Helper to stop any Docker containers publishing a given host port
async
function
stopDockerContainersOnPort
(
port
:
number
):
Promise
<
void
>
{
try
{
// List container IDs that publish the given port
const
list
=
spawn
(
"docker"
,
[
"ps"
,
"--filter"
,
`publish=
${
port
}
`
,
"-q"
],
{
stdio
:
"pipe"
,
});
let
stdout
=
""
;
list
.
stdout
?.
on
(
"data"
,
(
data
)
=>
{
stdout
+=
data
.
toString
();
});
await
new
Promise
<
void
>
((
resolve
)
=>
{
list
.
on
(
"close"
,
()
=>
resolve
());
list
.
on
(
"error"
,
()
=>
resolve
());
});
const
containerIds
=
stdout
.
split
(
"
\
n"
)
.
map
((
s
)
=>
s
.
trim
())
.
filter
(
Boolean
);
if
(
containerIds
.
length
===
0
)
{
return
;
}
// Stop each container best-effort
await
Promise
.
all
(
containerIds
.
map
(
(
id
)
=>
new
Promise
<
void
>
((
resolve
)
=>
{
const
stop
=
spawn
(
"docker"
,
[
"stop"
,
id
],
{
stdio
:
"pipe"
});
stop
.
on
(
"close"
,
()
=>
resolve
());
stop
.
on
(
"error"
,
()
=>
resolve
());
}),
),
);
}
catch
(
e
)
{
logger
.
warn
(
`Failed stopping Docker containers on port
${
port
}
:
${
e
}
`
);
}
}
export
function
registerAppHandlers
()
{
handle
(
"restart-dyad"
,
async
()
=>
{
app
.
relaunch
();
...
...
@@ -523,8 +761,8 @@ export function registerAppHandlers() {
const
appPath
=
getDyadAppPath
(
app
.
path
);
try
{
//
Kill any orphaned process on port 32100 (in case previous run left it)
await
killProcessOn
Port
(
32100
);
//
There may have been a previous run that left a process on port 32100.
await
cleanUp
Port
(
32100
);
await
executeApp
({
appPath
,
appId
,
...
...
@@ -581,8 +819,7 @@ export function registerAppHandlers() {
}
try
{
// Use the killProcess utility to stop the process
await
killProcess
(
process
);
await
stopAppByInfo
(
appId
,
appInfo
);
// Now, safely remove the app from the map *after* confirming closure
removeAppIfCurrentProcess
(
appId
,
process
);
...
...
@@ -616,19 +853,17 @@ export function registerAppHandlers() {
// First stop the app if it's running
const
appInfo
=
runningApps
.
get
(
appId
);
if
(
appInfo
)
{
const
{
process
,
process
Id
}
=
appInfo
;
const
{
processId
}
=
appInfo
;
logger
.
log
(
`Stopping app
${
appId
}
(processId
${
processId
}
) before restart`
,
);
await
killProcess
(
process
);
runningApps
.
delete
(
appId
);
await
stopAppByInfo
(
appId
,
appInfo
);
}
else
{
logger
.
log
(
`App
${
appId
}
not running. Proceeding to start.`
);
}
//
Kill any orphaned process on port 32100 (in case previous run left it)
await
killProcessOn
Port
(
32100
);
//
There may have been a previous run that left a process on port 32100.
await
cleanUp
Port
(
32100
);
// Now start the app again
const
app
=
await
db
.
query
.
apps
.
findFirst
({
...
...
@@ -643,6 +878,9 @@ export function registerAppHandlers() {
// Remove node_modules if requested
if
(
removeNodeModules
)
{
const
settings
=
readSettings
();
const
runtimeMode
=
settings
.
runtimeMode2
??
"host"
;
const
nodeModulesPath
=
path
.
join
(
appPath
,
"node_modules"
);
logger
.
log
(
`Removing node_modules for app
${
appId
}
at
${
nodeModulesPath
}
`
,
...
...
@@ -656,6 +894,24 @@ export function registerAppHandlers() {
}
else
{
logger
.
log
(
`No node_modules directory found for app
${
appId
}
`
);
}
// If running in Docker mode, also remove container volumes so deps reinstall freshly
if
(
runtimeMode
===
"docker"
)
{
logger
.
log
(
`Docker mode detected for app
${
appId
}
. Removing Docker volumes dyad-pnpm-
${
appId
}
...`
,
);
try
{
await
removeDockerVolumesForApp
(
appId
);
logger
.
log
(
`Removed Docker volumes for app
${
appId
}
(dyad-pnpm-
${
appId
}
).`
,
);
}
catch
(
e
)
{
// Best-effort cleanup; log and continue
logger
.
warn
(
`Failed to remove Docker volumes for app
${
appId
}
. Continuing:
${
e
}
`
,
);
}
}
}
logger
.
debug
(
...
...
@@ -786,8 +1042,7 @@ export function registerAppHandlers() {
const
appInfo
=
runningApps
.
get
(
appId
)
!
;
try
{
logger
.
log
(
`Stopping app
${
appId
}
before deletion.`
);
// Adjusted log
await
killProcess
(
appInfo
.
process
);
runningApps
.
delete
(
appId
);
await
stopAppByInfo
(
appId
,
appInfo
);
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error stopping app
${
appId
}
before deletion:`
,
error
);
// Adjusted log
// Continue with deletion even if stopping fails
...
...
@@ -860,8 +1115,7 @@ export function registerAppHandlers() {
if
(
runningApps
.
has
(
appId
))
{
const
appInfo
=
runningApps
.
get
(
appId
)
!
;
try
{
await
killProcess
(
appInfo
.
process
);
runningApps
.
delete
(
appId
);
await
stopAppByInfo
(
appId
,
appInfo
);
}
catch
(
error
:
any
)
{
logger
.
error
(
`Error stopping app
${
appId
}
before renaming:`
,
error
);
throw
new
Error
(
...
...
@@ -957,8 +1211,7 @@ export function registerAppHandlers() {
for
(
const
appId
of
runningAppIds
)
{
try
{
const
appInfo
=
runningApps
.
get
(
appId
)
!
;
await
killProcess
(
appInfo
.
process
);
runningApps
.
delete
(
appId
);
await
stopAppByInfo
(
appId
,
appInfo
);
}
catch
(
error
)
{
logger
.
error
(
`Error stopping app
${
appId
}
during reset:`
,
error
);
// Continue with reset even if stopping fails
...
...
@@ -1088,3 +1341,25 @@ export function registerAppHandlers() {
},
);
}
function
getCommand
({
installCommand
,
startCommand
,
}:
{
installCommand
?:
string
|
null
;
startCommand
?:
string
|
null
;
})
{
const
hasCustomCommands
=
!!
installCommand
?.
trim
()
&&
!!
startCommand
?.
trim
();
return
hasCustomCommands
?
`
${
installCommand
!
.
trim
()}
&&
${
startCommand
!
.
trim
()}
`
:
DEFAULT_COMMAND
;
}
async
function
cleanUpPort
(
port
:
number
)
{
const
settings
=
readSettings
();
if
(
settings
.
runtimeMode2
===
"docker"
)
{
await
stopDockerContainersOnPort
(
port
);
}
else
{
await
killProcessOnPort
(
port
);
}
}
src/ipc/utils/process_manager.ts
浏览文件 @
9869fefb
import
{
ChildProcess
}
from
"node:child_process"
;
import
{
ChildProcess
,
spawn
}
from
"node:child_process"
;
import
treeKill
from
"tree-kill"
;
// Define a type for the value stored in runningApps
export
interface
RunningAppInfo
{
process
:
ChildProcess
;
processId
:
number
;
isDocker
:
boolean
;
containerName
?:
string
;
}
// Store running app processes
...
...
@@ -81,6 +83,49 @@ export function killProcess(process: ChildProcess): Promise<void> {
});
}
/**
* Gracefully stops a Docker container by name. Resolves even if the container doesn't exist.
*/
export
function
stopDockerContainer
(
containerName
:
string
):
Promise
<
void
>
{
return
new
Promise
<
void
>
((
resolve
)
=>
{
const
stop
=
spawn
(
"docker"
,
[
"stop"
,
containerName
],
{
stdio
:
"pipe"
});
stop
.
on
(
"close"
,
()
=>
resolve
());
stop
.
on
(
"error"
,
()
=>
resolve
());
});
}
/**
* Removes Docker named volumes used for an app's dependencies.
* Best-effort: resolves even if volumes don't exist.
*/
export
function
removeDockerVolumesForApp
(
appId
:
number
):
Promise
<
void
>
{
return
new
Promise
<
void
>
((
resolve
)
=>
{
const
pnpmVolume
=
`dyad-pnpm-
${
appId
}
`
;
const
rm
=
spawn
(
"docker"
,
[
"volume"
,
"rm"
,
"-f"
,
pnpmVolume
],
{
stdio
:
"pipe"
,
});
rm
.
on
(
"close"
,
()
=>
resolve
());
rm
.
on
(
"error"
,
()
=>
resolve
());
});
}
/**
* Stops an app based on its RunningAppInfo (container vs host) and removes it from the running map.
*/
export
async
function
stopAppByInfo
(
appId
:
number
,
appInfo
:
RunningAppInfo
,
):
Promise
<
void
>
{
if
(
appInfo
.
isDocker
)
{
const
containerName
=
appInfo
.
containerName
||
`dyad-app-
${
appId
}
`
;
await
stopDockerContainer
(
containerName
);
}
else
{
await
killProcess
(
appInfo
.
process
);
}
runningApps
.
delete
(
appId
);
}
/**
* Removes an app from the running apps map if it's the current process
* @param appId The app ID
...
...
src/lib/schemas.ts
浏览文件 @
9869fefb
...
...
@@ -69,6 +69,9 @@ export type ProviderSetting = z.infer<typeof ProviderSettingSchema>;
export
const
RuntimeModeSchema
=
z
.
enum
([
"web-sandbox"
,
"local-node"
,
"unset"
]);
export
type
RuntimeMode
=
z
.
infer
<
typeof
RuntimeModeSchema
>
;
export
const
RuntimeMode2Schema
=
z
.
enum
([
"host"
,
"docker"
]);
export
type
RuntimeMode2
=
z
.
infer
<
typeof
RuntimeMode2Schema
>
;
export
const
ChatModeSchema
=
z
.
enum
([
"build"
,
"ask"
]);
export
type
ChatMode
=
z
.
infer
<
typeof
ChatModeSchema
>
;
...
...
@@ -170,6 +173,7 @@ export const UserSettingsSchema = z.object({
enableNativeGit
:
z
.
boolean
().
optional
(),
enableAutoUpdate
:
z
.
boolean
(),
releaseChannel
:
ReleaseChannelSchema
,
runtimeMode2
:
RuntimeMode2Schema
.
optional
(),
////////////////////////////////
// E2E TESTING ONLY.
...
...
src/pages/settings.tsx
浏览文件 @
9869fefb
...
...
@@ -23,6 +23,7 @@ import { AutoFixProblemsSwitch } from "@/components/AutoFixProblemsSwitch";
import
{
AutoUpdateSwitch
}
from
"@/components/AutoUpdateSwitch"
;
import
{
ReleaseChannelSelector
}
from
"@/components/ReleaseChannelSelector"
;
import
{
NeonIntegration
}
from
"@/components/NeonIntegration"
;
import
{
RuntimeModeSelector
}
from
"@/components/RuntimeModeSelector"
;
export
default
function
SettingsPage
()
{
const
[
isResetDialogOpen
,
setIsResetDialogOpen
]
=
useState
(
false
);
...
...
@@ -256,6 +257,10 @@ export function GeneralSettings({ appVersion }: { appVersion: string | null }) {
<
ReleaseChannelSelector
/>
</
div
>
<
div
className=
"mt-4"
>
<
RuntimeModeSelector
/>
</
div
>
<
div
className=
"flex items-center text-sm text-gray-500 dark:text-gray-400 mt-4"
>
<
span
className=
"mr-2 font-medium"
>
App Version:
</
span
>
<
span
className=
"bg-gray-100 dark:bg-gray-700 px-2 py-0.5 rounded text-gray-800 dark:text-gray-200 font-mono"
>
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论