@runfusion/fusion 0.26.0 → 0.27.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/dist/bin.js +11036 -1992
- package/dist/client/assets/AgentDetailView-B7QRcHJH.css +1 -0
- package/dist/client/assets/AgentDetailView-DwLmRXTY.js +18 -0
- package/dist/client/assets/{AgentsView-D6Zi5zfP.js → AgentsView-D-N6aA0P.js} +12 -7
- package/dist/client/assets/ChatView-DnCdKu8Z.js +1 -0
- package/dist/client/assets/{DevServerView--_WBvIDQ.js → DevServerView-BiA1nYtt.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-xedtR-Rd.js → DirectoryPicker-DvBviDG6.js} +1 -1
- package/dist/client/assets/{DocumentsView-Bg2oaZks.js → DocumentsView-BWXOxpuq.js} +1 -1
- package/dist/client/assets/{EvalsView-B3uOCXfr.js → EvalsView-CJFbtL7i.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-Bx6yXVS5.js → ExperimentalAgentOnboardingModal-DuGIPd0B.js} +1 -1
- package/dist/client/assets/InsightsView-BBpRiolN.js +11 -0
- package/dist/client/assets/{MemoryView-xcN_eouf.js → MemoryView-48LuNkKk.js} +2 -2
- package/dist/client/assets/NodesView-CGQWSNZM.js +14 -0
- package/dist/client/assets/{PiExtensionsManager-Cc8aAZXg.js → PiExtensionsManager-i-7UL2oh.js} +2 -2
- package/dist/client/assets/PluginManager-DoSAykD6.js +1 -0
- package/dist/client/assets/{ResearchView-CERNf7sJ.js → ResearchView-XZuRtOxE.js} +1 -1
- package/dist/client/assets/{SettingsModal-Cis-4Lot.css → SettingsModal-Ci0_sqbU.css} +1 -1
- package/dist/client/assets/{SettingsModal-B1r0yASu.js → SettingsModal-CmeF8CN4.js} +1 -1
- package/dist/client/assets/SettingsModal-DBcjf9Bu.js +31 -0
- package/dist/client/assets/SettingsModal-DWKgRxBA.css +1 -0
- package/dist/client/assets/{SetupWizardModal-D1q548_L.js → SetupWizardModal-CgtvpMX9.js} +1 -1
- package/dist/client/assets/{SkillsView-ClLM6u6p.js → SkillsView-DErYRumF.js} +1 -1
- package/dist/client/assets/{StashRecoveryView-ze0pEZ5U.js → StashRecoveryView-QJrNS4Vg.js} +1 -1
- package/dist/client/assets/{TodoView-CTmIfy2M.js → TodoView-BD9NRwq0.js} +2 -2
- package/dist/client/assets/{dashboard-view-CyWN-d02.js → dashboard-view-BWGH_fAq.js} +1 -1
- package/dist/client/assets/dashboard-view-BoTzlP8b.css +1 -0
- package/dist/client/assets/dashboard-view-Ws9_ZnKu.js +21 -0
- package/dist/client/assets/{folder-open-BZuKESeq.js → folder-open-CHSlllzf.js} +1 -1
- package/dist/client/assets/index-DCovGm5b.css +1 -0
- package/dist/client/assets/index-bEwSVl7B.js +692 -0
- package/dist/client/assets/{star-D75YKEq-.js → star-BgVwWAPz.js} +1 -1
- package/dist/client/assets/{upload-BYYTgWFj.js → upload-CAzycxr9.js} +1 -1
- package/dist/client/assets/{users-RS90Aii3.js → users-CZnxCCCJ.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/droid-cli/src/__tests__/index.test.ts +228 -0
- package/dist/extension.js +5517 -1193
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/pi-claude-cli/src/__tests__/provider.test.ts +36 -22
- package/dist/pi-claude-cli/src/provider.ts +7 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/manifest.json +19 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +20 -2
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/TestRunnerPanel.test.tsx +99 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/config-flow.test.ts +91 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-view.test.tsx +40 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-views.test.ts +46 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/draft-store.test.ts +50 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/exec-mock.ts +80 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/fixtures.test.ts +40 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/registry.ts +82 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/generator.test.ts +54 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manage-view.test.tsx +98 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manifest.test.ts +21 -5
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/registration.test.ts +29 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/run-routes.test.ts +98 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runner.test.ts +55 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runtime-availability.test.ts +61 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/validation.test.ts +30 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/wizard-routes.test.ts +61 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/workflow-integration.test.ts +19 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.css +43 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.tsx +49 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/generator.ts +95 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/redact.ts +9 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/runner.ts +79 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/types.ts +31 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/index.ts +46 -2
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/EditDraftModal.tsx +75 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/useDrafts.ts +73 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.css +79 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.tsx +122 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/routes/wizard-routes.ts +272 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.css +70 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.tsx +98 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/run/useRunGeneratedCli.ts +37 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/__tests__/executor-runtime-env.test.ts +191 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/executor-runtime-env.ts +75 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/storage/draft-store.ts +85 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/cli-press-store.test.ts +128 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/credentials.test.ts +62 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-store.ts +427 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-types.ts +110 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/store/credentials.ts +95 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/steps.tsx +55 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/types.ts +33 -0
- package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/validation.ts +63 -0
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/manifest.json +10 -0
- package/dist/plugins/fusion-plugin-reports/package.json +18 -2
- package/dist/plugins/fusion-plugin-reports/src/__tests__/approval.test.ts +164 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/manifest.test.ts +14 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/routes-approval.test.ts +109 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/scaffold.test.ts +60 -0
- package/dist/plugins/fusion-plugin-reports/src/__tests__/share-blocks.test.ts +83 -0
- package/dist/plugins/fusion-plugin-reports/src/aggregation.ts +23 -0
- package/dist/plugins/fusion-plugin-reports/src/approval.ts +97 -0
- package/dist/plugins/fusion-plugin-reports/src/cadence.ts +23 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.css +82 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.tsx +24 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportComparisonDrawer.test.tsx +12 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportDetailPanel.test.tsx +12 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportFiltersBar.test.tsx +14 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportsView.test.tsx +27 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/api.test.ts +19 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReportSectionDiff.test.ts +11 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReports.test.ts +13 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/api.ts +85 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.css +59 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.tsx +58 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportComparisonDrawer.tsx +21 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportDetailPanel.tsx +29 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportEmptyState.tsx +3 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportFiltersBar.tsx +19 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportListItem.tsx +8 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.css +29 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.tsx +43 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ReportApprovalPanel.test.tsx +38 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ShareBlocksPanel.test.tsx +24 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/test-setup.ts +18 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/types.ts +22 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportPreview.ts +44 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportSectionDiff.ts +59 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useReports.ts +71 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard/useViewportMode.ts +13 -0
- package/dist/plugins/fusion-plugin-reports/src/dashboard-view.tsx +6 -0
- package/dist/plugins/fusion-plugin-reports/src/index.ts +48 -2
- package/dist/plugins/fusion-plugin-reports/src/pipeline.ts +58 -0
- package/dist/plugins/fusion-plugin-reports/src/render/__tests__/escape.test.ts +20 -0
- package/dist/plugins/fusion-plugin-reports/src/render/__tests__/html-template.test.ts +110 -0
- package/dist/plugins/fusion-plugin-reports/src/render/__tests__/standalone-html.test.ts +66 -0
- package/dist/plugins/fusion-plugin-reports/src/render/escape.ts +12 -0
- package/dist/plugins/fusion-plugin-reports/src/render/html-styles.ts +40 -0
- package/dist/plugins/fusion-plugin-reports/src/render/html-template.ts +137 -0
- package/dist/plugins/fusion-plugin-reports/src/render/index.ts +4 -0
- package/dist/plugins/fusion-plugin-reports/src/render/standalone-html.ts +75 -0
- package/dist/plugins/fusion-plugin-reports/src/report-schema.ts +31 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/__tests__/report-export-routes.test.ts +104 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/report-approval-routes.ts +98 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/report-export-routes.ts +77 -0
- package/dist/plugins/fusion-plugin-reports/src/routes/report-list-routes.ts +72 -0
- package/dist/plugins/fusion-plugin-reports/src/runs-store.ts +69 -0
- package/dist/plugins/fusion-plugin-reports/src/share-blocks.ts +82 -0
- package/dist/plugins/fusion-plugin-reports/src/store/report-store.ts +51 -2
- package/dist/plugins/fusion-plugin-reports/src/store/report-types.ts +6 -1
- package/dist/plugins/fusion-plugin-roadmap/bundled.js +1528 -29391
- package/dist/plugins/fusion-plugin-roadmap/manifest.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +1 -1
- package/skill/fusion/references/engine-tools.md +1 -1
- package/skill/fusion/references/extension-tools.md +3 -3
- package/skill/fusion/references/fusion-capabilities.md +1 -1
- package/dist/client/assets/AgentDetailView-BwJaLqZh.css +0 -1
- package/dist/client/assets/AgentDetailView-Cv-vgOj3.js +0 -18
- package/dist/client/assets/ChatView-CAHjY9uO.js +0 -1
- package/dist/client/assets/InsightsView-Q1zvtF4F.js +0 -11
- package/dist/client/assets/NodesView-RxXg58_Q.js +0 -14
- package/dist/client/assets/PluginManager-BEkyBajl.js +0 -1
- package/dist/client/assets/SettingsModal-BLsac7CJ.js +0 -31
- package/dist/client/assets/SettingsModal-BNSrO1M9.css +0 -1
- package/dist/client/assets/dashboard-view-4xAN3yO5.js +0 -21
- package/dist/client/assets/dashboard-view-BkTMSZYn.css +0 -1
- package/dist/client/assets/index-Bdw6llW6.js +0 -692
- package/dist/client/assets/index-CZGlyJuS.css +0 -1
- package/dist/plugins/fusion-plugin-roadmap/bundled.css +0 -1093
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { PluginDashboardViewContext } from "@fusion/dashboard/app/plugins/types";
|
|
2
|
+
import { List, Pencil, RefreshCw, Trash2 } from "lucide-react";
|
|
3
|
+
import { useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { EditDraftModal } from "./manage/EditDraftModal.js";
|
|
5
|
+
import { useDrafts } from "./manage/useDrafts.js";
|
|
6
|
+
import { CliPrintingPressTestRunner } from "./run/TestRunnerPanel.js";
|
|
7
|
+
import type { ServiceDraft } from "./wizard/types.js";
|
|
8
|
+
import "./manage-view.css";
|
|
9
|
+
|
|
10
|
+
// FN-3763 symbol drift note: plugin id/registry route uses "fusion-plugin-cli-printing-press"
|
|
11
|
+
// and bundled registration lives in registerBundledPluginViews.ts (not pluginViewRegistry.tsx).
|
|
12
|
+
export function CliPrintingPressManageView({ context: _context }: { context?: PluginDashboardViewContext }) {
|
|
13
|
+
const { drafts, loading, error, refresh, getDraft, updateDraft, regenerateDraft, deleteDraft } = useDrafts();
|
|
14
|
+
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
15
|
+
const [selectedDraft, setSelectedDraft] = useState<ServiceDraft | null>(null);
|
|
16
|
+
const [detailError, setDetailError] = useState<string | null>(null);
|
|
17
|
+
const [statusMessage, setStatusMessage] = useState<string | null>(null);
|
|
18
|
+
const [isEditOpen, setIsEditOpen] = useState(false);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!drafts.length) {
|
|
22
|
+
setSelectedId(null);
|
|
23
|
+
setSelectedDraft(null);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const activeId = selectedId && drafts.some((item) => item.id === selectedId) ? selectedId : drafts[0]?.id ?? null;
|
|
27
|
+
setSelectedId(activeId);
|
|
28
|
+
}, [drafts, selectedId]);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!selectedId) return;
|
|
32
|
+
void (async () => {
|
|
33
|
+
try {
|
|
34
|
+
setDetailError(null);
|
|
35
|
+
setSelectedDraft(await getDraft(selectedId));
|
|
36
|
+
} catch (err) {
|
|
37
|
+
setDetailError(err instanceof Error ? err.message : "Failed to load draft details");
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
}, [getDraft, selectedId]);
|
|
41
|
+
|
|
42
|
+
const selectedListItem = useMemo(() => drafts.find((item) => item.id === selectedId) ?? null, [drafts, selectedId]);
|
|
43
|
+
|
|
44
|
+
async function onRegenerate() {
|
|
45
|
+
if (!selectedId) return;
|
|
46
|
+
try {
|
|
47
|
+
const response = await regenerateDraft(selectedId);
|
|
48
|
+
setSelectedDraft(response.draft);
|
|
49
|
+
setStatusMessage(`Regenerated at ${new Date(response.artifact.generatedAt).toLocaleString()}`);
|
|
50
|
+
await refresh();
|
|
51
|
+
} catch (err) {
|
|
52
|
+
setStatusMessage(err instanceof Error ? err.message : "Failed to regenerate draft");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function onDelete() {
|
|
57
|
+
if (!selectedId) return;
|
|
58
|
+
if (!globalThis.confirm("Delete this draft?")) return;
|
|
59
|
+
try {
|
|
60
|
+
await deleteDraft(selectedId);
|
|
61
|
+
setStatusMessage("Draft removed");
|
|
62
|
+
await refresh();
|
|
63
|
+
} catch (err) {
|
|
64
|
+
setStatusMessage(err instanceof Error ? err.message : "Failed to delete draft");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function onSaveEditedDraft(nextDraft: ServiceDraft) {
|
|
69
|
+
if (!selectedId) return;
|
|
70
|
+
const saved = await updateDraft(selectedId, nextDraft);
|
|
71
|
+
setSelectedDraft(saved);
|
|
72
|
+
setIsEditOpen(false);
|
|
73
|
+
setStatusMessage("Draft updated");
|
|
74
|
+
await refresh();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (loading) return <section className="card cli-press-manage-state"><p>Loading drafts…</p></section>;
|
|
78
|
+
if (error) return <section className="card cli-press-manage-state"><p className="form-error">{error}</p></section>;
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<section className="cli-press-manage">
|
|
82
|
+
<header className="cli-press-manage-header">
|
|
83
|
+
<h2><List /> Manage Service CLIs</h2>
|
|
84
|
+
</header>
|
|
85
|
+
{statusMessage ? <p className="cli-press-manage-status">{statusMessage}</p> : null}
|
|
86
|
+
{!drafts.length ? <div className="card"><p>No saved drafts yet. Use the Create Service CLI view to add one.</p></div> : (
|
|
87
|
+
<div className="cli-press-manage-layout">
|
|
88
|
+
<div className="cli-press-manage-list">
|
|
89
|
+
{drafts.map((draft) => (
|
|
90
|
+
<button key={draft.id} className={`card cli-press-manage-row${draft.id === selectedId ? " is-selected" : ""}`} onClick={() => setSelectedId(draft.id)}>
|
|
91
|
+
<div className="cli-press-manage-row-title">{draft.name || draft.slug}</div>
|
|
92
|
+
<div className="cli-press-manage-row-meta">{draft.slug} • {new Date(draft.updatedAt).toLocaleString()}</div>
|
|
93
|
+
</button>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
<div className="card cli-press-manage-detail">
|
|
97
|
+
{detailError ? <p className="form-error">{detailError}</p> : null}
|
|
98
|
+
{selectedDraft ? (
|
|
99
|
+
<>
|
|
100
|
+
<h3>{selectedDraft.name}</h3>
|
|
101
|
+
<p><strong>Slug:</strong> {selectedListItem?.slug}</p>
|
|
102
|
+
<p><strong>Base URL:</strong> {selectedDraft.baseUrl}</p>
|
|
103
|
+
<p><strong>Endpoints:</strong> {selectedDraft.endpoints.length}</p>
|
|
104
|
+
<p><strong>Credentials:</strong> {selectedDraft.credential.kind}</p>
|
|
105
|
+
<p><strong>Updated:</strong> {new Date(selectedDraft.updatedAt).toLocaleString()}</p>
|
|
106
|
+
<div className="cli-press-manage-actions">
|
|
107
|
+
<button className="btn" onClick={() => setIsEditOpen(true)}><Pencil /> Edit</button>
|
|
108
|
+
<button className="btn" onClick={() => void onRegenerate()}><RefreshCw /> Regenerate</button>
|
|
109
|
+
<button className="btn btn-danger" onClick={() => void onDelete()}><Trash2 /> Delete</button>
|
|
110
|
+
</div>
|
|
111
|
+
<CliPrintingPressTestRunner draftId={selectedDraft.id} draft={selectedDraft} />
|
|
112
|
+
</>
|
|
113
|
+
) : <p>Select a draft to inspect details.</p>}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
{isEditOpen && selectedDraft ? <EditDraftModal initialDraft={selectedDraft} onClose={() => setIsEditOpen(false)} onSave={onSaveEditedDraft} /> : null}
|
|
118
|
+
</section>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default CliPrintingPressManageView;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import type { PluginContext, PluginRouteDefinition, PluginRouteResult } from "@fusion/core";
|
|
2
|
+
import { generateCli } from "../generation/generator.js";
|
|
3
|
+
import { runGeneratedCli } from "../generation/runner.js";
|
|
4
|
+
import type { GeneratedCliArtifact, RunRequest } from "../generation/types.js";
|
|
5
|
+
import { createCliPressStore } from "../store/cli-press-store.js";
|
|
6
|
+
import type { CliSpec, Service } from "../store/cli-press-types.js";
|
|
7
|
+
import type { ServiceDraft } from "../wizard/types.js";
|
|
8
|
+
import { validateDraft } from "../wizard/validation.js";
|
|
9
|
+
|
|
10
|
+
interface RouteRequest {
|
|
11
|
+
params: Record<string, string>;
|
|
12
|
+
body?: unknown;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function asRequest(req: unknown): RouteRequest { return req as RouteRequest; }
|
|
16
|
+
function ok(body: unknown, status = 200): PluginRouteResult { return { status, body }; }
|
|
17
|
+
|
|
18
|
+
function asArtifact(draft: ServiceDraft): GeneratedCliArtifact | null {
|
|
19
|
+
if (!draft.artifactPath || !draft.generatedAt) return null;
|
|
20
|
+
return {
|
|
21
|
+
draftId: draft.id,
|
|
22
|
+
slug: draft.slug,
|
|
23
|
+
binPath: draft.artifactPath,
|
|
24
|
+
entrypoint: "node",
|
|
25
|
+
generatedAt: draft.generatedAt,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getArtifactDir(id: string, projectRoot: string): string {
|
|
30
|
+
return `${projectRoot}/.fusion/plugins/cli-printing-press/artifacts/${id}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getStore(ctx: PluginContext) {
|
|
34
|
+
return createCliPressStore(ctx.taskStore.getDatabase());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toDraft(service: Service, spec: CliSpec | undefined, endpoints: ServiceDraft["endpoints"]): ServiceDraft {
|
|
38
|
+
return {
|
|
39
|
+
id: service.id,
|
|
40
|
+
name: service.displayName,
|
|
41
|
+
slug: service.slug,
|
|
42
|
+
description: service.description ?? "",
|
|
43
|
+
baseUrl: service.baseUrl,
|
|
44
|
+
transport: "http",
|
|
45
|
+
endpoints,
|
|
46
|
+
credential: { kind: "none" },
|
|
47
|
+
createdAt: service.createdAt,
|
|
48
|
+
updatedAt: service.updatedAt,
|
|
49
|
+
generatedAt: spec?.generatedAt,
|
|
50
|
+
regeneratedAt: spec?.generatedAt,
|
|
51
|
+
artifactPath: spec?.status === "generated" ? spec.specJson : undefined,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isPrimitive(value: unknown): value is string | number | boolean {
|
|
56
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateRunRequest(body: unknown): { ok: true; value: RunRequest } | { ok: false; error: string } {
|
|
60
|
+
if (!body || typeof body !== "object") return { ok: false, error: "Request body is required" };
|
|
61
|
+
const candidate = body as Record<string, unknown>;
|
|
62
|
+
if (typeof candidate.endpointId !== "string" || !candidate.endpointId.trim()) return { ok: false, error: "endpointId is required" };
|
|
63
|
+
if (!candidate.params || typeof candidate.params !== "object" || Array.isArray(candidate.params)) return { ok: false, error: "params must be an object" };
|
|
64
|
+
for (const value of Object.values(candidate.params as Record<string, unknown>)) {
|
|
65
|
+
if (!isPrimitive(value)) return { ok: false, error: "params values must be primitives" };
|
|
66
|
+
}
|
|
67
|
+
if (candidate.credentials !== undefined) {
|
|
68
|
+
if (!candidate.credentials || typeof candidate.credentials !== "object" || Array.isArray(candidate.credentials)) return { ok: false, error: "credentials must be an object" };
|
|
69
|
+
for (const value of Object.values(candidate.credentials as Record<string, unknown>)) {
|
|
70
|
+
if (typeof value !== "string") return { ok: false, error: "credentials values must be strings" };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (candidate.timeoutMs !== undefined) {
|
|
74
|
+
if (!Number.isFinite(candidate.timeoutMs) || !Number.isInteger(candidate.timeoutMs) || (candidate.timeoutMs as number) <= 0 || (candidate.timeoutMs as number) > 300_000) {
|
|
75
|
+
return { ok: false, error: "timeoutMs must be an integer between 1 and 300000" };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
value: {
|
|
81
|
+
endpointId: candidate.endpointId,
|
|
82
|
+
params: candidate.params as Record<string, string | number | boolean>,
|
|
83
|
+
credentials: candidate.credentials as Record<string, string> | undefined,
|
|
84
|
+
timeoutMs: candidate.timeoutMs as number | undefined,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createCliPrintingPressRoutes(): PluginRouteDefinition[] {
|
|
90
|
+
return [
|
|
91
|
+
{
|
|
92
|
+
method: "POST",
|
|
93
|
+
path: "/drafts",
|
|
94
|
+
handler: async (req, ctx: PluginContext) => {
|
|
95
|
+
const request = asRequest(req);
|
|
96
|
+
const draft = request.body as ServiceDraft;
|
|
97
|
+
const result = validateDraft(draft);
|
|
98
|
+
if (!result.ok) return { status: 400, body: { error: Object.values(result.errors)[0] ?? "Validation failed", errors: result.errors } };
|
|
99
|
+
const store = getStore(ctx);
|
|
100
|
+
const createdService = store.createService({
|
|
101
|
+
slug: draft.slug,
|
|
102
|
+
displayName: draft.name,
|
|
103
|
+
description: draft.description,
|
|
104
|
+
baseUrl: draft.baseUrl,
|
|
105
|
+
sourceKind: "manual",
|
|
106
|
+
sourceRef: undefined,
|
|
107
|
+
});
|
|
108
|
+
const createdSpec = store.createSpec({
|
|
109
|
+
serviceId: createdService.id,
|
|
110
|
+
name: `${draft.slug}-cli`,
|
|
111
|
+
version: "0.1.0",
|
|
112
|
+
generatorVersion: "cli-printing-press",
|
|
113
|
+
specJson: JSON.stringify(draft),
|
|
114
|
+
status: "draft",
|
|
115
|
+
generatedAt: undefined,
|
|
116
|
+
lastGenerationError: undefined,
|
|
117
|
+
});
|
|
118
|
+
store.setSetting({ serviceId: createdService.id, key: "endpoints", value: JSON.stringify(draft.endpoints), scope: "wizard" });
|
|
119
|
+
const created = toDraft(createdService, createdSpec, draft.endpoints);
|
|
120
|
+
return ok(created, 201);
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
method: "GET",
|
|
125
|
+
path: "/drafts",
|
|
126
|
+
handler: async (_req, ctx: PluginContext) => {
|
|
127
|
+
const store = getStore(ctx);
|
|
128
|
+
const drafts = store.listServices().map((service) => ({
|
|
129
|
+
id: service.id,
|
|
130
|
+
name: service.displayName,
|
|
131
|
+
slug: service.slug,
|
|
132
|
+
updatedAt: service.updatedAt,
|
|
133
|
+
}));
|
|
134
|
+
return ok(drafts);
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
method: "GET",
|
|
139
|
+
path: "/drafts/:id",
|
|
140
|
+
handler: async (req, ctx: PluginContext) => {
|
|
141
|
+
const request = asRequest(req);
|
|
142
|
+
const store = getStore(ctx);
|
|
143
|
+
const service = store.getService(request.params.id);
|
|
144
|
+
if (!service) return ok({ error: "Draft not found" }, 404);
|
|
145
|
+
const spec = store.listSpecs(service.id)[0];
|
|
146
|
+
const endpointsSetting = store.listSettings(service.id).find((entry) => entry.key === "endpoints" && entry.scope === "wizard");
|
|
147
|
+
const endpoints = endpointsSetting ? (JSON.parse(endpointsSetting.value) as ServiceDraft["endpoints"]) : [];
|
|
148
|
+
return ok(toDraft(service, spec, endpoints));
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
method: "PUT",
|
|
153
|
+
path: "/drafts/:id",
|
|
154
|
+
handler: async (req, ctx: PluginContext) => {
|
|
155
|
+
const request = asRequest(req);
|
|
156
|
+
const draft = request.body as ServiceDraft;
|
|
157
|
+
const result = validateDraft(draft);
|
|
158
|
+
if (!result.ok) return { status: 400, body: { error: Object.values(result.errors)[0] ?? "Validation failed", errors: result.errors } };
|
|
159
|
+
const store = getStore(ctx);
|
|
160
|
+
const service = store.getService(request.params.id);
|
|
161
|
+
if (!service) return ok({ error: "Draft not found" }, 404);
|
|
162
|
+
const updatedService = store.updateService(service.id, {
|
|
163
|
+
displayName: draft.name,
|
|
164
|
+
description: draft.description,
|
|
165
|
+
baseUrl: draft.baseUrl,
|
|
166
|
+
sourceKind: "manual",
|
|
167
|
+
});
|
|
168
|
+
const existingSpec = store.listSpecs(service.id)[0];
|
|
169
|
+
const updatedSpec = existingSpec
|
|
170
|
+
? store.updateSpec(existingSpec.id, { specJson: JSON.stringify(draft), status: "draft", lastGenerationError: undefined })
|
|
171
|
+
: store.createSpec({
|
|
172
|
+
serviceId: service.id,
|
|
173
|
+
name: `${draft.slug}-cli`,
|
|
174
|
+
version: "0.1.0",
|
|
175
|
+
generatorVersion: "cli-printing-press",
|
|
176
|
+
specJson: JSON.stringify(draft),
|
|
177
|
+
status: "draft",
|
|
178
|
+
generatedAt: undefined,
|
|
179
|
+
lastGenerationError: undefined,
|
|
180
|
+
});
|
|
181
|
+
store.setSetting({ serviceId: service.id, key: "endpoints", value: JSON.stringify(draft.endpoints), scope: "wizard" });
|
|
182
|
+
return ok(toDraft(updatedService, updatedSpec, draft.endpoints));
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
method: "POST",
|
|
187
|
+
path: "/drafts/:id/regenerate",
|
|
188
|
+
handler: async (req, ctx: PluginContext) => {
|
|
189
|
+
const request = asRequest(req);
|
|
190
|
+
const projectRoot = ctx.taskStore.getRootDir();
|
|
191
|
+
const store = getStore(ctx);
|
|
192
|
+
const service = store.getService(request.params.id);
|
|
193
|
+
if (!service) return ok({ error: "Draft not found" }, 404);
|
|
194
|
+
const spec = store.listSpecs(service.id)[0];
|
|
195
|
+
if (!spec) return ok({ error: "Draft not found" }, 404);
|
|
196
|
+
const draft = JSON.parse(spec.specJson) as ServiceDraft;
|
|
197
|
+
const artifact = await generateCli({ draft, outDir: getArtifactDir(draft.id, projectRoot) });
|
|
198
|
+
const updatedSpec = store.updateSpec(spec.id, {
|
|
199
|
+
specJson: JSON.stringify({ ...draft, regeneratedAt: artifact.generatedAt, generatedAt: artifact.generatedAt, artifactPath: artifact.binPath }),
|
|
200
|
+
generatedAt: artifact.generatedAt,
|
|
201
|
+
status: "generated",
|
|
202
|
+
lastGenerationError: undefined,
|
|
203
|
+
});
|
|
204
|
+
store.createArtifact({
|
|
205
|
+
cliSpecId: spec.id,
|
|
206
|
+
kind: "script",
|
|
207
|
+
path: artifact.binPath.replace(`${projectRoot}/.fusion/`, ""),
|
|
208
|
+
executable: true,
|
|
209
|
+
checksum: undefined,
|
|
210
|
+
sizeBytes: undefined,
|
|
211
|
+
});
|
|
212
|
+
return ok({ draft: JSON.parse(updatedSpec.specJson) as ServiceDraft, artifact });
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
method: "POST",
|
|
217
|
+
path: "/drafts/:id/run",
|
|
218
|
+
handler: async (req, ctx: PluginContext) => {
|
|
219
|
+
const request = asRequest(req);
|
|
220
|
+
const parsed = validateRunRequest(request.body);
|
|
221
|
+
if (!parsed.ok) return ok({ error: parsed.error }, 400);
|
|
222
|
+
|
|
223
|
+
const store = getStore(ctx);
|
|
224
|
+
const service = store.getService(request.params.id);
|
|
225
|
+
if (!service) return ok({ error: "Draft not found" }, 404);
|
|
226
|
+
const spec = store.listSpecs(service.id)[0];
|
|
227
|
+
if (!spec) return ok({ error: "Draft not found" }, 404);
|
|
228
|
+
const draft = JSON.parse(spec.specJson) as ServiceDraft;
|
|
229
|
+
const artifact = asArtifact(draft);
|
|
230
|
+
if (!artifact) return ok({ error: "Draft has not been generated yet" }, 409);
|
|
231
|
+
|
|
232
|
+
const endpointExists = draft.endpoints.some((endpoint) => endpoint.id === parsed.value.endpointId);
|
|
233
|
+
if (!endpointExists) return ok({ error: "Endpoint not found" }, 400);
|
|
234
|
+
|
|
235
|
+
const result = await runGeneratedCli({
|
|
236
|
+
artifact,
|
|
237
|
+
endpointId: parsed.value.endpointId,
|
|
238
|
+
params: parsed.value.params,
|
|
239
|
+
credentials: parsed.value.credentials,
|
|
240
|
+
timeoutMs: parsed.value.timeoutMs,
|
|
241
|
+
cwd: ctx.taskStore.getRootDir(),
|
|
242
|
+
});
|
|
243
|
+
return ok(result, 200);
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
method: "GET",
|
|
248
|
+
path: "/drafts/:id/artifact",
|
|
249
|
+
handler: async (req, ctx: PluginContext) => {
|
|
250
|
+
const request = asRequest(req);
|
|
251
|
+
const store = getStore(ctx);
|
|
252
|
+
const service = store.getService(request.params.id);
|
|
253
|
+
if (!service) return ok({ error: "Artifact not found" }, 404);
|
|
254
|
+
const spec = store.listSpecs(service.id)[0];
|
|
255
|
+
if (!spec) return ok({ error: "Artifact not found" }, 404);
|
|
256
|
+
const draft = JSON.parse(spec.specJson) as ServiceDraft;
|
|
257
|
+
const artifact = asArtifact(draft);
|
|
258
|
+
return artifact ? ok({ artifact }) : ok({ error: "Artifact not found" }, 404);
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
method: "DELETE",
|
|
263
|
+
path: "/drafts/:id",
|
|
264
|
+
handler: async (req, ctx: PluginContext) => {
|
|
265
|
+
const request = asRequest(req);
|
|
266
|
+
const store = getStore(ctx);
|
|
267
|
+
store.deleteService(request.params.id);
|
|
268
|
+
return { status: 204 };
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
.clipp-test-runner {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
gap: var(--space-md);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.clipp-test-runner-header {
|
|
8
|
+
display: flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
justify-content: space-between;
|
|
11
|
+
gap: var(--space-md);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.clipp-test-runner-header h4 {
|
|
15
|
+
margin: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.clipp-test-runner-header p {
|
|
19
|
+
margin: 0;
|
|
20
|
+
color: var(--text-muted);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.clipp-test-runner-slug {
|
|
24
|
+
color: var(--text-muted);
|
|
25
|
+
font-weight: 400;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.clipp-test-runner-grid {
|
|
29
|
+
display: grid;
|
|
30
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
31
|
+
gap: var(--space-md);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.clipp-test-runner-form,
|
|
35
|
+
.clipp-test-runner-output {
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
gap: var(--space-sm);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.clipp-test-runner-help {
|
|
42
|
+
color: var(--text-muted);
|
|
43
|
+
margin: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.clipp-test-runner-status {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
gap: var(--space-sm);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.clipp-test-runner-pre {
|
|
53
|
+
margin: 0;
|
|
54
|
+
padding: var(--space-sm);
|
|
55
|
+
border-radius: var(--radius-sm);
|
|
56
|
+
font-family: var(--font-mono);
|
|
57
|
+
background: var(--surface);
|
|
58
|
+
border: var(--btn-border-width) solid var(--border);
|
|
59
|
+
white-space: pre-wrap;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.clipp-test-runner-pre-stderr {
|
|
63
|
+
background: color-mix(in srgb, var(--color-error) 10%, transparent);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@media (max-width: 768px) {
|
|
67
|
+
.clipp-test-runner-grid {
|
|
68
|
+
grid-template-columns: minmax(0, 1fr);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { AlertTriangle, CheckCircle2, Play, RefreshCw } from "lucide-react";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import type { RunResult } from "../generation/types.js";
|
|
4
|
+
import type { ServiceDraft } from "../wizard/types.js";
|
|
5
|
+
import { useRunGeneratedCli } from "./useRunGeneratedCli.js";
|
|
6
|
+
import "./TestRunnerPanel.css";
|
|
7
|
+
|
|
8
|
+
export function CliPrintingPressTestRunner({ draftId, draft }: { draftId: string; draft: ServiceDraft }) {
|
|
9
|
+
const { regenerate, run } = useRunGeneratedCli();
|
|
10
|
+
const [selectedEndpointId, setSelectedEndpointId] = useState(draft.endpoints[0]?.id ?? "");
|
|
11
|
+
const [params, setParams] = useState<Record<string, string | number | boolean>>({});
|
|
12
|
+
const [credentials, setCredentials] = useState<Record<string, string>>({});
|
|
13
|
+
const [result, setResult] = useState<RunResult | null>(null);
|
|
14
|
+
const [generatedAt, setGeneratedAt] = useState<string | undefined>(draft.generatedAt ?? draft.regeneratedAt);
|
|
15
|
+
const [running, setRunning] = useState(false);
|
|
16
|
+
const [error, setError] = useState<string | null>(null);
|
|
17
|
+
|
|
18
|
+
const endpoint = useMemo(() => draft.endpoints.find((item) => item.id === selectedEndpointId) ?? draft.endpoints[0], [draft.endpoints, selectedEndpointId]);
|
|
19
|
+
const paramKeys = useMemo(() => (endpoint?.params ?? "").split(",").map((item) => item.trim()).filter(Boolean), [endpoint?.params]);
|
|
20
|
+
|
|
21
|
+
async function onRegenerate() {
|
|
22
|
+
setError(null);
|
|
23
|
+
const response = await regenerate(draftId);
|
|
24
|
+
setGeneratedAt(response.artifact.generatedAt);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function onRun() {
|
|
28
|
+
if (!endpoint) return;
|
|
29
|
+
setRunning(true);
|
|
30
|
+
setError(null);
|
|
31
|
+
setResult(null);
|
|
32
|
+
try {
|
|
33
|
+
setResult(await run(draftId, { endpointId: endpoint.id, params, credentials }));
|
|
34
|
+
} catch (err) {
|
|
35
|
+
setError(err instanceof Error ? err.message : "Run failed");
|
|
36
|
+
} finally {
|
|
37
|
+
setRunning(false);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const status = running ? "running" : (result?.timedOut || (typeof result?.exitCode === "number" && result.exitCode !== 0) ? "error" : (result ? "success" : "idle"));
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<section className="card clipp-test-runner">
|
|
45
|
+
<header className="clipp-test-runner-header">
|
|
46
|
+
<div>
|
|
47
|
+
<h4>{draft.name} <span className="clipp-test-runner-slug">({draft.slug})</span></h4>
|
|
48
|
+
<p>Generated: {generatedAt ? new Date(generatedAt).toLocaleString() : "Not generated"}</p>
|
|
49
|
+
</div>
|
|
50
|
+
<button className="btn btn-icon" onClick={() => void onRegenerate()} aria-label="Regenerate draft">
|
|
51
|
+
<RefreshCw />
|
|
52
|
+
</button>
|
|
53
|
+
</header>
|
|
54
|
+
|
|
55
|
+
<div className="clipp-test-runner-grid">
|
|
56
|
+
<div className="clipp-test-runner-form">
|
|
57
|
+
<label htmlFor={`clipp-endpoint-${draftId}`}>Endpoint</label>
|
|
58
|
+
<select id={`clipp-endpoint-${draftId}`} className="select" value={endpoint?.id ?? ""} onChange={(event) => setSelectedEndpointId(event.target.value)}>
|
|
59
|
+
{draft.endpoints.map((item) => <option key={item.id} value={item.id}>{item.method} {item.path}</option>)}
|
|
60
|
+
</select>
|
|
61
|
+
|
|
62
|
+
{paramKeys.map((key) => (
|
|
63
|
+
<div key={key} className="form-group">
|
|
64
|
+
<label htmlFor={`clipp-param-${key}`}>{key}</label>
|
|
65
|
+
<input id={`clipp-param-${key}`} className="input" value={String(params[key] ?? "")} onChange={(event) => setParams((prev) => ({ ...prev, [key]: event.target.value }))} />
|
|
66
|
+
</div>
|
|
67
|
+
))}
|
|
68
|
+
|
|
69
|
+
<div className="clipp-test-runner-credentials">
|
|
70
|
+
<label>Credentials</label>
|
|
71
|
+
<input className="input" type="password" placeholder="api_key" value={credentials.api_key ?? ""} onChange={(event) => setCredentials((prev) => ({ ...prev, api_key: event.target.value }))} />
|
|
72
|
+
<p className="clipp-test-runner-help">Credential values are used only for this run and are not persisted.</p>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<button className="btn btn-primary" disabled={running} onClick={() => void onRun()}><Play /> Run</button>
|
|
76
|
+
{error ? <p className="form-error">{error}</p> : null}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div className="clipp-test-runner-output">
|
|
80
|
+
<div className="clipp-test-runner-status">
|
|
81
|
+
<span className="status-dot" />
|
|
82
|
+
{status === "running" ? <><AlertTriangle /> Running…</> : null}
|
|
83
|
+
{status === "success" ? <><CheckCircle2 /> Success</> : null}
|
|
84
|
+
{status === "error" ? <><AlertTriangle /> Failed</> : null}
|
|
85
|
+
{result ? <span>{result.durationMs}ms</span> : null}
|
|
86
|
+
</div>
|
|
87
|
+
{result ? (
|
|
88
|
+
<>
|
|
89
|
+
<pre className="clipp-test-runner-pre">$ node {result.argv.join(" ")}</pre>
|
|
90
|
+
<pre className="clipp-test-runner-pre">{result.stdout || "(no stdout)"}</pre>
|
|
91
|
+
<pre className="clipp-test-runner-pre clipp-test-runner-pre-stderr">{result.stderr || "(no stderr)"}</pre>
|
|
92
|
+
</>
|
|
93
|
+
) : <p>No run output yet.</p>}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</section>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { GeneratedCliArtifact, RunRequest, RunResult } from "../generation/types.js";
|
|
2
|
+
import type { ServiceDraft } from "../wizard/types.js";
|
|
3
|
+
|
|
4
|
+
const BASE_PATH = "/api/plugins/cli-printing-press/drafts";
|
|
5
|
+
|
|
6
|
+
async function parseJson<T>(response: Response): Promise<T> {
|
|
7
|
+
const body = await response.json().catch(() => ({}));
|
|
8
|
+
if (!response.ok) {
|
|
9
|
+
const error = (body as { error?: string }).error ?? "Request failed";
|
|
10
|
+
throw new Error(error);
|
|
11
|
+
}
|
|
12
|
+
return body as T;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useRunGeneratedCli() {
|
|
16
|
+
async function regenerate(id: string, signal?: AbortSignal): Promise<{ draft: ServiceDraft; artifact: GeneratedCliArtifact }> {
|
|
17
|
+
const response = await fetch(`${BASE_PATH}/${id}/regenerate`, { method: "POST", signal });
|
|
18
|
+
return parseJson<{ draft: ServiceDraft; artifact: GeneratedCliArtifact }>(response);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function run(id: string, payload: RunRequest, signal?: AbortSignal): Promise<RunResult> {
|
|
22
|
+
const response = await fetch(`${BASE_PATH}/${id}/run`, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: { "content-type": "application/json" },
|
|
25
|
+
body: JSON.stringify(payload),
|
|
26
|
+
signal,
|
|
27
|
+
});
|
|
28
|
+
return parseJson<RunResult>(response);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function getArtifact(id: string, signal?: AbortSignal): Promise<{ artifact: GeneratedCliArtifact }> {
|
|
32
|
+
const response = await fetch(`${BASE_PATH}/${id}/artifact`, { signal });
|
|
33
|
+
return parseJson<{ artifact: GeneratedCliArtifact }>(response);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { regenerate, run, getArtifact };
|
|
37
|
+
}
|