Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
bit-pm
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
燕伟桐
bit-pm
Commits
2c284d0f
Unverified
提交
2c284d0f
authored
7月 11, 2025
作者:
Will Chen
提交者:
GitHub
7月 11, 2025
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Allow configuring environmental variables in panel (#626)
- [ ] Add test cases
上级
4b84b12f
全部展开
隐藏空白字符变更
内嵌
并排
正在显示
18 个修改的文件
包含
281 行增加
和
7 行删除
+281
-7
env_var.spec.ts
e2e-tests/env_var.spec.ts
+62
-0
test_helper.ts
e2e-tests/helpers/test_helper.ts
+1
-1
env_var.spec.ts_create-aKey
e2e-tests/snapshots/env_var.spec.ts_create-aKey
+2
-0
env_var.spec.ts_create-bKey
e2e-tests/snapshots/env_var.spec.ts_create-bKey
+3
-0
env_var.spec.ts_delete-aKey
e2e-tests/snapshots/env_var.spec.ts_delete-aKey
+2
-0
env_var.spec.ts_edit-bKey
e2e-tests/snapshots/env_var.spec.ts_edit-bKey
+3
-0
app_env_vars_utils.test.ts
src/__tests__/app_env_vars_utils.test.ts
+0
-0
TitleBar.tsx
src/app/TitleBar.tsx
+1
-1
appAtoms.ts
src/atoms/appAtoms.ts
+3
-1
ConfigurePanel.tsx
src/components/preview_panel/ConfigurePanel.tsx
+0
-0
PreviewHeader.tsx
src/components/preview_panel/PreviewHeader.tsx
+19
-4
PreviewPanel.tsx
src/components/preview_panel/PreviewPanel.tsx
+3
-0
app_env_vars_handlers.ts
src/ipc/handlers/app_env_vars_handlers.ts
+81
-0
ipc_client.ts
src/ipc/ipc_client.ts
+12
-0
ipc_host.ts
src/ipc/ipc_host.ts
+2
-0
ipc_types.ts
src/ipc/ipc_types.ts
+14
-0
app_env_var_utils.ts
src/ipc/utils/app_env_var_utils.ts
+71
-0
preload.ts
src/preload.ts
+2
-0
没有找到文件。
e2e-tests/env_var.spec.ts
0 → 100644
浏览文件 @
2c284d0f
import
{
expect
}
from
"@playwright/test"
;
import
{
test
}
from
"./helpers/test_helper"
;
import
path
from
"path"
;
import
fs
from
"fs"
;
test
(
"env var"
,
async
({
po
})
=>
{
await
po
.
sendPrompt
(
"tc=1"
);
const
appPath
=
await
po
.
getCurrentAppPath
();
await
po
.
selectPreviewMode
(
"configure"
);
// Create a new env var
await
po
.
page
.
getByRole
(
"button"
,
{
name
:
"Add Environment Variable"
})
.
click
();
await
po
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Key"
}).
click
();
await
po
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Key"
}).
fill
(
"aKey"
);
await
po
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Value"
}).
click
();
await
po
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Value"
}).
fill
(
"aValue"
);
await
po
.
page
.
getByRole
(
"button"
,
{
name
:
"Save"
}).
click
();
await
snapshotEnvVar
({
appPath
,
name
:
"create-aKey"
});
// Create second env var
await
po
.
page
.
getByRole
(
"button"
,
{
name
:
"Add Environment Variable"
})
.
click
();
await
po
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Key"
}).
click
();
await
po
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Key"
}).
fill
(
"bKey"
);
await
po
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Value"
}).
click
();
await
po
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Value"
}).
fill
(
"bValue"
);
await
po
.
page
.
getByRole
(
"button"
,
{
name
:
"Save"
}).
click
();
await
snapshotEnvVar
({
appPath
,
name
:
"create-bKey"
});
// Edit second env var
await
po
.
page
.
getByTestId
(
"edit-env-var-bKey"
).
click
();
await
po
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Value"
}).
click
();
await
po
.
page
.
getByRole
(
"textbox"
,
{
name
:
"Value"
}).
fill
(
"bValue2"
);
await
po
.
page
.
getByTestId
(
"save-edit-env-var"
).
click
();
await
snapshotEnvVar
({
appPath
,
name
:
"edit-bKey"
});
// Delete first env var
await
po
.
page
.
getByTestId
(
"delete-env-var-aKey"
).
click
();
await
snapshotEnvVar
({
appPath
,
name
:
"delete-aKey"
});
});
async
function
snapshotEnvVar
({
appPath
,
name
,
}:
{
appPath
:
string
;
name
:
string
;
})
{
expect
(()
=>
{
const
envFile
=
path
.
join
(
appPath
,
".env.local"
);
const
envFileContent
=
fs
.
readFileSync
(
envFile
,
"utf8"
);
expect
(
envFileContent
).
toMatchSnapshot
({
name
});
}).
toPass
();
}
e2e-tests/helpers/test_helper.ts
浏览文件 @
2c284d0f
...
@@ -422,7 +422,7 @@ export class PageObject {
...
@@ -422,7 +422,7 @@ export class PageObject {
// Preview panel
// Preview panel
////////////////////////////////
////////////////////////////////
async
selectPreviewMode
(
mode
:
"code"
|
"problems"
|
"preview"
)
{
async
selectPreviewMode
(
mode
:
"code"
|
"problems"
|
"preview"
|
"configure"
)
{
await
this
.
page
.
getByTestId
(
`
${
mode
}
-mode-button`
).
click
();
await
this
.
page
.
getByTestId
(
`
${
mode
}
-mode-button`
).
click
();
}
}
...
...
e2e-tests/snapshots/env_var.spec.ts_create-aKey
0 → 100644
浏览文件 @
2c284d0f
aKey=aValue
\ No newline at end of file
e2e-tests/snapshots/env_var.spec.ts_create-bKey
0 → 100644
浏览文件 @
2c284d0f
aKey=aValue
bKey=bValue
\ No newline at end of file
e2e-tests/snapshots/env_var.spec.ts_delete-aKey
0 → 100644
浏览文件 @
2c284d0f
bKey=bValue2
\ No newline at end of file
e2e-tests/snapshots/env_var.spec.ts_edit-bKey
0 → 100644
浏览文件 @
2c284d0f
aKey=aValue
bKey=bValue2
\ No newline at end of file
src/__tests__/app_env_vars_utils.test.ts
0 → 100644
浏览文件 @
2c284d0f
差异被折叠。
点击展开。
src/app/TitleBar.tsx
浏览文件 @
2c284d0f
...
@@ -228,7 +228,7 @@ export function AICreditStatus({ userBudget }: { userBudget: UserBudgetInfo }) {
...
@@ -228,7 +228,7 @@ export function AICreditStatus({ userBudget }: { userBudget: UserBudgetInfo }) {
return
(
return
(
<
Tooltip
>
<
Tooltip
>
<
TooltipTrigger
>
<
TooltipTrigger
>
<
div
className=
"text-xs mt-0.5"
>
{
remaining
}
credits
left
</
div
>
<
div
className=
"text-xs mt-0.5"
>
{
remaining
}
credits
</
div
>
</
TooltipTrigger
>
</
TooltipTrigger
>
<
TooltipContent
>
<
TooltipContent
>
<
div
>
<
div
>
...
...
src/atoms/appAtoms.ts
浏览文件 @
2c284d0f
...
@@ -7,7 +7,9 @@ export const selectedAppIdAtom = atom<number | null>(null);
...
@@ -7,7 +7,9 @@ export const selectedAppIdAtom = atom<number | null>(null);
export
const
appsListAtom
=
atom
<
App
[]
>
([]);
export
const
appsListAtom
=
atom
<
App
[]
>
([]);
export
const
appBasePathAtom
=
atom
<
string
>
(
""
);
export
const
appBasePathAtom
=
atom
<
string
>
(
""
);
export
const
versionsListAtom
=
atom
<
Version
[]
>
([]);
export
const
versionsListAtom
=
atom
<
Version
[]
>
([]);
export
const
previewModeAtom
=
atom
<
"preview"
|
"code"
|
"problems"
>
(
"preview"
);
export
const
previewModeAtom
=
atom
<
"preview"
|
"code"
|
"problems"
|
"configure"
>
(
"preview"
);
export
const
selectedVersionIdAtom
=
atom
<
string
|
null
>
(
null
);
export
const
selectedVersionIdAtom
=
atom
<
string
|
null
>
(
null
);
export
const
appOutputAtom
=
atom
<
AppOutput
[]
>
([]);
export
const
appOutputAtom
=
atom
<
AppOutput
[]
>
([]);
export
const
appUrlAtom
=
atom
<
export
const
appUrlAtom
=
atom
<
...
...
src/components/preview_panel/ConfigurePanel.tsx
0 → 100644
浏览文件 @
2c284d0f
差异被折叠。
点击展开。
src/components/preview_panel/PreviewHeader.tsx
浏览文件 @
2c284d0f
...
@@ -9,6 +9,7 @@ import {
...
@@ -9,6 +9,7 @@ import {
Cog
,
Cog
,
Trash2
,
Trash2
,
AlertTriangle
,
AlertTriangle
,
Wrench
,
}
from
"lucide-react"
;
}
from
"lucide-react"
;
import
{
motion
}
from
"framer-motion"
;
import
{
motion
}
from
"framer-motion"
;
import
{
useEffect
,
useRef
,
useState
,
useCallback
}
from
"react"
;
import
{
useEffect
,
useRef
,
useState
,
useCallback
}
from
"react"
;
...
@@ -25,7 +26,7 @@ import { useMutation } from "@tanstack/react-query";
...
@@ -25,7 +26,7 @@ import { useMutation } from "@tanstack/react-query";
import
{
useCheckProblems
}
from
"@/hooks/useCheckProblems"
;
import
{
useCheckProblems
}
from
"@/hooks/useCheckProblems"
;
import
{
isPreviewOpenAtom
}
from
"@/atoms/viewAtoms"
;
import
{
isPreviewOpenAtom
}
from
"@/atoms/viewAtoms"
;
export
type
PreviewMode
=
"preview"
|
"code"
|
"problems"
;
export
type
PreviewMode
=
"preview"
|
"code"
|
"problems"
|
"configure"
;
// Preview Header component with preview mode toggle
// Preview Header component with preview mode toggle
export
const
PreviewHeader
=
()
=>
{
export
const
PreviewHeader
=
()
=>
{
...
@@ -35,6 +36,7 @@ export const PreviewHeader = () => {
...
@@ -35,6 +36,7 @@ export const PreviewHeader = () => {
const
previewRef
=
useRef
<
HTMLButtonElement
>
(
null
);
const
previewRef
=
useRef
<
HTMLButtonElement
>
(
null
);
const
codeRef
=
useRef
<
HTMLButtonElement
>
(
null
);
const
codeRef
=
useRef
<
HTMLButtonElement
>
(
null
);
const
problemsRef
=
useRef
<
HTMLButtonElement
>
(
null
);
const
problemsRef
=
useRef
<
HTMLButtonElement
>
(
null
);
const
configureRef
=
useRef
<
HTMLButtonElement
>
(
null
);
const
[
indicatorStyle
,
setIndicatorStyle
]
=
useState
({
left
:
0
,
width
:
0
});
const
[
indicatorStyle
,
setIndicatorStyle
]
=
useState
({
left
:
0
,
width
:
0
});
const
{
problemReport
}
=
useCheckProblems
(
selectedAppId
);
const
{
problemReport
}
=
useCheckProblems
(
selectedAppId
);
const
{
restartApp
,
refreshAppIframe
}
=
useRunApp
();
const
{
restartApp
,
refreshAppIframe
}
=
useRunApp
();
...
@@ -101,6 +103,9 @@ export const PreviewHeader = () => {
...
@@ -101,6 +103,9 @@ export const PreviewHeader = () => {
case
"problems"
:
case
"problems"
:
targetRef
=
problemsRef
;
targetRef
=
problemsRef
;
break
;
break
;
case
"configure"
:
targetRef
=
configureRef
;
break
;
default
:
default
:
return
;
return
;
}
}
...
@@ -146,7 +151,7 @@ export const PreviewHeader = () => {
...
@@ -146,7 +151,7 @@ export const PreviewHeader = () => {
<
button
<
button
data
-
testid=
"preview-mode-button"
data
-
testid=
"preview-mode-button"
ref=
{
previewRef
}
ref=
{
previewRef
}
className=
"cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10"
className=
"cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10
hover:bg-[var(--background)]
"
onClick=
{
()
=>
selectPanel
(
"preview"
)
}
onClick=
{
()
=>
selectPanel
(
"preview"
)
}
>
>
<
Eye
size=
{
14
}
/>
<
Eye
size=
{
14
}
/>
...
@@ -155,7 +160,7 @@ export const PreviewHeader = () => {
...
@@ -155,7 +160,7 @@ export const PreviewHeader = () => {
<
button
<
button
data
-
testid=
"problems-mode-button"
data
-
testid=
"problems-mode-button"
ref=
{
problemsRef
}
ref=
{
problemsRef
}
className=
"cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10"
className=
"cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10
hover:bg-[var(--background)]
"
onClick=
{
()
=>
selectPanel
(
"problems"
)
}
onClick=
{
()
=>
selectPanel
(
"problems"
)
}
>
>
<
AlertTriangle
size=
{
14
}
/>
<
AlertTriangle
size=
{
14
}
/>
...
@@ -166,15 +171,25 @@ export const PreviewHeader = () => {
...
@@ -166,15 +171,25 @@ export const PreviewHeader = () => {
</
span
>
</
span
>
)
}
)
}
</
button
>
</
button
>
<
button
<
button
data
-
testid=
"code-mode-button"
data
-
testid=
"code-mode-button"
ref=
{
codeRef
}
ref=
{
codeRef
}
className=
"cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10"
className=
"cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10
hover:bg-[var(--background)]
"
onClick=
{
()
=>
selectPanel
(
"code"
)
}
onClick=
{
()
=>
selectPanel
(
"code"
)
}
>
>
<
Code
size=
{
14
}
/>
<
Code
size=
{
14
}
/>
<
span
>
Code
</
span
>
<
span
>
Code
</
span
>
</
button
>
</
button
>
<
button
data
-
testid=
"configure-mode-button"
ref=
{
configureRef
}
className=
"cursor-pointer relative flex items-center gap-1 px-2 py-1 rounded-md text-sm font-medium z-10 hover:bg-[var(--background)]"
onClick=
{
()
=>
selectPanel
(
"configure"
)
}
>
<
Wrench
size=
{
14
}
/>
<
span
>
Configure
</
span
>
</
button
>
</
div
>
</
div
>
<
div
className=
"flex items-center"
>
<
div
className=
"flex items-center"
>
<
DropdownMenu
>
<
DropdownMenu
>
...
...
src/components/preview_panel/PreviewPanel.tsx
浏览文件 @
2c284d0f
...
@@ -9,6 +9,7 @@ import {
...
@@ -9,6 +9,7 @@ import {
import
{
CodeView
}
from
"./CodeView"
;
import
{
CodeView
}
from
"./CodeView"
;
import
{
PreviewIframe
}
from
"./PreviewIframe"
;
import
{
PreviewIframe
}
from
"./PreviewIframe"
;
import
{
Problems
}
from
"./Problems"
;
import
{
Problems
}
from
"./Problems"
;
import
{
ConfigurePanel
}
from
"./ConfigurePanel"
;
import
{
ChevronDown
,
ChevronUp
,
Logs
}
from
"lucide-react"
;
import
{
ChevronDown
,
ChevronUp
,
Logs
}
from
"lucide-react"
;
import
{
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
useEffect
,
useRef
,
useState
}
from
"react"
;
import
{
PanelGroup
,
Panel
,
PanelResizeHandle
}
from
"react-resizable-panels"
;
import
{
PanelGroup
,
Panel
,
PanelResizeHandle
}
from
"react-resizable-panels"
;
...
@@ -113,6 +114,8 @@ export function PreviewPanel() {
...
@@ -113,6 +114,8 @@ export function PreviewPanel() {
<
PreviewIframe
key=
{
key
}
loading=
{
loading
}
/>
<
PreviewIframe
key=
{
key
}
loading=
{
loading
}
/>
)
:
previewMode
===
"code"
?
(
)
:
previewMode
===
"code"
?
(
<
CodeView
loading=
{
loading
}
app=
{
app
}
/>
<
CodeView
loading=
{
loading
}
app=
{
app
}
/>
)
:
previewMode
===
"configure"
?
(
<
ConfigurePanel
/>
)
:
(
)
:
(
<
Problems
/>
<
Problems
/>
)
}
)
}
...
...
src/ipc/handlers/app_env_vars_handlers.ts
0 → 100644
浏览文件 @
2c284d0f
/**
* DO NOT USE LOGGER HERE.
* Environment variables are sensitive and should not be logged.
*/
import
{
ipcMain
}
from
"electron"
;
import
*
as
fs
from
"fs"
;
import
*
as
path
from
"path"
;
import
{
db
}
from
"../../db"
;
import
{
apps
}
from
"../../db/schema"
;
import
{
eq
}
from
"drizzle-orm"
;
import
{
getDyadAppPath
}
from
"../../paths/paths"
;
import
{
GetAppEnvVarsParams
,
SetAppEnvVarsParams
}
from
"../ipc_types"
;
import
{
parseEnvFile
,
serializeEnvFile
}
from
"../utils/app_env_var_utils"
;
export
function
registerAppEnvVarsHandlers
()
{
// Handler to get app environment variables
ipcMain
.
handle
(
"get-app-env-vars"
,
async
(
event
,
{
appId
}:
GetAppEnvVarsParams
)
=>
{
try
{
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
});
if
(
!
app
)
{
throw
new
Error
(
"App not found"
);
}
const
appPath
=
getDyadAppPath
(
app
.
path
);
const
envFilePath
=
path
.
join
(
appPath
,
".env.local"
);
// If .env.local doesn't exist, return empty array
try
{
await
fs
.
promises
.
access
(
envFilePath
);
}
catch
{
return
[];
}
const
content
=
await
fs
.
promises
.
readFile
(
envFilePath
,
"utf8"
);
const
envVars
=
parseEnvFile
(
content
);
return
envVars
;
}
catch
(
error
)
{
console
.
error
(
"Error getting app environment variables:"
,
error
);
throw
new
Error
(
`Failed to get environment variables:
${
error
instanceof
Error
?
error
.
message
:
"Unknown error"
}
`
,
);
}
},
);
// Handler to set app environment variables
ipcMain
.
handle
(
"set-app-env-vars"
,
async
(
event
,
{
appId
,
envVars
}:
SetAppEnvVarsParams
)
=>
{
try
{
const
app
=
await
db
.
query
.
apps
.
findFirst
({
where
:
eq
(
apps
.
id
,
appId
),
});
if
(
!
app
)
{
throw
new
Error
(
"App not found"
);
}
const
appPath
=
getDyadAppPath
(
app
.
path
);
const
envFilePath
=
path
.
join
(
appPath
,
".env.local"
);
// Serialize environment variables to .env.local format
const
content
=
serializeEnvFile
(
envVars
);
// Write to .env.local file
await
fs
.
promises
.
writeFile
(
envFilePath
,
content
,
"utf8"
);
}
catch
(
error
)
{
console
.
error
(
"Error setting app environment variables:"
,
error
);
throw
new
Error
(
`Failed to set environment variables:
${
error
instanceof
Error
?
error
.
message
:
"Unknown error"
}
`
,
);
}
},
);
}
src/ipc/ipc_client.ts
浏览文件 @
2c284d0f
...
@@ -38,6 +38,8 @@ import type {
...
@@ -38,6 +38,8 @@ import type {
AppUpgrade
,
AppUpgrade
,
ProblemReport
,
ProblemReport
,
EditAppFileReturnType
,
EditAppFileReturnType
,
GetAppEnvVarsParams
,
SetAppEnvVarsParams
,
}
from
"./ipc_types"
;
}
from
"./ipc_types"
;
import
type
{
AppChatContext
,
ProposalResult
}
from
"@/lib/schemas"
;
import
type
{
AppChatContext
,
ProposalResult
}
from
"@/lib/schemas"
;
import
{
showError
}
from
"@/lib/toast"
;
import
{
showError
}
from
"@/lib/toast"
;
...
@@ -183,6 +185,16 @@ export class IpcClient {
...
@@ -183,6 +185,16 @@ export class IpcClient {
return
this
.
ipcRenderer
.
invoke
(
"get-app"
,
appId
);
return
this
.
ipcRenderer
.
invoke
(
"get-app"
,
appId
);
}
}
public
async
getAppEnvVars
(
params
:
GetAppEnvVarsParams
,
):
Promise
<
{
key
:
string
;
value
:
string
}[]
>
{
return
this
.
ipcRenderer
.
invoke
(
"get-app-env-vars"
,
params
);
}
public
async
setAppEnvVars
(
params
:
SetAppEnvVarsParams
):
Promise
<
void
>
{
return
this
.
ipcRenderer
.
invoke
(
"set-app-env-vars"
,
params
);
}
public
async
getChat
(
chatId
:
number
):
Promise
<
Chat
>
{
public
async
getChat
(
chatId
:
number
):
Promise
<
Chat
>
{
try
{
try
{
const
data
=
await
this
.
ipcRenderer
.
invoke
(
"get-chat"
,
chatId
);
const
data
=
await
this
.
ipcRenderer
.
invoke
(
"get-chat"
,
chatId
);
...
...
src/ipc/ipc_host.ts
浏览文件 @
2c284d0f
...
@@ -23,6 +23,7 @@ import { registerContextPathsHandlers } from "./handlers/context_paths_handlers"
...
@@ -23,6 +23,7 @@ import { registerContextPathsHandlers } from "./handlers/context_paths_handlers"
import
{
registerAppUpgradeHandlers
}
from
"./handlers/app_upgrade_handlers"
;
import
{
registerAppUpgradeHandlers
}
from
"./handlers/app_upgrade_handlers"
;
import
{
registerCapacitorHandlers
}
from
"./handlers/capacitor_handlers"
;
import
{
registerCapacitorHandlers
}
from
"./handlers/capacitor_handlers"
;
import
{
registerProblemsHandlers
}
from
"./handlers/problems_handlers"
;
import
{
registerProblemsHandlers
}
from
"./handlers/problems_handlers"
;
import
{
registerAppEnvVarsHandlers
}
from
"./handlers/app_env_vars_handlers"
;
export
function
registerIpcHandlers
()
{
export
function
registerIpcHandlers
()
{
// Register all IPC handlers by category
// Register all IPC handlers by category
...
@@ -51,4 +52,5 @@ export function registerIpcHandlers() {
...
@@ -51,4 +52,5 @@ export function registerIpcHandlers() {
registerContextPathsHandlers
();
registerContextPathsHandlers
();
registerAppUpgradeHandlers
();
registerAppUpgradeHandlers
();
registerCapacitorHandlers
();
registerCapacitorHandlers
();
registerAppEnvVarsHandlers
();
}
}
src/ipc/ipc_types.ts
浏览文件 @
2c284d0f
...
@@ -252,3 +252,17 @@ export interface AppUpgrade {
...
@@ -252,3 +252,17 @@ export interface AppUpgrade {
export
interface
EditAppFileReturnType
{
export
interface
EditAppFileReturnType
{
warning
?:
string
;
warning
?:
string
;
}
}
export
interface
EnvVar
{
key
:
string
;
value
:
string
;
}
export
interface
SetAppEnvVarsParams
{
appId
:
number
;
envVars
:
EnvVar
[];
}
export
interface
GetAppEnvVarsParams
{
appId
:
number
;
}
src/ipc/utils/app_env_var_utils.ts
0 → 100644
浏览文件 @
2c284d0f
/**
* DO NOT USE LOGGER HERE.
* Environment variables are sensitive and should not be logged.
*/
import
{
EnvVar
}
from
"../ipc_types"
;
// Helper function to parse .env.local file content
export
function
parseEnvFile
(
content
:
string
):
EnvVar
[]
{
const
envVars
:
EnvVar
[]
=
[];
const
lines
=
content
.
split
(
"
\
n"
);
for
(
const
line
of
lines
)
{
const
trimmedLine
=
line
.
trim
();
// Skip empty lines and comments
if
(
!
trimmedLine
||
trimmedLine
.
startsWith
(
"#"
))
{
continue
;
}
// Parse key=value pairs
const
equalIndex
=
trimmedLine
.
indexOf
(
"="
);
if
(
equalIndex
>
0
)
{
const
key
=
trimmedLine
.
substring
(
0
,
equalIndex
).
trim
();
const
value
=
trimmedLine
.
substring
(
equalIndex
+
1
).
trim
();
// Handle quoted values with potential inline comments
let
cleanValue
=
value
;
if
(
value
.
startsWith
(
'"'
))
{
// Find the closing quote, handling escaped quotes
let
endQuoteIndex
=
-
1
;
for
(
let
i
=
1
;
i
<
value
.
length
;
i
++
)
{
if
(
value
[
i
]
===
'"'
&&
value
[
i
-
1
]
!==
"
\
\"
) {
endQuoteIndex = i;
break;
}
}
if (endQuoteIndex !== -1) {
cleanValue = value.slice(1, endQuoteIndex);
// Unescape escaped quotes
cleanValue = cleanValue.replace(/
\
\"
/g, '"
');
}
} else if (value.startsWith("'
")) {
// Find the closing quote for single quotes
const endQuoteIndex = value.indexOf("
'", 1);
if (endQuoteIndex !== -1) {
cleanValue = value.slice(1, endQuoteIndex);
}
}
// For unquoted values, keep everything as-is (including potential # symbols)
envVars.push({ key, value: cleanValue });
}
}
return envVars;
}
// Helper function to serialize environment variables to .env.local format
export function serializeEnvFile(envVars: EnvVar[]): string {
return envVars
.map(({ key, value }) => {
// Add quotes if value contains spaces or special characters
const needsQuotes = /[
\
s#"'
=&
?]
/
.
test
(
value
);
const
quotedValue
=
needsQuotes
?
`"
${
value
.
replace
(
/"/g
,
'
\\
"'
)}
"`
:
value
;
return
`
${
key
}
=
${
quotedValue
}
`
;
})
.
join
(
"
\
n"
);
}
src/preload.ts
浏览文件 @
2c284d0f
...
@@ -26,6 +26,8 @@ const validInvokeChannels = [
...
@@ -26,6 +26,8 @@ const validInvokeChannels = [
"get-chat-logs"
,
"get-chat-logs"
,
"list-apps"
,
"list-apps"
,
"get-app"
,
"get-app"
,
"get-app-env-vars"
,
"set-app-env-vars"
,
"edit-app-file"
,
"edit-app-file"
,
"read-app-file"
,
"read-app-file"
,
"run-app"
,
"run-app"
,
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论