@akiojin/gwt 2.13.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +41 -0
- package/README.md +38 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +17 -11
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/components/App.d.ts +0 -6
- package/dist/cli/ui/components/App.d.ts.map +1 -1
- package/dist/cli/ui/components/App.js +27 -88
- package/dist/cli/ui/components/App.js.map +1 -1
- package/dist/cli/ui/components/common/Select.js +2 -2
- package/dist/cli/ui/components/common/Select.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.d.ts.map +1 -1
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.js +3 -3
- package/dist/cli/ui/components/screens/BranchQuickStartScreen.js.map +1 -1
- package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
- package/dist/cli/ui/utils/continueSession.js +1 -1
- package/dist/cli/ui/utils/continueSession.js.map +1 -1
- package/dist/client/assets/index-DsDNCy5f.css +1 -0
- package/dist/client/assets/index-f5D2XwDh.js +72 -0
- package/dist/client/index.html +2 -2
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +21 -11
- package/dist/codex.js.map +1 -1
- package/dist/config/builtin-tools.d.ts.map +1 -1
- package/dist/config/builtin-tools.js +3 -2
- package/dist/config/builtin-tools.js.map +1 -1
- package/dist/config/shared-env.d.ts +41 -0
- package/dist/config/shared-env.d.ts.map +1 -0
- package/dist/config/shared-env.js +114 -0
- package/dist/config/shared-env.js.map +1 -0
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +20 -17
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -8
- package/dist/index.js.map +1 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +4 -1
- package/dist/logging/logger.js.map +1 -1
- package/dist/qwen.d.ts.map +1 -1
- package/dist/qwen.js +15 -11
- package/dist/qwen.js.map +1 -1
- package/dist/services/aiToolResolver.d.ts +41 -0
- package/dist/services/aiToolResolver.d.ts.map +1 -0
- package/dist/services/aiToolResolver.js +194 -0
- package/dist/services/aiToolResolver.js.map +1 -0
- package/dist/services/customToolResolver.d.ts +10 -0
- package/dist/services/customToolResolver.d.ts.map +1 -0
- package/dist/services/customToolResolver.js +71 -0
- package/dist/services/customToolResolver.js.map +1 -0
- package/dist/shared/aiToolConstants.d.ts +9 -0
- package/dist/shared/aiToolConstants.d.ts.map +1 -0
- package/dist/shared/aiToolConstants.js +29 -0
- package/dist/shared/aiToolConstants.js.map +1 -0
- package/dist/types/tools.d.ts +12 -0
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/utils/prompt.d.ts.map +1 -1
- package/dist/utils/prompt.js.map +1 -1
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +15 -6
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/terminal.d.ts +12 -3
- package/dist/utils/terminal.d.ts.map +1 -1
- package/dist/utils/terminal.js +5 -34
- package/dist/utils/terminal.js.map +1 -1
- package/dist/utils/webui.d.ts +8 -0
- package/dist/utils/webui.d.ts.map +1 -0
- package/dist/utils/webui.js +35 -0
- package/dist/utils/webui.js.map +1 -0
- package/dist/web/client/src/components/AIToolLaunchModal.d.ts +9 -0
- package/dist/web/client/src/components/AIToolLaunchModal.d.ts.map +1 -0
- package/dist/web/client/src/components/AIToolLaunchModal.js +363 -0
- package/dist/web/client/src/components/AIToolLaunchModal.js.map +1 -0
- package/dist/web/client/src/components/BranchGraph.d.ts.map +1 -1
- package/dist/web/client/src/components/BranchGraph.js +46 -49
- package/dist/web/client/src/components/BranchGraph.js.map +1 -1
- package/dist/web/client/src/components/CustomToolForm.d.ts +23 -0
- package/dist/web/client/src/components/CustomToolForm.d.ts.map +1 -0
- package/dist/web/client/src/components/CustomToolForm.js +209 -0
- package/dist/web/client/src/components/CustomToolForm.js.map +1 -0
- package/dist/web/client/src/components/CustomToolList.d.ts +10 -0
- package/dist/web/client/src/components/CustomToolList.d.ts.map +1 -0
- package/dist/web/client/src/components/CustomToolList.js +57 -0
- package/dist/web/client/src/components/CustomToolList.js.map +1 -0
- package/dist/web/client/src/components/EnvEditor.d.ts.map +1 -1
- package/dist/web/client/src/components/EnvEditor.js +33 -26
- package/dist/web/client/src/components/EnvEditor.js.map +1 -1
- package/dist/web/client/src/components/EnvironmentEditor.d.ts +17 -0
- package/dist/web/client/src/components/EnvironmentEditor.d.ts.map +1 -0
- package/dist/web/client/src/components/EnvironmentEditor.js +22 -0
- package/dist/web/client/src/components/EnvironmentEditor.js.map +1 -0
- package/dist/web/client/src/components/Terminal.d.ts.map +1 -1
- package/dist/web/client/src/components/Terminal.js +10 -3
- package/dist/web/client/src/components/Terminal.js.map +1 -1
- package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts +10 -0
- package/dist/web/client/src/components/branch-detail/BranchInfoCards.d.ts.map +1 -0
- package/dist/web/client/src/components/branch-detail/BranchInfoCards.js +104 -0
- package/dist/web/client/src/components/branch-detail/BranchInfoCards.js.map +1 -0
- package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts +22 -0
- package/dist/web/client/src/components/branch-detail/SessionHistoryTable.d.ts.map +1 -0
- package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js +79 -0
- package/dist/web/client/src/components/branch-detail/SessionHistoryTable.js.map +1 -0
- package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts +11 -0
- package/dist/web/client/src/components/branch-detail/TerminalPanel.d.ts.map +1 -0
- package/dist/web/client/src/components/branch-detail/TerminalPanel.js +32 -0
- package/dist/web/client/src/components/branch-detail/TerminalPanel.js.map +1 -0
- package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts +40 -0
- package/dist/web/client/src/components/branch-detail/ToolLauncher.d.ts.map +1 -0
- package/dist/web/client/src/components/branch-detail/ToolLauncher.js +147 -0
- package/dist/web/client/src/components/branch-detail/ToolLauncher.js.map +1 -0
- package/dist/web/client/src/components/branch-detail/index.d.ts +5 -0
- package/dist/web/client/src/components/branch-detail/index.d.ts.map +1 -0
- package/dist/web/client/src/components/branch-detail/index.js +5 -0
- package/dist/web/client/src/components/branch-detail/index.js.map +1 -0
- package/dist/web/client/src/components/common/BranchCard.d.ts +17 -0
- package/dist/web/client/src/components/common/BranchCard.d.ts.map +1 -0
- package/dist/web/client/src/components/common/BranchCard.js +36 -0
- package/dist/web/client/src/components/common/BranchCard.js.map +1 -0
- package/dist/web/client/src/components/common/MetricCard.d.ts +10 -0
- package/dist/web/client/src/components/common/MetricCard.d.ts.map +1 -0
- package/dist/web/client/src/components/common/MetricCard.js +10 -0
- package/dist/web/client/src/components/common/MetricCard.js.map +1 -0
- package/dist/web/client/src/components/common/PageHeader.d.ts +12 -0
- package/dist/web/client/src/components/common/PageHeader.d.ts.map +1 -0
- package/dist/web/client/src/components/common/PageHeader.js +14 -0
- package/dist/web/client/src/components/common/PageHeader.js.map +1 -0
- package/dist/web/client/src/components/common/SearchInput.d.ts +14 -0
- package/dist/web/client/src/components/common/SearchInput.d.ts.map +1 -0
- package/dist/web/client/src/components/common/SearchInput.js +15 -0
- package/dist/web/client/src/components/common/SearchInput.js.map +1 -0
- package/dist/web/client/src/components/common/StatusBadge.d.ts +10 -0
- package/dist/web/client/src/components/common/StatusBadge.d.ts.map +1 -0
- package/dist/web/client/src/components/common/StatusBadge.js +15 -0
- package/dist/web/client/src/components/common/StatusBadge.js.map +1 -0
- package/dist/web/client/src/components/common/index.d.ts +6 -0
- package/dist/web/client/src/components/common/index.d.ts.map +1 -0
- package/dist/web/client/src/components/common/index.js +6 -0
- package/dist/web/client/src/components/common/index.js.map +1 -0
- package/dist/web/client/src/components/ui/alert.d.ts +9 -0
- package/dist/web/client/src/components/ui/alert.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/alert.js +25 -0
- package/dist/web/client/src/components/ui/alert.js.map +1 -0
- package/dist/web/client/src/components/ui/badge.d.ts +10 -0
- package/dist/web/client/src/components/ui/badge.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/badge.js +25 -0
- package/dist/web/client/src/components/ui/badge.js.map +1 -0
- package/dist/web/client/src/components/ui/button.d.ts +12 -0
- package/dist/web/client/src/components/ui/button.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/button.js +33 -0
- package/dist/web/client/src/components/ui/button.js.map +1 -0
- package/dist/web/client/src/components/ui/card.d.ts +9 -0
- package/dist/web/client/src/components/ui/card.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/card.js +16 -0
- package/dist/web/client/src/components/ui/card.js.map +1 -0
- package/dist/web/client/src/components/ui/index.d.ts +8 -0
- package/dist/web/client/src/components/ui/index.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/index.js +8 -0
- package/dist/web/client/src/components/ui/index.js.map +1 -0
- package/dist/web/client/src/components/ui/input.d.ts +4 -0
- package/dist/web/client/src/components/ui/input.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/input.js +8 -0
- package/dist/web/client/src/components/ui/input.js.map +1 -0
- package/dist/web/client/src/components/ui/select.d.ts +14 -0
- package/dist/web/client/src/components/ui/select.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/select.js +39 -0
- package/dist/web/client/src/components/ui/select.js.map +1 -0
- package/dist/web/client/src/components/ui/table.d.ts +11 -0
- package/dist/web/client/src/components/ui/table.d.ts.map +1 -0
- package/dist/web/client/src/components/ui/table.js +21 -0
- package/dist/web/client/src/components/ui/table.js.map +1 -0
- package/dist/web/client/src/hooks/useSessions.d.ts.map +1 -1
- package/dist/web/client/src/hooks/useSessions.js +6 -1
- package/dist/web/client/src/hooks/useSessions.js.map +1 -1
- package/dist/web/client/src/lib/utils.d.ts +7 -0
- package/dist/web/client/src/lib/utils.d.ts.map +1 -0
- package/dist/web/client/src/lib/utils.js +10 -0
- package/dist/web/client/src/lib/utils.js.map +1 -0
- package/dist/web/client/src/lib/websocket.d.ts +7 -0
- package/dist/web/client/src/lib/websocket.d.ts.map +1 -1
- package/dist/web/client/src/lib/websocket.js +44 -0
- package/dist/web/client/src/lib/websocket.js.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchDetailPage.js +125 -376
- package/dist/web/client/src/pages/BranchDetailPage.js.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/BranchListPage.js +89 -127
- package/dist/web/client/src/pages/BranchListPage.js.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.d.ts.map +1 -1
- package/dist/web/client/src/pages/ConfigManagementPage.js +46 -41
- package/dist/web/client/src/pages/ConfigManagementPage.js.map +1 -1
- package/dist/web/client/src/pages/ConfigPage.d.ts +3 -0
- package/dist/web/client/src/pages/ConfigPage.d.ts.map +1 -0
- package/dist/web/client/src/pages/ConfigPage.js +216 -0
- package/dist/web/client/src/pages/ConfigPage.js.map +1 -0
- package/dist/web/client/vite.config.d.ts.map +1 -1
- package/dist/web/client/vite.config.js +8 -1
- package/dist/web/client/vite.config.js.map +1 -1
- package/dist/web/server/index.d.ts +24 -2
- package/dist/web/server/index.d.ts.map +1 -1
- package/dist/web/server/index.js +46 -15
- package/dist/web/server/index.js.map +1 -1
- package/dist/web/server/pty/manager.d.ts +12 -10
- package/dist/web/server/pty/manager.d.ts.map +1 -1
- package/dist/web/server/pty/manager.js +76 -43
- package/dist/web/server/pty/manager.js.map +1 -1
- package/dist/web/server/routes/sessions.d.ts.map +1 -1
- package/dist/web/server/routes/sessions.js +35 -2
- package/dist/web/server/routes/sessions.js.map +1 -1
- package/dist/web/server/routes/worktrees.js +2 -2
- package/dist/web/server/routes/worktrees.js.map +1 -1
- package/dist/web/server/services/worktrees.d.ts.map +1 -1
- package/dist/web/server/services/worktrees.js +7 -1
- package/dist/web/server/services/worktrees.js.map +1 -1
- package/dist/web/server/tray.d.ts +25 -0
- package/dist/web/server/tray.d.ts.map +1 -0
- package/dist/web/server/tray.js +98 -0
- package/dist/web/server/tray.js.map +1 -0
- package/dist/web/server/websocket/handler.d.ts +18 -2
- package/dist/web/server/websocket/handler.d.ts.map +1 -1
- package/dist/web/server/websocket/handler.js +82 -9
- package/dist/web/server/websocket/handler.js.map +1 -1
- package/package.json +15 -2
- package/src/claude.ts +26 -15
- package/src/cli/ui/__tests__/components/App.protected-branch.test.tsx +1 -1
- package/src/cli/ui/__tests__/components/common/Select.test.tsx +11 -0
- package/src/cli/ui/__tests__/components/screens/BranchListScreen.test.tsx +3 -1
- package/src/cli/ui/__tests__/components/screens/BranchQuickStartScreen.test.tsx +4 -4
- package/src/cli/ui/components/App.tsx +33 -133
- package/src/cli/ui/components/common/Select.tsx +2 -2
- package/src/cli/ui/components/screens/BranchListScreen.tsx +28 -21
- package/src/cli/ui/components/screens/BranchQuickStartScreen.tsx +41 -46
- package/src/cli/ui/utils/continueSession.ts +1 -7
- package/src/codex.ts +31 -22
- package/src/config/builtin-tools.ts +6 -2
- package/src/config/shared-env.ts +139 -0
- package/src/gemini.ts +35 -22
- package/src/index.ts +13 -8
- package/src/logging/logger.ts +5 -2
- package/src/qwen.ts +28 -19
- package/src/services/aiToolResolver.ts +276 -0
- package/src/services/customToolResolver.ts +98 -0
- package/src/shared/aiToolConstants.ts +30 -0
- package/src/trayicon.d.ts +30 -0
- package/src/types/tools.ts +15 -0
- package/src/utils/prompt.ts +15 -9
- package/src/utils/session.ts +80 -26
- package/src/utils/terminal.ts +11 -41
- package/src/utils/webui.ts +43 -0
- package/src/web/client/components.json +21 -0
- package/src/web/client/src/components/AIToolLaunchModal.tsx +575 -0
- package/src/web/client/src/components/BranchGraph.tsx +95 -75
- package/src/web/client/src/components/CustomToolForm.tsx +386 -0
- package/src/web/client/src/components/CustomToolList.tsx +119 -0
- package/src/web/client/src/components/EnvEditor.tsx +91 -81
- package/src/web/client/src/components/EnvironmentEditor.tsx +97 -0
- package/src/web/client/src/components/Terminal.tsx +11 -3
- package/src/web/client/src/components/branch-detail/BranchInfoCards.tsx +179 -0
- package/src/web/client/src/components/branch-detail/SessionHistoryTable.tsx +181 -0
- package/src/web/client/src/components/branch-detail/TerminalPanel.tsx +92 -0
- package/src/web/client/src/components/branch-detail/ToolLauncher.tsx +327 -0
- package/src/web/client/src/components/branch-detail/index.ts +4 -0
- package/src/web/client/src/components/common/BranchCard.tsx +117 -0
- package/src/web/client/src/components/common/MetricCard.tsx +22 -0
- package/src/web/client/src/components/common/PageHeader.tsx +44 -0
- package/src/web/client/src/components/common/SearchInput.tsx +40 -0
- package/src/web/client/src/components/common/StatusBadge.tsx +37 -0
- package/src/web/client/src/components/common/index.ts +5 -0
- package/src/web/client/src/components/ui/alert.tsx +63 -0
- package/src/web/client/src/components/ui/badge.tsx +44 -0
- package/src/web/client/src/components/ui/button.tsx +57 -0
- package/src/web/client/src/components/ui/card.tsx +82 -0
- package/src/web/client/src/components/ui/index.ts +32 -0
- package/src/web/client/src/components/ui/input.tsx +21 -0
- package/src/web/client/src/components/ui/select.tsx +156 -0
- package/src/web/client/src/components/ui/table.tsx +119 -0
- package/src/web/client/src/hooks/useSessions.ts +10 -1
- package/src/web/client/src/index.css +46 -816
- package/src/web/client/src/lib/utils.ts +10 -0
- package/src/web/client/src/lib/websocket.ts +48 -1
- package/src/web/client/src/pages/BranchDetailPage.tsx +247 -723
- package/src/web/client/src/pages/BranchListPage.tsx +190 -236
- package/src/web/client/src/pages/ConfigManagementPage.tsx +94 -76
- package/src/web/client/src/pages/ConfigPage.tsx +362 -0
- package/src/web/client/vite.config.ts +8 -1
- package/src/web/server/index.ts +72 -15
- package/src/web/server/pty/manager.ts +128 -55
- package/src/web/server/routes/sessions.ts +59 -7
- package/src/web/server/routes/worktrees.ts +3 -3
- package/src/web/server/services/worktrees.ts +12 -4
- package/src/web/server/tray.ts +120 -0
- package/src/web/server/websocket/handler.ts +119 -13
- package/dist/client/assets/index-DeNwPosA.css +0 -1
- package/dist/client/assets/index-Dl798X5w.js +0 -32
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
2
|
import { Link } from "react-router-dom";
|
|
3
|
+
import { Card, CardHeader, CardContent } from "@/components/ui/card";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
3
6
|
import type { Branch } from "../../../../types/api.js";
|
|
4
7
|
|
|
5
8
|
const UNKNOWN_BASE = "__unknown__";
|
|
@@ -55,7 +58,6 @@ export function BranchGraph({ branches }: BranchGraphProps) {
|
|
|
55
58
|
const base = branch.baseBranch ?? UNKNOWN_BASE;
|
|
56
59
|
|
|
57
60
|
if (!branch.baseBranch && referencedBases.has(branch.name)) {
|
|
58
|
-
// ベースとして参照されている場合は、グラフ上で基点ノードとしてのみ表示
|
|
59
61
|
return;
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -75,71 +77,83 @@ export function BranchGraph({ branches }: BranchGraphProps) {
|
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
return Array.from(laneMap.values()).sort((a, b) => {
|
|
78
|
-
if (a.id === UNKNOWN_BASE)
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
if (b.id === UNKNOWN_BASE) {
|
|
82
|
-
return -1;
|
|
83
|
-
}
|
|
80
|
+
if (a.id === UNKNOWN_BASE) return 1;
|
|
81
|
+
if (b.id === UNKNOWN_BASE) return -1;
|
|
84
82
|
return a.baseLabel.localeCompare(b.baseLabel, "ja");
|
|
85
83
|
});
|
|
86
84
|
}, [branches, branchMap, referencedBases]);
|
|
87
85
|
|
|
88
86
|
if (!lanes.length) {
|
|
89
87
|
return (
|
|
90
|
-
<
|
|
91
|
-
<
|
|
92
|
-
<p
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
<Card className="border-dashed">
|
|
89
|
+
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
|
90
|
+
<p className="text-muted-foreground">
|
|
91
|
+
グラフ表示できるブランチがありません。
|
|
92
|
+
</p>
|
|
93
|
+
<p className="text-sm text-muted-foreground">
|
|
94
|
+
fetch済みのブランチやWorktreeを追加すると関係図が表示されます。
|
|
95
|
+
</p>
|
|
96
|
+
</CardContent>
|
|
97
|
+
</Card>
|
|
96
98
|
);
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
return (
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
<div>
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
<Card>
|
|
103
|
+
<CardHeader className="pb-4">
|
|
104
|
+
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
105
|
+
<div>
|
|
106
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
107
|
+
BRANCH GRAPH
|
|
108
|
+
</p>
|
|
109
|
+
<h2 className="mt-1 text-lg font-semibold">
|
|
110
|
+
ベースブランチの関係をグラフィカルに把握
|
|
111
|
+
</h2>
|
|
112
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
113
|
+
baseRef、Git
|
|
114
|
+
upstream、merge-baseヒューリスティクスを用いて推定したベースブランチ単位で派生ノードをレーン表示します。
|
|
115
|
+
</p>
|
|
116
|
+
</div>
|
|
117
|
+
<div className="flex flex-wrap gap-2">
|
|
118
|
+
<Badge variant="outline">Base</Badge>
|
|
119
|
+
<Badge variant="local">Local</Badge>
|
|
120
|
+
<Badge variant="remote">Remote</Badge>
|
|
121
|
+
<Badge variant="success">Worktree</Badge>
|
|
122
|
+
</div>
|
|
110
123
|
</div>
|
|
111
|
-
|
|
112
|
-
<span className="graph-chip graph-chip--base">Base</span>
|
|
113
|
-
<span className="graph-chip graph-chip--local">Local</span>
|
|
114
|
-
<span className="graph-chip graph-chip--remote">Remote</span>
|
|
115
|
-
<span className="graph-chip graph-chip--worktree">Worktree</span>
|
|
116
|
-
</div>
|
|
117
|
-
</header>
|
|
124
|
+
</CardHeader>
|
|
118
125
|
|
|
119
|
-
<
|
|
126
|
+
<CardContent className="space-y-4">
|
|
120
127
|
{lanes.map((lane) => (
|
|
121
|
-
<article
|
|
122
|
-
<div className="
|
|
123
|
-
<
|
|
124
|
-
{lane.baseLabel}
|
|
128
|
+
<article key={lane.id} className="rounded-lg border bg-muted/30 p-4">
|
|
129
|
+
<div className="mb-3 flex items-center justify-between">
|
|
130
|
+
<div className="flex items-center gap-2">
|
|
131
|
+
<span className="font-semibold">{lane.baseLabel}</span>
|
|
125
132
|
{lane.baseNode && (
|
|
126
|
-
<
|
|
133
|
+
<Badge
|
|
134
|
+
variant={
|
|
135
|
+
lane.baseNode.type === "local" ? "local" : "remote"
|
|
136
|
+
}
|
|
137
|
+
className="text-xs"
|
|
138
|
+
>
|
|
127
139
|
{lane.baseNode.type === "local" ? "LOCAL" : "REMOTE"}
|
|
128
|
-
</
|
|
140
|
+
</Badge>
|
|
129
141
|
)}
|
|
130
142
|
{lane.isSyntheticBase && (
|
|
131
|
-
<
|
|
143
|
+
<Badge
|
|
144
|
+
variant="outline"
|
|
145
|
+
className="text-xs text-muted-foreground"
|
|
146
|
+
>
|
|
132
147
|
推定のみ
|
|
133
|
-
</
|
|
148
|
+
</Badge>
|
|
134
149
|
)}
|
|
135
|
-
</
|
|
136
|
-
<span className="
|
|
137
|
-
{lane.nodes.length} branch
|
|
138
|
-
{lane.nodes.length > 1 ? "es" : ""}
|
|
150
|
+
</div>
|
|
151
|
+
<span className="text-sm text-muted-foreground">
|
|
152
|
+
{lane.nodes.length} branch{lane.nodes.length > 1 ? "es" : ""}
|
|
139
153
|
</span>
|
|
140
154
|
</div>
|
|
141
155
|
|
|
142
|
-
<div className="
|
|
156
|
+
<div className="flex flex-wrap gap-2">
|
|
143
157
|
{renderBaseNode(lane)}
|
|
144
158
|
{lane.nodes.map((branch) => (
|
|
145
159
|
<BranchNode key={branch.name} branch={branch} />
|
|
@@ -147,25 +161,30 @@ export function BranchGraph({ branches }: BranchGraphProps) {
|
|
|
147
161
|
</div>
|
|
148
162
|
</article>
|
|
149
163
|
))}
|
|
150
|
-
</
|
|
151
|
-
</
|
|
164
|
+
</CardContent>
|
|
165
|
+
</Card>
|
|
152
166
|
);
|
|
153
167
|
}
|
|
154
168
|
|
|
155
169
|
function renderBaseNode(lane: Lane) {
|
|
156
170
|
const label =
|
|
157
171
|
lane.baseLabel === "ベース不明" ? "Unknown base" : lane.baseLabel;
|
|
172
|
+
|
|
158
173
|
const content = (
|
|
159
174
|
<div
|
|
160
|
-
className={
|
|
161
|
-
|
|
162
|
-
|
|
175
|
+
className={cn(
|
|
176
|
+
"group relative rounded-md border bg-card px-3 py-2 transition-colors hover:border-muted-foreground/50",
|
|
177
|
+
lane.baseNode?.type === "local" && "border-l-2 border-l-local",
|
|
178
|
+
lane.baseNode?.type === "remote" && "border-l-2 border-l-remote",
|
|
179
|
+
)}
|
|
163
180
|
>
|
|
164
|
-
<span className="
|
|
165
|
-
<span className="
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
181
|
+
<span className="block truncate text-sm font-medium">{label}</span>
|
|
182
|
+
<span className="text-xs text-muted-foreground">BASE</span>
|
|
183
|
+
|
|
184
|
+
{/* Tooltip */}
|
|
185
|
+
<div className="invisible absolute bottom-full left-0 z-10 mb-2 w-48 rounded-md border bg-popover p-2 text-xs shadow-md group-hover:visible">
|
|
186
|
+
<p className="font-medium">{label}</p>
|
|
187
|
+
<p className="text-muted-foreground">
|
|
169
188
|
{lane.baseNode
|
|
170
189
|
? `type: ${lane.baseNode.type}`
|
|
171
190
|
: "推定されたベースブランチ"}
|
|
@@ -179,7 +198,7 @@ function renderBaseNode(lane: Lane) {
|
|
|
179
198
|
<Link
|
|
180
199
|
key={`base-${lane.id}`}
|
|
181
200
|
to={`/${encodeURIComponent(lane.baseNode.name)}`}
|
|
182
|
-
className="
|
|
201
|
+
className="block"
|
|
183
202
|
aria-label={`ベースブランチ ${lane.baseNode.name} を開く`}
|
|
184
203
|
>
|
|
185
204
|
{content}
|
|
@@ -187,35 +206,36 @@ function renderBaseNode(lane: Lane) {
|
|
|
187
206
|
);
|
|
188
207
|
}
|
|
189
208
|
|
|
190
|
-
return
|
|
191
|
-
<div key={`base-${lane.id}`} className="branch-graph__node-link">
|
|
192
|
-
{content}
|
|
193
|
-
</div>
|
|
194
|
-
);
|
|
209
|
+
return <div key={`base-${lane.id}`}>{content}</div>;
|
|
195
210
|
}
|
|
196
211
|
|
|
197
212
|
function BranchNode({ branch }: { branch: Branch }) {
|
|
198
213
|
const node = (
|
|
199
214
|
<div
|
|
200
|
-
className={
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}`}
|
|
215
|
+
className={cn(
|
|
216
|
+
"group relative rounded-md border bg-card px-3 py-2 transition-colors hover:border-muted-foreground/50",
|
|
217
|
+
branch.type === "local" && "border-l-2 border-l-local",
|
|
218
|
+
branch.type === "remote" && "border-l-2 border-l-remote",
|
|
219
|
+
branch.mergeStatus === "merged" && "opacity-60",
|
|
220
|
+
)}
|
|
207
221
|
>
|
|
208
|
-
<span className="
|
|
222
|
+
<span className="block truncate text-sm font-medium">
|
|
209
223
|
{formatBranchLabel(branch)}
|
|
210
224
|
</span>
|
|
211
|
-
<span className="
|
|
225
|
+
<span className="text-xs text-muted-foreground">
|
|
212
226
|
{branch.worktreePath ? "Worktree" : "No Worktree"}
|
|
213
227
|
</span>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
<p>{
|
|
218
|
-
<p
|
|
228
|
+
|
|
229
|
+
{/* Tooltip */}
|
|
230
|
+
<div className="invisible absolute bottom-full left-0 z-10 mb-2 w-56 rounded-md border bg-popover p-2 text-xs shadow-md group-hover:visible">
|
|
231
|
+
<p className="font-medium">{branch.name}</p>
|
|
232
|
+
<p className="text-muted-foreground">
|
|
233
|
+
base: {branch.baseBranch ?? "unknown"}
|
|
234
|
+
</p>
|
|
235
|
+
<p className="text-muted-foreground">{getDivergenceLabel(branch)}</p>
|
|
236
|
+
<p className="text-muted-foreground">
|
|
237
|
+
{branch.worktreePath ?? "Worktree未作成"}
|
|
238
|
+
</p>
|
|
219
239
|
</div>
|
|
220
240
|
</div>
|
|
221
241
|
);
|
|
@@ -223,7 +243,7 @@ function BranchNode({ branch }: { branch: Branch }) {
|
|
|
223
243
|
return (
|
|
224
244
|
<Link
|
|
225
245
|
to={`/${encodeURIComponent(branch.name)}`}
|
|
226
|
-
className="
|
|
246
|
+
className="block"
|
|
227
247
|
aria-label={`${branch.name} の詳細を開く`}
|
|
228
248
|
>
|
|
229
249
|
{node}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
import { Input } from "@/components/ui/input";
|
|
4
|
+
import {
|
|
5
|
+
Select,
|
|
6
|
+
SelectContent,
|
|
7
|
+
SelectItem,
|
|
8
|
+
SelectTrigger,
|
|
9
|
+
SelectValue,
|
|
10
|
+
} from "@/components/ui/select";
|
|
11
|
+
import type {
|
|
12
|
+
CustomAITool,
|
|
13
|
+
EnvironmentVariable,
|
|
14
|
+
} from "../../../../types/api.js";
|
|
15
|
+
|
|
16
|
+
export interface CustomToolFormValue {
|
|
17
|
+
id: string;
|
|
18
|
+
displayName: string;
|
|
19
|
+
icon?: string | null;
|
|
20
|
+
description?: string | null;
|
|
21
|
+
executionType: CustomAITool["executionType"];
|
|
22
|
+
command: string;
|
|
23
|
+
defaultArgs?: string[] | null;
|
|
24
|
+
modeArgs: CustomAITool["modeArgs"];
|
|
25
|
+
permissionSkipArgs?: string[] | null;
|
|
26
|
+
env?: EnvironmentVariable[] | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface CustomToolFormProps {
|
|
30
|
+
initialValue?: CustomAITool;
|
|
31
|
+
onSubmit: (value: CustomToolFormValue) => void;
|
|
32
|
+
onCancel: () => void;
|
|
33
|
+
isSaving?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface FormErrors {
|
|
37
|
+
id?: string;
|
|
38
|
+
displayName?: string;
|
|
39
|
+
command?: string;
|
|
40
|
+
env?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function CustomToolForm({
|
|
44
|
+
initialValue,
|
|
45
|
+
onSubmit,
|
|
46
|
+
onCancel,
|
|
47
|
+
isSaving,
|
|
48
|
+
}: CustomToolFormProps) {
|
|
49
|
+
const [formState, setFormState] = useState(() =>
|
|
50
|
+
createInitialState(initialValue),
|
|
51
|
+
);
|
|
52
|
+
const [errors, setErrors] = useState<FormErrors>({});
|
|
53
|
+
|
|
54
|
+
const title = initialValue ? "ツールを編集" : "新規カスタムツール";
|
|
55
|
+
|
|
56
|
+
const handleChange =
|
|
57
|
+
(field: keyof typeof formState) =>
|
|
58
|
+
(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
59
|
+
setFormState((prev) => ({ ...prev, [field]: event.target.value }));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const handleSelectChange =
|
|
63
|
+
(field: keyof typeof formState) => (value: string) => {
|
|
64
|
+
setFormState((prev) => ({ ...prev, [field]: value }));
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleSubmit = (event: React.FormEvent) => {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
const nextErrors: FormErrors = {};
|
|
70
|
+
|
|
71
|
+
if (!formState.id.trim()) {
|
|
72
|
+
nextErrors.id = "IDは必須です";
|
|
73
|
+
}
|
|
74
|
+
if (!formState.displayName.trim()) {
|
|
75
|
+
nextErrors.displayName = "表示名は必須です";
|
|
76
|
+
}
|
|
77
|
+
if (!formState.command.trim()) {
|
|
78
|
+
nextErrors.command = "コマンド/パッケージ名は必須です";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const envResult = parseEnv(formState.env);
|
|
82
|
+
let parsedEnv: EnvironmentVariable[] | null = null;
|
|
83
|
+
if (envResult instanceof Error) {
|
|
84
|
+
nextErrors.env = envResult.message;
|
|
85
|
+
} else {
|
|
86
|
+
parsedEnv = envResult;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (Object.keys(nextErrors).length) {
|
|
90
|
+
setErrors(nextErrors);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setErrors({});
|
|
95
|
+
onSubmit({
|
|
96
|
+
id: formState.id.trim(),
|
|
97
|
+
displayName: formState.displayName.trim(),
|
|
98
|
+
icon: formState.icon?.trim() ? formState.icon.trim() : null,
|
|
99
|
+
description: formState.description?.trim()
|
|
100
|
+
? formState.description.trim()
|
|
101
|
+
: null,
|
|
102
|
+
executionType: formState.executionType,
|
|
103
|
+
command: formState.command.trim(),
|
|
104
|
+
defaultArgs: parseList(formState.defaultArgs),
|
|
105
|
+
modeArgs: {
|
|
106
|
+
normal: parseList(formState.modeNormal) ?? [],
|
|
107
|
+
continue: parseList(formState.modeContinue) ?? [],
|
|
108
|
+
resume: parseList(formState.modeResume) ?? [],
|
|
109
|
+
},
|
|
110
|
+
permissionSkipArgs: parseList(formState.permissionSkipArgs),
|
|
111
|
+
env: parsedEnv,
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
117
|
+
<div className="flex items-start justify-between gap-4">
|
|
118
|
+
<div>
|
|
119
|
+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
120
|
+
{title}
|
|
121
|
+
</p>
|
|
122
|
+
<h3 className="mt-1 text-lg font-semibold">
|
|
123
|
+
{formState.displayName || "カスタムAIツール"}
|
|
124
|
+
</h3>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="flex gap-2">
|
|
127
|
+
<Button
|
|
128
|
+
type="button"
|
|
129
|
+
variant="ghost"
|
|
130
|
+
onClick={onCancel}
|
|
131
|
+
disabled={isSaving}
|
|
132
|
+
>
|
|
133
|
+
キャンセル
|
|
134
|
+
</Button>
|
|
135
|
+
<Button type="submit" disabled={isSaving}>
|
|
136
|
+
{isSaving ? "保存中..." : "保存"}
|
|
137
|
+
</Button>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
142
|
+
<div className="space-y-2">
|
|
143
|
+
<label className="text-sm font-medium">ツールID *</label>
|
|
144
|
+
<Input
|
|
145
|
+
type="text"
|
|
146
|
+
value={formState.id}
|
|
147
|
+
onChange={handleChange("id")}
|
|
148
|
+
disabled={Boolean(initialValue)}
|
|
149
|
+
/>
|
|
150
|
+
{errors.id && <p className="text-xs text-destructive">{errors.id}</p>}
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div className="space-y-2">
|
|
154
|
+
<label className="text-sm font-medium">表示名 *</label>
|
|
155
|
+
<Input
|
|
156
|
+
type="text"
|
|
157
|
+
value={formState.displayName}
|
|
158
|
+
onChange={handleChange("displayName")}
|
|
159
|
+
/>
|
|
160
|
+
{errors.displayName && (
|
|
161
|
+
<p className="text-xs text-destructive">{errors.displayName}</p>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div className="space-y-2">
|
|
166
|
+
<label className="text-sm font-medium">アイコン (任意)</label>
|
|
167
|
+
<Input
|
|
168
|
+
type="text"
|
|
169
|
+
value={formState.icon}
|
|
170
|
+
onChange={handleChange("icon")}
|
|
171
|
+
maxLength={2}
|
|
172
|
+
/>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<div className="space-y-2">
|
|
176
|
+
<label className="text-sm font-medium">説明 (任意)</label>
|
|
177
|
+
<Input
|
|
178
|
+
type="text"
|
|
179
|
+
value={formState.description}
|
|
180
|
+
onChange={handleChange("description")}
|
|
181
|
+
/>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<div className="space-y-2">
|
|
185
|
+
<label className="text-sm font-medium">実行タイプ *</label>
|
|
186
|
+
<Select
|
|
187
|
+
value={formState.executionType}
|
|
188
|
+
onValueChange={handleSelectChange("executionType")}
|
|
189
|
+
disabled={Boolean(initialValue)}
|
|
190
|
+
>
|
|
191
|
+
<SelectTrigger>
|
|
192
|
+
<SelectValue />
|
|
193
|
+
</SelectTrigger>
|
|
194
|
+
<SelectContent>
|
|
195
|
+
<SelectItem value="path">path (絶対パス)</SelectItem>
|
|
196
|
+
<SelectItem value="bunx">bunx (パッケージ)</SelectItem>
|
|
197
|
+
<SelectItem value="command">command (PATH)</SelectItem>
|
|
198
|
+
</SelectContent>
|
|
199
|
+
</Select>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div className="space-y-2">
|
|
203
|
+
<label className="text-sm font-medium">
|
|
204
|
+
{formState.executionType === "bunx" ? "パッケージ名" : "コマンド"} *
|
|
205
|
+
</label>
|
|
206
|
+
<Input
|
|
207
|
+
type="text"
|
|
208
|
+
value={formState.command}
|
|
209
|
+
onChange={handleChange("command")}
|
|
210
|
+
/>
|
|
211
|
+
{errors.command && (
|
|
212
|
+
<p className="text-xs text-destructive">{errors.command}</p>
|
|
213
|
+
)}
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
218
|
+
<div className="space-y-2">
|
|
219
|
+
<label className="text-sm font-medium">
|
|
220
|
+
defaultArgs (改行区切り)
|
|
221
|
+
</label>
|
|
222
|
+
<textarea
|
|
223
|
+
value={formState.defaultArgs}
|
|
224
|
+
onChange={handleChange("defaultArgs")}
|
|
225
|
+
rows={2}
|
|
226
|
+
className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
227
|
+
/>
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
<div className="space-y-2">
|
|
231
|
+
<label className="text-sm font-medium">
|
|
232
|
+
permissionSkipArgs (改行区切り)
|
|
233
|
+
</label>
|
|
234
|
+
<textarea
|
|
235
|
+
value={formState.permissionSkipArgs}
|
|
236
|
+
onChange={handleChange("permissionSkipArgs")}
|
|
237
|
+
rows={2}
|
|
238
|
+
className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
239
|
+
/>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<div className="grid gap-4 sm:grid-cols-3">
|
|
244
|
+
<div className="space-y-2">
|
|
245
|
+
<label className="text-sm font-medium">normalモード引数</label>
|
|
246
|
+
<textarea
|
|
247
|
+
value={formState.modeNormal}
|
|
248
|
+
onChange={handleChange("modeNormal")}
|
|
249
|
+
rows={3}
|
|
250
|
+
className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
251
|
+
/>
|
|
252
|
+
</div>
|
|
253
|
+
<div className="space-y-2">
|
|
254
|
+
<label className="text-sm font-medium">continueモード引数</label>
|
|
255
|
+
<textarea
|
|
256
|
+
value={formState.modeContinue}
|
|
257
|
+
onChange={handleChange("modeContinue")}
|
|
258
|
+
rows={3}
|
|
259
|
+
className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
260
|
+
/>
|
|
261
|
+
</div>
|
|
262
|
+
<div className="space-y-2">
|
|
263
|
+
<label className="text-sm font-medium">resumeモード引数</label>
|
|
264
|
+
<textarea
|
|
265
|
+
value={formState.modeResume}
|
|
266
|
+
onChange={handleChange("modeResume")}
|
|
267
|
+
rows={3}
|
|
268
|
+
className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<div className="space-y-2">
|
|
274
|
+
<label className="text-sm font-medium">
|
|
275
|
+
環境変数 (key=value を改行で記述)
|
|
276
|
+
</label>
|
|
277
|
+
<textarea
|
|
278
|
+
value={formState.env}
|
|
279
|
+
onChange={handleChange("env")}
|
|
280
|
+
rows={3}
|
|
281
|
+
className="flex w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
282
|
+
/>
|
|
283
|
+
{errors.env && <p className="text-xs text-destructive">{errors.env}</p>}
|
|
284
|
+
</div>
|
|
285
|
+
</form>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function createInitialState(initialValue?: CustomAITool) {
|
|
290
|
+
if (!initialValue) {
|
|
291
|
+
return {
|
|
292
|
+
id: "",
|
|
293
|
+
displayName: "",
|
|
294
|
+
icon: "",
|
|
295
|
+
description: "",
|
|
296
|
+
executionType: "bunx" as CustomAITool["executionType"],
|
|
297
|
+
command: "",
|
|
298
|
+
defaultArgs: "",
|
|
299
|
+
modeNormal: "",
|
|
300
|
+
modeContinue: "",
|
|
301
|
+
modeResume: "",
|
|
302
|
+
permissionSkipArgs: "",
|
|
303
|
+
env: "",
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
id: initialValue.id,
|
|
309
|
+
displayName: initialValue.displayName,
|
|
310
|
+
icon: initialValue.icon ?? "",
|
|
311
|
+
description: initialValue.description ?? "",
|
|
312
|
+
executionType: initialValue.executionType,
|
|
313
|
+
command: initialValue.command,
|
|
314
|
+
defaultArgs: joinList(initialValue.defaultArgs),
|
|
315
|
+
modeNormal: joinList(initialValue.modeArgs?.normal),
|
|
316
|
+
modeContinue: joinList(initialValue.modeArgs?.continue),
|
|
317
|
+
modeResume: joinList(initialValue.modeArgs?.resume),
|
|
318
|
+
permissionSkipArgs: joinList(initialValue.permissionSkipArgs),
|
|
319
|
+
env: stringifyEnv(initialValue.env),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function joinList(values?: string[] | null): string {
|
|
324
|
+
if (!values || values.length === 0) {
|
|
325
|
+
return "";
|
|
326
|
+
}
|
|
327
|
+
return values.join("\n");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function parseList(value: string): string[] | null {
|
|
331
|
+
if (!value.trim()) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const values = value
|
|
335
|
+
.split(/\n|,/)
|
|
336
|
+
.map((item) => item.trim())
|
|
337
|
+
.filter(Boolean);
|
|
338
|
+
return values.length ? values : null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function stringifyEnv(env?: EnvironmentVariable[] | null): string {
|
|
342
|
+
if (!env || env.length === 0) {
|
|
343
|
+
return "";
|
|
344
|
+
}
|
|
345
|
+
return env
|
|
346
|
+
.filter((variable) => variable.key)
|
|
347
|
+
.map((variable) => `${variable.key}=${variable.value}`)
|
|
348
|
+
.join("\n");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function parseEnv(value: string): EnvironmentVariable[] | null | Error {
|
|
352
|
+
if (!value.trim()) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const now = new Date().toISOString();
|
|
357
|
+
const result: EnvironmentVariable[] = [];
|
|
358
|
+
const seen = new Set<string>();
|
|
359
|
+
const lines = value
|
|
360
|
+
.split(/\n/)
|
|
361
|
+
.map((line) => line.trim())
|
|
362
|
+
.filter(Boolean);
|
|
363
|
+
|
|
364
|
+
for (const line of lines) {
|
|
365
|
+
const [rawKey, ...rest] = line.split("=");
|
|
366
|
+
const key = (rawKey ?? "").trim();
|
|
367
|
+
if (!key || rest.length === 0) {
|
|
368
|
+
return new Error("環境変数は key=value 形式で入力してください");
|
|
369
|
+
}
|
|
370
|
+
const val = rest.join("=").trim();
|
|
371
|
+
if (!val) {
|
|
372
|
+
return new Error(`${key} の値を入力してください`);
|
|
373
|
+
}
|
|
374
|
+
if (seen.has(key)) {
|
|
375
|
+
return new Error(`環境変数 ${key} が重複しています`);
|
|
376
|
+
}
|
|
377
|
+
seen.add(key);
|
|
378
|
+
result.push({
|
|
379
|
+
key,
|
|
380
|
+
value: val,
|
|
381
|
+
lastUpdated: now,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return result;
|
|
386
|
+
}
|