@runfusion/fusion 0.26.0 → 0.27.1

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.
Files changed (174) hide show
  1. package/dist/bin.js +12847 -2514
  2. package/dist/client/assets/AgentDetailView-B7QRcHJH.css +1 -0
  3. package/dist/client/assets/AgentDetailView-shgiiUb4.js +18 -0
  4. package/dist/client/assets/{AgentsView-CV3vm7Qk.css → AgentsView-B3ADnF0D.css} +1 -1
  5. package/dist/client/assets/{AgentsView-D6Zi5zfP.js → AgentsView-CpwqOVDz.js} +12 -7
  6. package/dist/client/assets/ChatView-DyRBOIKL.js +1 -0
  7. package/dist/client/assets/{DevServerView--_WBvIDQ.js → DevServerView-Cdelj9-m.js} +1 -1
  8. package/dist/client/assets/{DirectoryPicker-xedtR-Rd.js → DirectoryPicker-C0kmRv0u.js} +1 -1
  9. package/dist/client/assets/{DocumentsView-Bg2oaZks.js → DocumentsView-B94U9ijs.js} +1 -1
  10. package/dist/client/assets/{EvalsView-B3uOCXfr.js → EvalsView-O_4YWy--.js} +1 -1
  11. package/dist/client/assets/{ExperimentalAgentOnboardingModal-Bx6yXVS5.js → ExperimentalAgentOnboardingModal-CkEiF85-.js} +1 -1
  12. package/dist/client/assets/InsightsView-D-Qe0tRr.js +11 -0
  13. package/dist/client/assets/{MemoryView-xcN_eouf.js → MemoryView-CoRUmRvb.js} +2 -2
  14. package/dist/client/assets/NodesView-DQzXjcLc.js +14 -0
  15. package/dist/client/assets/{PiExtensionsManager-Cc8aAZXg.js → PiExtensionsManager-Dn1LmFbq.js} +2 -2
  16. package/dist/client/assets/PluginManager-Y0fs-6No.js +1 -0
  17. package/dist/client/assets/{ResearchView-CERNf7sJ.js → ResearchView-CjOxKhdS.js} +1 -1
  18. package/dist/client/assets/{SettingsModal-B1r0yASu.js → SettingsModal-Bg1-3JO_.js} +1 -1
  19. package/dist/client/assets/{SettingsModal-Cis-4Lot.css → SettingsModal-Ci0_sqbU.css} +1 -1
  20. package/dist/client/assets/SettingsModal-DL7tjJQa.js +31 -0
  21. package/dist/client/assets/SettingsModal-DWKgRxBA.css +1 -0
  22. package/dist/client/assets/{SetupWizardModal-D1q548_L.js → SetupWizardModal-DuzYPbuJ.js} +1 -1
  23. package/dist/client/assets/{SkillsView-ClLM6u6p.js → SkillsView-BIFoVNUf.js} +1 -1
  24. package/dist/client/assets/{StashRecoveryView-ze0pEZ5U.js → StashRecoveryView-C52KsV7f.js} +1 -1
  25. package/dist/client/assets/{TodoView-CTmIfy2M.js → TodoView-sS_mT0Y7.js} +2 -2
  26. package/dist/client/assets/{dashboard-view-CyWN-d02.js → dashboard-view-BWGH_fAq.js} +1 -1
  27. package/dist/client/assets/dashboard-view-BoTzlP8b.css +1 -0
  28. package/dist/client/assets/dashboard-view-MB-86hAu.js +21 -0
  29. package/dist/client/assets/{folder-open-BZuKESeq.js → folder-open-B9cwJ-OX.js} +1 -1
  30. package/dist/client/assets/index-BOjPRqEk.js +692 -0
  31. package/dist/client/assets/index-BmSEq8Rb.css +1 -0
  32. package/dist/client/assets/{star-D75YKEq-.js → star-BDn04UYV.js} +1 -1
  33. package/dist/client/assets/{upload-BYYTgWFj.js → upload-zdPPycKQ.js} +1 -1
  34. package/dist/client/assets/{users-RS90Aii3.js → users-CPYZjK2g.js} +1 -1
  35. package/dist/client/index.html +2 -2
  36. package/dist/client/version.json +1 -1
  37. package/dist/droid-cli/package.json +1 -1
  38. package/dist/droid-cli/src/__tests__/index.test.ts +228 -0
  39. package/dist/extension.js +7433 -1920
  40. package/dist/pi-claude-cli/package.json +1 -1
  41. package/dist/pi-claude-cli/src/__tests__/provider.test.ts +36 -22
  42. package/dist/pi-claude-cli/src/provider.ts +7 -1
  43. package/dist/plugins/fusion-plugin-cli-printing-press/manifest.json +19 -1
  44. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +20 -2
  45. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/TestRunnerPanel.test.tsx +99 -0
  46. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/config-flow.test.ts +91 -0
  47. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-view.test.tsx +40 -0
  48. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/dashboard-views.test.ts +46 -0
  49. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/draft-store.test.ts +50 -0
  50. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/exec-mock.ts +80 -0
  51. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/fixtures.test.ts +40 -0
  52. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/fixtures/registry.ts +82 -0
  53. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/generator.test.ts +54 -0
  54. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manage-view.test.tsx +98 -0
  55. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/manifest.test.ts +21 -5
  56. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/registration.test.ts +29 -0
  57. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/run-routes.test.ts +98 -0
  58. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runner.test.ts +55 -0
  59. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/runtime-availability.test.ts +61 -0
  60. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/validation.test.ts +30 -0
  61. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/wizard-routes.test.ts +61 -0
  62. package/dist/plugins/fusion-plugin-cli-printing-press/src/__tests__/workflow-integration.test.ts +19 -0
  63. package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.css +43 -0
  64. package/dist/plugins/fusion-plugin-cli-printing-press/src/dashboard-view.tsx +49 -0
  65. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/generator.ts +95 -0
  66. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/redact.ts +9 -0
  67. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/runner.ts +79 -0
  68. package/dist/plugins/fusion-plugin-cli-printing-press/src/generation/types.ts +31 -0
  69. package/dist/plugins/fusion-plugin-cli-printing-press/src/index.ts +46 -2
  70. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/EditDraftModal.tsx +75 -0
  71. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage/useDrafts.ts +73 -0
  72. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.css +79 -0
  73. package/dist/plugins/fusion-plugin-cli-printing-press/src/manage-view.tsx +122 -0
  74. package/dist/plugins/fusion-plugin-cli-printing-press/src/routes/wizard-routes.ts +272 -0
  75. package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.css +70 -0
  76. package/dist/plugins/fusion-plugin-cli-printing-press/src/run/TestRunnerPanel.tsx +98 -0
  77. package/dist/plugins/fusion-plugin-cli-printing-press/src/run/useRunGeneratedCli.ts +37 -0
  78. package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/__tests__/executor-runtime-env.test.ts +191 -0
  79. package/dist/plugins/fusion-plugin-cli-printing-press/src/runtime/executor-runtime-env.ts +75 -0
  80. package/dist/plugins/fusion-plugin-cli-printing-press/src/storage/draft-store.ts +85 -0
  81. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/cli-press-store.test.ts +128 -0
  82. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/__tests__/credentials.test.ts +62 -0
  83. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-store.ts +427 -0
  84. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/cli-press-types.ts +110 -0
  85. package/dist/plugins/fusion-plugin-cli-printing-press/src/store/credentials.ts +95 -0
  86. package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/steps.tsx +55 -0
  87. package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/types.ts +33 -0
  88. package/dist/plugins/fusion-plugin-cli-printing-press/src/wizard/validation.ts +63 -0
  89. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  90. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  91. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  92. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  93. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  94. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  95. package/dist/plugins/fusion-plugin-reports/manifest.json +10 -0
  96. package/dist/plugins/fusion-plugin-reports/package.json +18 -2
  97. package/dist/plugins/fusion-plugin-reports/src/__tests__/approval.test.ts +164 -0
  98. package/dist/plugins/fusion-plugin-reports/src/__tests__/manifest.test.ts +14 -0
  99. package/dist/plugins/fusion-plugin-reports/src/__tests__/routes-approval.test.ts +109 -0
  100. package/dist/plugins/fusion-plugin-reports/src/__tests__/scaffold.test.ts +60 -0
  101. package/dist/plugins/fusion-plugin-reports/src/__tests__/share-blocks.test.ts +83 -0
  102. package/dist/plugins/fusion-plugin-reports/src/aggregation.ts +23 -0
  103. package/dist/plugins/fusion-plugin-reports/src/approval.ts +97 -0
  104. package/dist/plugins/fusion-plugin-reports/src/cadence.ts +23 -0
  105. package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.css +82 -0
  106. package/dist/plugins/fusion-plugin-reports/src/dashboard/ReportsView.tsx +24 -0
  107. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportComparisonDrawer.test.tsx +12 -0
  108. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportDetailPanel.test.tsx +12 -0
  109. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportFiltersBar.test.tsx +14 -0
  110. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/ReportsView.test.tsx +27 -0
  111. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/api.test.ts +19 -0
  112. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReportSectionDiff.test.ts +11 -0
  113. package/dist/plugins/fusion-plugin-reports/src/dashboard/__tests__/useReports.test.ts +13 -0
  114. package/dist/plugins/fusion-plugin-reports/src/dashboard/api.ts +85 -0
  115. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.css +59 -0
  116. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportApprovalPanel.tsx +58 -0
  117. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportComparisonDrawer.tsx +21 -0
  118. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportDetailPanel.tsx +29 -0
  119. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportEmptyState.tsx +3 -0
  120. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportFiltersBar.tsx +19 -0
  121. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ReportListItem.tsx +8 -0
  122. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.css +29 -0
  123. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/ShareBlocksPanel.tsx +43 -0
  124. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ReportApprovalPanel.test.tsx +38 -0
  125. package/dist/plugins/fusion-plugin-reports/src/dashboard/components/__tests__/ShareBlocksPanel.test.tsx +24 -0
  126. package/dist/plugins/fusion-plugin-reports/src/dashboard/test-setup.ts +18 -0
  127. package/dist/plugins/fusion-plugin-reports/src/dashboard/types.ts +22 -0
  128. package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportPreview.ts +44 -0
  129. package/dist/plugins/fusion-plugin-reports/src/dashboard/useReportSectionDiff.ts +59 -0
  130. package/dist/plugins/fusion-plugin-reports/src/dashboard/useReports.ts +71 -0
  131. package/dist/plugins/fusion-plugin-reports/src/dashboard/useViewportMode.ts +13 -0
  132. package/dist/plugins/fusion-plugin-reports/src/dashboard-view.tsx +6 -0
  133. package/dist/plugins/fusion-plugin-reports/src/index.ts +48 -2
  134. package/dist/plugins/fusion-plugin-reports/src/pipeline.ts +58 -0
  135. package/dist/plugins/fusion-plugin-reports/src/render/__tests__/escape.test.ts +20 -0
  136. package/dist/plugins/fusion-plugin-reports/src/render/__tests__/html-template.test.ts +110 -0
  137. package/dist/plugins/fusion-plugin-reports/src/render/__tests__/standalone-html.test.ts +66 -0
  138. package/dist/plugins/fusion-plugin-reports/src/render/escape.ts +12 -0
  139. package/dist/plugins/fusion-plugin-reports/src/render/html-styles.ts +40 -0
  140. package/dist/plugins/fusion-plugin-reports/src/render/html-template.ts +137 -0
  141. package/dist/plugins/fusion-plugin-reports/src/render/index.ts +4 -0
  142. package/dist/plugins/fusion-plugin-reports/src/render/standalone-html.ts +75 -0
  143. package/dist/plugins/fusion-plugin-reports/src/report-schema.ts +31 -0
  144. package/dist/plugins/fusion-plugin-reports/src/routes/__tests__/report-export-routes.test.ts +104 -0
  145. package/dist/plugins/fusion-plugin-reports/src/routes/report-approval-routes.ts +98 -0
  146. package/dist/plugins/fusion-plugin-reports/src/routes/report-export-routes.ts +77 -0
  147. package/dist/plugins/fusion-plugin-reports/src/routes/report-list-routes.ts +72 -0
  148. package/dist/plugins/fusion-plugin-reports/src/runs-store.ts +69 -0
  149. package/dist/plugins/fusion-plugin-reports/src/share-blocks.ts +82 -0
  150. package/dist/plugins/fusion-plugin-reports/src/store/report-store.ts +51 -2
  151. package/dist/plugins/fusion-plugin-reports/src/store/report-types.ts +6 -1
  152. package/dist/plugins/fusion-plugin-roadmap/bundled.js +1528 -29391
  153. package/dist/plugins/fusion-plugin-roadmap/manifest.json +1 -1
  154. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  155. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  156. package/package.json +1 -1
  157. package/skill/fusion/SKILL.md +1 -1
  158. package/skill/fusion/references/engine-tools.md +2 -2
  159. package/skill/fusion/references/extension-tools.md +4 -3
  160. package/skill/fusion/references/fusion-capabilities.md +1 -1
  161. package/skill/fusion/workflows/task-management.md +3 -1
  162. package/dist/client/assets/AgentDetailView-BwJaLqZh.css +0 -1
  163. package/dist/client/assets/AgentDetailView-Cv-vgOj3.js +0 -18
  164. package/dist/client/assets/ChatView-CAHjY9uO.js +0 -1
  165. package/dist/client/assets/InsightsView-Q1zvtF4F.js +0 -11
  166. package/dist/client/assets/NodesView-RxXg58_Q.js +0 -14
  167. package/dist/client/assets/PluginManager-BEkyBajl.js +0 -1
  168. package/dist/client/assets/SettingsModal-BLsac7CJ.js +0 -31
  169. package/dist/client/assets/SettingsModal-BNSrO1M9.css +0 -1
  170. package/dist/client/assets/dashboard-view-4xAN3yO5.js +0 -21
  171. package/dist/client/assets/dashboard-view-BkTMSZYn.css +0 -1
  172. package/dist/client/assets/index-Bdw6llW6.js +0 -692
  173. package/dist/client/assets/index-CZGlyJuS.css +0 -1
  174. package/dist/plugins/fusion-plugin-roadmap/bundled.css +0 -1093
@@ -0,0 +1,82 @@
1
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { Database } from "@fusion/core";
5
+ import { createCliPressStore } from "../../store/cli-press-store.js";
6
+ import { encodeCredentialValue } from "../../store/credentials.js";
7
+
8
+ export function makeFakeRegistry() {
9
+ const rootDir = mkdtempSync(join(tmpdir(), "cli-printing-press-registry-"));
10
+ const db = new Database(join(rootDir, ".fusion"), { inMemory: true });
11
+ db.init();
12
+ const store = createCliPressStore(db);
13
+
14
+ const acme = store.createService({
15
+ slug: "acme",
16
+ displayName: "Acme Service",
17
+ description: "Acme CLI",
18
+ baseUrl: "https://acme.example.com",
19
+ sourceKind: "manual",
20
+ });
21
+ const acmeSpec = store.createSpec({
22
+ serviceId: acme.id,
23
+ name: "acme-cli",
24
+ version: "1.0.0",
25
+ generatorVersion: "cli-printing-press",
26
+ specJson: JSON.stringify({ id: acme.id, slug: acme.slug }),
27
+ status: "generated",
28
+ generatedAt: new Date().toISOString(),
29
+ lastGenerationError: undefined,
30
+ });
31
+ const acmePath = `plugins/cli-printing-press/artifacts/${acme.id}/${acmeSpec.id}/acme`;
32
+ const acmeAbsPath = join(rootDir, ".fusion", acmePath);
33
+ mkdirSync(join(acmeAbsPath, ".."), { recursive: true });
34
+ writeFileSync(acmeAbsPath, "#!/bin/sh\necho acme\n");
35
+ store.createArtifact({ cliSpecId: acmeSpec.id, kind: "script", path: acmePath, executable: true });
36
+ store.createCredential({
37
+ serviceId: acme.id,
38
+ name: "token",
39
+ kind: "env_var",
40
+ placement: { kind: "env_var", envVar: "ACME_TOKEN" },
41
+ value: encodeCredentialValue("acme-secret"),
42
+ });
43
+
44
+ const beta = store.createService({
45
+ slug: "beta",
46
+ displayName: "Beta Service",
47
+ description: "Beta CLI",
48
+ baseUrl: "https://beta.example.com",
49
+ sourceKind: "manual",
50
+ });
51
+ const betaSpec = store.createSpec({
52
+ serviceId: beta.id,
53
+ name: "beta-cli",
54
+ version: "1.0.0",
55
+ generatorVersion: "cli-printing-press",
56
+ specJson: JSON.stringify({ id: beta.id, slug: beta.slug, unbuilt: true }),
57
+ status: "draft",
58
+ generatedAt: undefined,
59
+ lastGenerationError: undefined,
60
+ });
61
+ store.createCredential({
62
+ serviceId: beta.id,
63
+ name: "header-token",
64
+ kind: "header",
65
+ placement: { kind: "header", header: "X-Beta-Token" },
66
+ value: encodeCredentialValue("beta-secret"),
67
+ });
68
+
69
+ const cleanup = () => {
70
+ db.close();
71
+ rmSync(rootDir, { recursive: true, force: true });
72
+ };
73
+
74
+ return {
75
+ rootDir,
76
+ db,
77
+ store,
78
+ services: { acme, beta },
79
+ specs: { acme: acmeSpec, beta: betaSpec },
80
+ cleanup,
81
+ };
82
+ }
@@ -0,0 +1,54 @@
1
+ import { access, mkdtemp, readFile, stat } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+ import { generateCli } from "../generation/generator.js";
6
+ import { createDraftStore, getArtifactDir } from "../storage/draft-store.js";
7
+ import type { ServiceDraft } from "../wizard/types.js";
8
+
9
+ function makeDraft(id = "d-1"): ServiceDraft {
10
+ const now = new Date().toISOString();
11
+ return {
12
+ id,
13
+ name: "Demo",
14
+ slug: "demo",
15
+ description: "",
16
+ baseUrl: "https://example.com",
17
+ transport: "http",
18
+ endpoints: [{ id: "ep-1", name: "Ping", method: "GET", path: "/ping", params: "id" }],
19
+ credential: { kind: "none" },
20
+ createdAt: now,
21
+ updatedAt: now,
22
+ };
23
+ }
24
+
25
+ describe("generateCli", () => {
26
+ it("creates an executable file in artifact dir", async () => {
27
+ const rootDir = await mkdtemp(join(tmpdir(), "clipp-gen-"));
28
+ const draft = makeDraft();
29
+ const artifact = await generateCli({ draft, outDir: getArtifactDir(draft.id, rootDir) });
30
+
31
+ await access(artifact.binPath);
32
+ const contents = await readFile(artifact.binPath, "utf8");
33
+ expect(contents).toContain("const draft =");
34
+
35
+ if (process.platform !== "win32") {
36
+ const mode = (await stat(artifact.binPath)).mode & 0o777;
37
+ expect(mode).toBe(0o755);
38
+ }
39
+ });
40
+
41
+ it("supports persisting generatedAt on rerun", async () => {
42
+ const rootDir = await mkdtemp(join(tmpdir(), "clipp-gen-store-"));
43
+ const store = createDraftStore({ rootDir });
44
+ const created = await store.create(makeDraft("d-2"));
45
+
46
+ const first = await generateCli({ draft: created, outDir: getArtifactDir(created.id, rootDir) });
47
+ const updated = await store.update(created.id, { generatedAt: first.generatedAt, artifactPath: first.binPath });
48
+ const second = await generateCli({ draft: updated, outDir: getArtifactDir(updated.id, rootDir) });
49
+ const updatedAgain = await store.update(created.id, { generatedAt: second.generatedAt, artifactPath: second.binPath });
50
+
51
+ expect(updatedAgain.generatedAt).toBe(second.generatedAt);
52
+ expect(updatedAgain.generatedAt).not.toBe(first.generatedAt);
53
+ });
54
+ });
@@ -0,0 +1,98 @@
1
+ // @vitest-environment jsdom
2
+ import { render, screen, waitFor } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { CliPrintingPressManageView } from "../manage-view";
6
+ import type { ServiceDraft } from "../wizard/types";
7
+
8
+ vi.mock("lucide-react", () => ({
9
+ List: () => null,
10
+ Pencil: () => null,
11
+ RefreshCw: () => null,
12
+ Trash2: () => null,
13
+ Play: () => null,
14
+ CheckCircle2: () => null,
15
+ AlertTriangle: () => null,
16
+ }));
17
+
18
+ function makeDraft(id: string, name = "Demo"): ServiceDraft {
19
+ const now = new Date().toISOString();
20
+ return {
21
+ id,
22
+ name,
23
+ slug: id,
24
+ description: "",
25
+ baseUrl: `https://${id}.example.com`,
26
+ transport: "http",
27
+ endpoints: [{ id: `${id}-e1`, name: "Ping", method: "GET", path: "/ping" }],
28
+ credential: { kind: "none" },
29
+ createdAt: now,
30
+ updatedAt: now,
31
+ };
32
+ }
33
+
34
+ describe("CliPrintingPressManageView", () => {
35
+ beforeEach(() => {
36
+ vi.restoreAllMocks();
37
+ });
38
+
39
+ it("renders empty state", async () => {
40
+ vi.stubGlobal("fetch", vi.fn(async () => ({ ok: true, json: async () => [] })));
41
+ render(<CliPrintingPressManageView />);
42
+ expect(await screen.findByText(/No saved drafts yet/i)).toBeTruthy();
43
+ });
44
+
45
+ it("renders list, detail, edit/save, regenerate, and delete", async () => {
46
+ const draft1 = makeDraft("draft-1", "Draft One");
47
+ const draft2 = makeDraft("draft-2", "Draft Two");
48
+ const updated = { ...draft1, name: "Draft One Edited" };
49
+
50
+ const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
51
+ const url = String(input);
52
+ const method = init?.method ?? "GET";
53
+
54
+ if (method === "GET" && url.endsWith("/drafts")) {
55
+ return { ok: true, json: async () => ([
56
+ { id: draft1.id, name: draft1.name, slug: draft1.slug, updatedAt: draft1.updatedAt },
57
+ { id: draft2.id, name: draft2.name, slug: draft2.slug, updatedAt: draft2.updatedAt },
58
+ ]) };
59
+ }
60
+ if (method === "GET" && url.endsWith(`/drafts/${draft1.id}`)) return { ok: true, json: async () => draft1 };
61
+ if (method === "GET" && url.endsWith(`/drafts/${draft2.id}`)) return { ok: true, json: async () => draft2 };
62
+ if (method === "PUT" && url.endsWith(`/drafts/${draft1.id}`)) return { ok: true, json: async () => updated };
63
+ if (method === "POST" && url.endsWith(`/drafts/${draft1.id}/regenerate`)) {
64
+ const generatedAt = new Date().toISOString();
65
+ return { ok: true, json: async () => ({ draft: { ...updated, regeneratedAt: generatedAt, generatedAt, artifactPath: "/tmp/demo.mjs" }, artifact: { draftId: draft1.id, slug: draft1.slug, binPath: "/tmp/demo.mjs", entrypoint: "node", generatedAt } }) };
66
+ }
67
+ if (method === "DELETE" && url.endsWith(`/drafts/${draft1.id}`)) return { ok: true, status: 204, json: async () => ({}) };
68
+ return { ok: false, status: 404, json: async () => ({ error: "Not found" }) };
69
+ });
70
+
71
+ vi.stubGlobal("fetch", fetchMock);
72
+ vi.spyOn(globalThis, "confirm").mockReturnValue(true);
73
+
74
+ const user = userEvent.setup();
75
+ render(<CliPrintingPressManageView />);
76
+
77
+ expect(await screen.findByText("Draft One")).toBeTruthy();
78
+ expect(screen.getByText("Draft Two")).toBeTruthy();
79
+ expect(await screen.findByText(/Base URL:/)).toBeTruthy();
80
+
81
+ await user.click(screen.getByRole("button", { name: /Edit/i }));
82
+ await user.clear(screen.getByLabelText("Name"));
83
+ await user.type(screen.getByLabelText("Name"), "Draft One Edited");
84
+ await user.click(screen.getByRole("button", { name: "Next" }));
85
+ await user.click(screen.getByRole("button", { name: "Next" }));
86
+ await user.click(screen.getByRole("button", { name: "Next" }));
87
+ await user.click(screen.getByRole("button", { name: "Next" }));
88
+ await user.click(screen.getByRole("button", { name: "Save" }));
89
+
90
+ await waitFor(() => expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining(`/drafts/${draft1.id}`), expect.objectContaining({ method: "PUT" })));
91
+
92
+ await user.click(screen.getByRole("button", { name: /^Regenerate$/i }));
93
+ expect(await screen.findByText(/Regenerated at/i)).toBeTruthy();
94
+
95
+ await user.click(screen.getByRole("button", { name: /Delete/i }));
96
+ await waitFor(() => expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining(`/drafts/${draft1.id}`), expect.objectContaining({ method: "DELETE" })));
97
+ });
98
+ });
@@ -1,9 +1,9 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, expect, it } from "vitest";
2
2
  import { validatePluginManifest } from "@fusion/core";
3
3
  import plugin from "../index.js";
4
4
  import manifestJson from "../../manifest.json" with { type: "json" };
5
5
 
6
- describe("cli-printing-press plugin stub (FN-3762)", () => {
6
+ describe("cli-printing-press plugin", () => {
7
7
  it("registers with the correct manifest id", () => {
8
8
  expect(plugin.manifest.id).toBe("fusion-plugin-cli-printing-press");
9
9
  });
@@ -13,8 +13,24 @@ describe("cli-printing-press plugin stub (FN-3762)", () => {
13
13
  expect(validation.valid).toBe(true);
14
14
  });
15
15
 
16
- it("has no routes or dashboardViews in the stub", () => {
17
- expect(plugin.routes).toBeUndefined();
18
- expect(plugin.dashboardViews).toBeUndefined();
16
+ it("registers the wizard and manage dashboard views", () => {
17
+ expect(plugin.dashboardViews).toEqual([
18
+ {
19
+ viewId: "wizard",
20
+ label: "Create Service CLI",
21
+ componentPath: "./dashboard-view",
22
+ icon: "Wand2",
23
+ placement: "primary",
24
+ order: 60,
25
+ },
26
+ {
27
+ viewId: "manage",
28
+ label: "Manage Service CLIs",
29
+ componentPath: "./manage-view",
30
+ icon: "List",
31
+ placement: "primary",
32
+ order: 61,
33
+ },
34
+ ]);
19
35
  });
20
36
  });
@@ -0,0 +1,29 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { validatePluginManifest } from "@fusion/core";
3
+ import plugin from "../index.js";
4
+ import { ensureCliPressSchema } from "../store/cli-press-store.js";
5
+ import { makeFakeRegistry } from "./fixtures/registry.js";
6
+
7
+ describe("plugin registration contracts", () => {
8
+ it("declares expected manifest and semver version", () => {
9
+ expect(plugin.manifest.id).toBe("fusion-plugin-cli-printing-press");
10
+ expect(plugin.manifest.version).toMatch(/^\d+\.\d+\.\d+$/);
11
+ expect(validatePluginManifest(plugin.manifest).valid).toBe(true);
12
+ });
13
+
14
+ it("registers schema, routes, dashboard views and executor runtime hook", () => {
15
+ const h = makeFakeRegistry();
16
+ try {
17
+ expect(() => ensureCliPressSchema(h.db)).not.toThrow();
18
+ expect(plugin.routes?.some((route) => route.path === "/drafts")).toBe(true);
19
+ expect(plugin.dashboardViews?.map((view) => view.viewId)).toEqual(["wizard", "manage"]);
20
+ expect(typeof plugin.executorRuntimeEnv).toBe("function");
21
+ } finally {
22
+ h.cleanup();
23
+ }
24
+ });
25
+
26
+ it.skip("TODO(FN-3768): assert plugin workflow-step template contributions once shipped", () => {
27
+ // This branch does not yet export workflow step templates/contributions for cli-printing-press.
28
+ });
29
+ });
@@ -0,0 +1,98 @@
1
+ import { mkdtemp } from "node:fs/promises";
2
+ import { createServer } from "node:http";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { afterEach, describe, expect, it } from "vitest";
6
+ import { Database } from "@fusion/core";
7
+ import { createCliPrintingPressRoutes } from "../routes/wizard-routes.js";
8
+ import type { ServiceDraft } from "../wizard/types.js";
9
+
10
+ function makeDraft(baseUrl: string): ServiceDraft {
11
+ const now = new Date().toISOString();
12
+ return {
13
+ id: "",
14
+ name: "Demo",
15
+ slug: "demo",
16
+ description: "",
17
+ baseUrl,
18
+ transport: "http",
19
+ endpoints: [{ id: "e1", name: "Ping", method: "GET", path: "/ping" }],
20
+ credential: { kind: "none" },
21
+ createdAt: now,
22
+ updatedAt: now,
23
+ };
24
+ }
25
+
26
+ function route(method: string, path: string) {
27
+ const found = createCliPrintingPressRoutes().find((entry) => entry.method === method && entry.path === path);
28
+ if (!found) throw new Error(`missing route ${method} ${path}`);
29
+ return found;
30
+ }
31
+
32
+ const servers: Array<{ close: () => void }> = [];
33
+ afterEach(() => {
34
+ while (servers.length) servers.pop()?.close();
35
+ });
36
+
37
+ async function startServer(handler: (req: any, res: any) => void): Promise<string> {
38
+ const server = createServer(handler);
39
+ await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
40
+ servers.push(server);
41
+ const addr = server.address();
42
+ if (!addr || typeof addr === "string") throw new Error("Invalid address");
43
+ return `http://127.0.0.1:${addr.port}`;
44
+ }
45
+
46
+ describe("run routes", () => {
47
+ it("regenerates and runs successfully", async () => {
48
+ const baseUrl = await startServer((_req, res) => {
49
+ res.statusCode = 200;
50
+ res.end("pong");
51
+ });
52
+ const rootDir = await mkdtemp(join(tmpdir(), "cli-printing-press-run-routes-"));
53
+ const db = new Database(join(rootDir, ".fusion"), { inMemory: true });
54
+ db.init();
55
+ const ctx = { taskStore: { getRootDir: () => rootDir, getDatabase: () => db } } as any;
56
+
57
+ const createRes = await route("POST", "/drafts").handler({ params: {}, body: makeDraft(baseUrl) }, ctx);
58
+ const id = (createRes.body as { id: string }).id;
59
+
60
+ const regenRes = await route("POST", "/drafts/:id/regenerate").handler({ params: { id } }, ctx);
61
+ expect(regenRes.status).toBe(200);
62
+ expect((regenRes.body as any).stub).toBeUndefined();
63
+
64
+ const runRes = await route("POST", "/drafts/:id/run").handler({ params: { id }, body: { endpointId: "e1", params: {} } }, ctx);
65
+ expect(runRes.status).toBe(200);
66
+ expect((runRes.body as any).stdout).toContain("pong");
67
+ });
68
+
69
+ it("returns validation, 404, 409, and timeout responses", async () => {
70
+ const baseUrl = await startServer((_req, res) => {
71
+ setTimeout(() => {
72
+ res.statusCode = 200;
73
+ res.end("slow");
74
+ }, 50);
75
+ });
76
+ const rootDir = await mkdtemp(join(tmpdir(), "cli-printing-press-run-routes-"));
77
+ const db = new Database(join(rootDir, ".fusion"), { inMemory: true });
78
+ db.init();
79
+ const ctx = { taskStore: { getRootDir: () => rootDir, getDatabase: () => db } } as any;
80
+
81
+ const createRes = await route("POST", "/drafts").handler({ params: {}, body: makeDraft(baseUrl) }, ctx);
82
+ const id = (createRes.body as { id: string }).id;
83
+
84
+ const noArtifact = await route("POST", "/drafts/:id/run").handler({ params: { id }, body: { endpointId: "e1", params: {} } }, ctx);
85
+ expect(noArtifact.status).toBe(409);
86
+
87
+ const badBody = await route("POST", "/drafts/:id/run").handler({ params: { id }, body: { endpointId: "", params: {} } }, ctx);
88
+ expect(badBody.status).toBe(400);
89
+
90
+ const unknown = await route("POST", "/drafts/:id/run").handler({ params: { id: "missing" }, body: { endpointId: "e1", params: {} } }, ctx);
91
+ expect(unknown.status).toBe(404);
92
+
93
+ await route("POST", "/drafts/:id/regenerate").handler({ params: { id } }, ctx);
94
+ const timeout = await route("POST", "/drafts/:id/run").handler({ params: { id }, body: { endpointId: "e1", params: {}, timeoutMs: 1 } }, ctx);
95
+ expect(timeout.status).toBe(200);
96
+ expect((timeout.body as any).timedOut).toBe(true);
97
+ });
98
+ });
@@ -0,0 +1,55 @@
1
+ import { mkdtemp, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+ import { runGeneratedCli } from "../generation/runner.js";
6
+ import type { GeneratedCliArtifact } from "../generation/types.js";
7
+
8
+ async function writeFixture(script: string): Promise<{ artifact: GeneratedCliArtifact; root: string }> {
9
+ const root = await mkdtemp(join(tmpdir(), "clipp-runner-"));
10
+ const binPath = join(root, "fixture.mjs");
11
+ await writeFile(binPath, script, "utf8");
12
+ return {
13
+ root,
14
+ artifact: {
15
+ draftId: "draft-1",
16
+ slug: "fixture",
17
+ binPath,
18
+ entrypoint: "node",
19
+ generatedAt: new Date().toISOString(),
20
+ },
21
+ };
22
+ }
23
+
24
+ describe("runGeneratedCli", () => {
25
+ it("returns success output", async () => {
26
+ const { artifact, root } = await writeFixture("console.log('ok-output')");
27
+ const result = await runGeneratedCli({ artifact, endpointId: "x", params: { enabled: true }, cwd: root });
28
+ expect(result.exitCode).toBe(0);
29
+ expect(result.stdout).toContain("ok-output");
30
+ expect(result.timedOut).toBe(false);
31
+ });
32
+
33
+ it("captures non-zero exit", async () => {
34
+ const { artifact, root } = await writeFixture("console.error('bad-output'); process.exit(7)");
35
+ const result = await runGeneratedCli({ artifact, endpointId: "x", params: {}, cwd: root });
36
+ expect(result.exitCode).toBe(7);
37
+ expect(result.stderr).toContain("bad-output");
38
+ });
39
+
40
+ it("captures timeout", async () => {
41
+ const { artifact, root } = await writeFixture("await new Promise((resolve) => setTimeout(resolve, 500)); console.log('late')");
42
+ const result = await runGeneratedCli({ artifact, endpointId: "x", params: {}, timeoutMs: 1, cwd: root });
43
+ expect(result.timedOut).toBe(true);
44
+ expect(result.exitCode).toBeNull();
45
+ });
46
+
47
+ it("redacts credentials from stdout and argv echo", async () => {
48
+ const secret = "super-secret-value";
49
+ const { artifact, root } = await writeFixture("console.log(process.env.CLIPP_CRED_API_KEY)");
50
+ const result = await runGeneratedCli({ artifact, endpointId: "x", params: { token: secret }, credentials: { api_key: secret }, cwd: root });
51
+ expect(result.stdout).not.toContain(secret);
52
+ expect(result.argv.join(" ")).not.toContain(secret);
53
+ expect(result.stdout).toContain("***");
54
+ });
55
+ });
@@ -0,0 +1,61 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import plugin from "../index.js";
3
+ import { buildExecutorRuntimeEnv } from "../runtime/executor-runtime-env.js";
4
+ import { makeFakeRegistry } from "./fixtures/registry.js";
5
+
6
+ describe("runtime availability", () => {
7
+ it("exposes executor runtime env hook through plugin entry", () => {
8
+ expect(typeof plugin.executorRuntimeEnv).toBe("function");
9
+ });
10
+
11
+ it("returns PATH/env entries for generated CLIs only", () => {
12
+ const h = makeFakeRegistry();
13
+ try {
14
+ const result = buildExecutorRuntimeEnv(
15
+ h.store,
16
+ { taskId: "FN-3769", worktreePath: h.rootDir, rootDir: h.rootDir },
17
+ {
18
+ pluginId: "fusion-plugin-cli-printing-press",
19
+ taskStore: {} as never,
20
+ settings: {},
21
+ logger: { info() {}, warn() {}, error() {}, debug() {} },
22
+ emitEvent() {},
23
+ },
24
+ );
25
+
26
+ expect(result.pathPrepend).toHaveLength(1);
27
+ expect(result.pathPrepend[0]).toContain(`/artifacts/${h.services.acme.id}/${h.specs.acme.id}`);
28
+ expect(result.env).toEqual({ ACME_TOKEN: "acme-secret" });
29
+ } finally {
30
+ h.cleanup();
31
+ }
32
+ });
33
+
34
+ it("skips draft specs from PATH contributions", () => {
35
+ const h = makeFakeRegistry();
36
+ try {
37
+ const betaArtifacts = h.store.listArtifacts(h.specs.beta.id);
38
+ expect(betaArtifacts).toHaveLength(0);
39
+
40
+ const result = buildExecutorRuntimeEnv(
41
+ h.store,
42
+ { taskId: "FN-3769", worktreePath: h.rootDir, rootDir: h.rootDir },
43
+ {
44
+ pluginId: "fusion-plugin-cli-printing-press",
45
+ taskStore: {} as never,
46
+ settings: {},
47
+ logger: { info() {}, warn() {}, error() {}, debug() {} },
48
+ emitEvent() {},
49
+ },
50
+ );
51
+
52
+ expect(result.pathPrepend.some((entry) => entry.includes(`/artifacts/${h.services.beta.id}/`))).toBe(false);
53
+ } finally {
54
+ h.cleanup();
55
+ }
56
+ });
57
+
58
+ it.skip("TODO(FN-3767): cover resolveGeneratedCliInvocation contract once exported", () => {
59
+ // FN-3767 documented resolveGeneratedCliInvocation, but this branch has only buildExecutorRuntimeEnv.
60
+ });
61
+ });
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { ServiceDraft } from "../wizard/types";
3
+ import { validateBasics, validateCredentials, validateDraft, validateEndpoints, validateTransport } from "../wizard/validation";
4
+
5
+ function makeDraft(): ServiceDraft {
6
+ const now = new Date().toISOString();
7
+ return { id: "", name: "GitHub", slug: "github", description: "", baseUrl: "https://api.github.com", transport: "http", endpoints: [{ id: "e1", name: "List Repos", method: "GET", path: "/user/repos" }], credential: { kind: "apiKey", header: "Authorization", envVar: "GITHUB_TOKEN" }, createdAt: now, updatedAt: now };
8
+ }
9
+
10
+ describe("wizard validation", () => {
11
+ it("validates basics", () => {
12
+ expect(validateBasics(makeDraft()).ok).toBe(true);
13
+ expect(validateBasics({ ...makeDraft(), slug: "Bad Slug" }).ok).toBe(false);
14
+ });
15
+ it("validates transport", () => {
16
+ expect(validateTransport(makeDraft()).ok).toBe(true);
17
+ });
18
+ it("validates endpoints", () => {
19
+ expect(validateEndpoints(makeDraft()).ok).toBe(true);
20
+ expect(validateEndpoints({ ...makeDraft(), endpoints: [] }).ok).toBe(false);
21
+ });
22
+ it("validates credentials", () => {
23
+ expect(validateCredentials({ kind: "bearerToken", envVar: "TOKEN" }).ok).toBe(true);
24
+ expect(validateCredentials({ kind: "bearerToken", envVar: "" }).ok).toBe(false);
25
+ });
26
+ it("validates full draft", () => {
27
+ expect(validateDraft(makeDraft()).ok).toBe(true);
28
+ expect(validateDraft({ ...makeDraft(), baseUrl: "not-url" }).ok).toBe(false);
29
+ });
30
+ });
@@ -0,0 +1,61 @@
1
+ import { mkdtemp } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+ import { Database } from "@fusion/core";
6
+ import { createCliPrintingPressRoutes } from "../routes/wizard-routes";
7
+ import type { ServiceDraft } from "../wizard/types";
8
+
9
+ function makeDraft(): ServiceDraft {
10
+ const now = new Date().toISOString();
11
+ return { id: "", name: "Demo", slug: "demo", description: "", baseUrl: "https://example.com", transport: "http", endpoints: [{ id: "e1", name: "Ping", method: "GET", path: "/ping" }], credential: { kind: "none" }, createdAt: now, updatedAt: now };
12
+ }
13
+
14
+ function route(method: string, path: string) {
15
+ const found = createCliPrintingPressRoutes().find((entry) => entry.method === method && entry.path === path);
16
+ if (!found) throw new Error(`missing route ${method} ${path}`);
17
+ return found;
18
+ }
19
+
20
+ describe("wizard routes", () => {
21
+ it("handles create/get/delete lifecycle", async () => {
22
+ const rootDir = await mkdtemp(join(tmpdir(), "cli-printing-press-routes-"));
23
+ const db = new Database(join(rootDir, ".fusion"), { inMemory: true });
24
+ db.init();
25
+ const ctx = { taskStore: { getRootDir: () => rootDir, getDatabase: () => db } } as any;
26
+
27
+ const createRes = await route("POST", "/drafts").handler({ params: {}, body: makeDraft() }, ctx);
28
+ expect(createRes.status).toBe(201);
29
+ const id = (createRes.body as { id: string }).id;
30
+
31
+ const getRes = await route("GET", "/drafts/:id").handler({ params: { id } }, ctx);
32
+ expect(getRes.status).toBe(200);
33
+
34
+ const missRes = await route("GET", "/drafts/:id").handler({ params: { id: "missing" } }, ctx);
35
+ expect(missRes.status).toBe(404);
36
+
37
+ const invalidRes = await route("POST", "/drafts").handler({ params: {}, body: { ...makeDraft(), slug: "Bad Slug" } }, ctx);
38
+ expect(invalidRes.status).toBe(400);
39
+ expect((invalidRes.body as { errors: Record<string, string> }).errors.slug).toBeTruthy();
40
+
41
+ const putRes = await route("PUT", "/drafts/:id").handler({ params: { id }, body: { ...makeDraft(), id, name: "Renamed" } }, ctx);
42
+ expect(putRes.status).toBe(200);
43
+ expect((putRes.body as { name: string }).name).toBe("Renamed");
44
+
45
+ const invalidPutRes = await route("PUT", "/drafts/:id").handler({ params: { id }, body: { ...makeDraft(), id, baseUrl: "invalid-url" } }, ctx);
46
+ expect(invalidPutRes.status).toBe(400);
47
+
48
+ const missingPutRes = await route("PUT", "/drafts/:id").handler({ params: { id: "missing" }, body: { ...makeDraft(), id: "missing" } }, ctx);
49
+ expect(missingPutRes.status).toBe(404);
50
+
51
+ const regenRes = await route("POST", "/drafts/:id/regenerate").handler({ params: { id } }, ctx);
52
+ expect(regenRes.status).toBe(200);
53
+ expect((regenRes.body as { artifact?: { binPath: string } }).artifact?.binPath).toBeTruthy();
54
+
55
+ const missingRegenRes = await route("POST", "/drafts/:id/regenerate").handler({ params: { id: "missing" } }, ctx);
56
+ expect(missingRegenRes.status).toBe(404);
57
+
58
+ const deleteRes = await route("DELETE", "/drafts/:id").handler({ params: { id } }, ctx);
59
+ expect(deleteRes.status).toBe(204);
60
+ });
61
+ });
@@ -0,0 +1,19 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import plugin from "../index.js";
3
+ import { installExecMock } from "./fixtures/exec-mock.js";
4
+
5
+ describe("workflow integration contracts", () => {
6
+ it("guards against execSync usage in workflow-oriented execution fixtures", () => {
7
+ const execMock = installExecMock();
8
+ execMock.assertExecSyncUnused();
9
+ expect(typeof plugin.manifest.id).toBe("string");
10
+ });
11
+
12
+ it.skip("TODO(FN-3768): execute script-mode workflow handler once plugin workflow step contributions are available", () => {
13
+ // Missing in this branch: cli-printing-press workflow step contribution + script handler wiring.
14
+ });
15
+
16
+ it.skip("TODO(FN-3768): run through runWorkflowSteps with plugin:cli-printing-press:<id>", () => {
17
+ // packages/core resolvePluginWorkflowStep currently hard-codes mode="prompt".
18
+ });
19
+ });