@burdenoff/fluidgrids-fe-sdk 2026.1028.6 → 2026.1028.8

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 (50) hide show
  1. package/dist/ExecutionDetailPage-ACTSQG5E.js +20 -0
  2. package/dist/{ExecutionsListPage-IMN7OTNZ.js → ExecutionsListPage-FABXLXWT.js} +8 -8
  3. package/dist/WorkflowBuilder-OECFTQDQ.js +24 -0
  4. package/dist/WorkflowsList-4HG3KBMR.js +20 -0
  5. package/dist/{chunk-IQE7ZY6Q.js → chunk-2ZV6UJD6.js} +2 -2
  6. package/dist/{chunk-F6NH55LU.js → chunk-3GOZYLTM.js} +3 -3
  7. package/dist/{chunk-ACUNTZTQ.js → chunk-3LYRZ4KN.js} +3 -3
  8. package/dist/{chunk-MKKBEVXO.js → chunk-6T5EBO7G.js} +1 -1
  9. package/dist/chunk-77NA7TPJ.js +38 -0
  10. package/dist/{chunk-QW24ZBSR.js → chunk-7OJDILWM.js} +1 -1
  11. package/dist/{chunk-6J4RR46J.js → chunk-7PBOU7JC.js} +2 -1
  12. package/dist/{chunk-NAFJQGLV.js → chunk-DL2OCKHC.js} +5 -5
  13. package/dist/{chunk-LOZ6XNOX.js → chunk-DOPSPCO3.js} +26 -1
  14. package/dist/{chunk-FO22JCOW.js → chunk-EAF47LYM.js} +368 -103
  15. package/dist/{chunk-XZPXIOGE.js → chunk-GGINSR75.js} +1402 -7
  16. package/dist/{chunk-FDEZGRRL.js → chunk-GQQV2L74.js} +1 -1
  17. package/dist/{chunk-UWNYWWWG.js → chunk-ILTWXDRG.js} +4 -4
  18. package/dist/{chunk-VP2RXXRF.js → chunk-J3PETXDA.js} +7 -7
  19. package/dist/{chunk-WBH2XEWO.js → chunk-JUUYPCFY.js} +4 -1
  20. package/dist/chunk-M62CJVTR.js +458 -0
  21. package/dist/{chunk-C4AY6JI2.js → chunk-NVUJFHTQ.js} +2 -2
  22. package/dist/{chunk-A2JSAGNQ.js → chunk-OAFNNQN7.js} +1 -1
  23. package/dist/{chunk-RLA4YUDY.js → chunk-OYFBLD5F.js} +3 -3
  24. package/dist/{chunk-STPQQDIJ.js → chunk-QBEQOQF7.js} +867 -127
  25. package/dist/{chunk-3KNW5QLR.js → chunk-SQ6VSXFI.js} +2 -2
  26. package/dist/chunk-TX6WIT3N.js +749 -0
  27. package/dist/{chunk-I2ZNBBRK.js → chunk-VFPIMW52.js} +3 -14
  28. package/dist/{chunk-VQHBQAJY.js → chunk-VZHR7FCD.js} +3 -3
  29. package/dist/{chunk-3VJ3AIFS.js → chunk-YNOR2AGJ.js} +2 -2
  30. package/dist/{chunk-RZQPHWFV.js → chunk-YQZ7HBOJ.js} +5 -3
  31. package/dist/{chunk-Q22GC7RJ.js → chunk-ZFCXCSY4.js} +1 -1
  32. package/dist/executions.js +13 -13
  33. package/dist/global-operations-D2USVOPM.js +2 -0
  34. package/dist/history.js +5 -5
  35. package/dist/index.d.ts +1294 -57
  36. package/dist/index.js +37 -35
  37. package/dist/nodes.js +2 -2
  38. package/dist/observability.js +8 -7
  39. package/dist/settings.js +8 -6
  40. package/dist/templates.js +12 -10
  41. package/dist/{workflow-builder-C7qKY7LD.d.ts → workflow-builder-BxyapBo5.d.ts} +18 -0
  42. package/dist/workflow-builder.d.ts +1 -1
  43. package/dist/workflow-builder.js +14 -12
  44. package/dist/workflows-list.js +10 -8
  45. package/package.json +1 -1
  46. package/dist/ExecutionDetailPage-DHIQNBAZ.js +0 -20
  47. package/dist/WorkflowBuilder-VJAAKETX.js +0 -22
  48. package/dist/WorkflowsList-3EWDWWIH.js +0 -18
  49. package/dist/chunk-HHN2TIK2.js +0 -59
  50. package/dist/global-operations-ZWQZ7DNT.js +0 -2
@@ -1,17 +1,18 @@
1
- import { CreateWorkflowRefusalDialog, firstWorkflowProgress, GUIDED_TOUR_STATE, builderPathFor } from './chunk-A2JSAGNQ.js';
2
- import { useExecutions } from './chunk-3VJ3AIFS.js';
3
- import { QuotaExhaustedDialog } from './chunk-HHN2TIK2.js';
4
- import { useCreateWorkflow, useWorkflowDefinitions, useLazyWorkflowDefinition, useImportWorkflow, useDuplicateWorkflow, useDeleteWorkflow } from './chunk-RZQPHWFV.js';
1
+ import { CreateWorkflowRefusalDialog, firstWorkflowProgress, GUIDED_TOUR_STATE, builderPathFor } from './chunk-OAFNNQN7.js';
2
+ import { useExecutions } from './chunk-YNOR2AGJ.js';
3
+ import { useExportWorkflowAs, downloadWorkflowExport, importAcceptAttribute, QuotaExhaustedDialog, importFileKind, parseWorkflowImport, CODE_EXPORT_FORMATS, ImportSourceError, assertImportByteSize, MAX_IMPORT_ARCHIVE_BYTES, readProjectArchive, checkTypeScriptImport, MAX_IMPORT_SOURCE_BYTES, typeScriptModuleFile, typeScriptImportSource, workflowNameFromFile, primaryImportModule } from './chunk-M62CJVTR.js';
4
+ import { useCreateWorkflow, useWorkflowDefinitions, useLazyWorkflowDefinition, useImportWorkflow, useDuplicateWorkflow, useDeleteWorkflow } from './chunk-YQZ7HBOJ.js';
5
5
  import { workflowsListControls } from './chunk-D3UWK7GY.js';
6
6
  import { useFluidGridsPageControls } from './chunk-PJAWNFJV.js';
7
7
  import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
8
- import { createThenOpen, useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, SdkPermissionButton, BILLING_PLANS_PATH, useFlowStore, SdkPermissionGate, isQuotaExhaustedFailure } from './chunk-C4AY6JI2.js';
8
+ import { useFluidGridsFlag, FLUIDGRIDS_FLAGS } from './chunk-77NA7TPJ.js';
9
+ import { createThenOpen, useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, SdkPermissionButton, BILLING_PLANS_PATH, describeOperationError, useFlowStore, SdkPermissionGate, isQuotaExhaustedFailure } from './chunk-NVUJFHTQ.js';
9
10
  import { useTr } from './chunk-UULVIT25.js';
10
11
  import { useState, useRef, useCallback, useMemo } from 'react';
11
12
  import { useNavigate } from 'react-router';
12
13
  import { v4 } from 'uuid';
13
14
  import { Icon } from '@iconify/react';
14
- import { NextSteps, Button, Badge, Input, IllustratedEmptyState, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Label, Textarea, DialogFooter, Skeleton, TooltipProvider, Card, Tooltip, TooltipTrigger, TooltipContent, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from '@burdenoff/fe-libs/ui';
15
+ import { NextSteps, Button, Badge, Input, IllustratedEmptyState, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Label, Textarea, DialogFooter, Skeleton, TooltipProvider, Card, Tooltip, TooltipTrigger, TooltipContent, AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@burdenoff/fe-libs/ui';
15
16
  import { cn } from '@burdenoff/fe-libs/utils';
16
17
  import { TagBadge } from '@burdenoff/fe-libs/tag';
17
18
  import { toast } from 'sonner';
@@ -269,6 +270,72 @@ function WorkflowStatusPill({ status }) {
269
270
  info.label
270
271
  ] });
271
272
  }
273
+ function exportFormatLabel(tr, format) {
274
+ switch (format) {
275
+ case "TYPESCRIPT" /* Typescript */:
276
+ return tr(
277
+ "fluidgrids.builder.cardWorkflow.exportAsTypeScript",
278
+ "Export as TypeScript"
279
+ );
280
+ case "PYTHON" /* Python */:
281
+ return tr(
282
+ "fluidgrids.builder.cardWorkflow.exportAsPython",
283
+ "Export as Python"
284
+ );
285
+ default:
286
+ return tr(
287
+ "fluidgrids.builder.cardWorkflow.exportAsJson",
288
+ "Export as JSON"
289
+ );
290
+ }
291
+ }
292
+ var FORMAT_ICON = {
293
+ ["TYPESCRIPT" /* Typescript */]: "mynaui:code",
294
+ ["PYTHON" /* Python */]: "mynaui:code-square",
295
+ ["JSON" /* Json */]: "mynaui:file-text"
296
+ };
297
+ function WorkflowExportMenu({
298
+ onExportAs,
299
+ exporting
300
+ }) {
301
+ const tr = useTr();
302
+ const label = tr("fluidgrids.builder.cardWorkflow.exportMenu", "Export");
303
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
304
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
305
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
306
+ Button,
307
+ {
308
+ variant: "ghost",
309
+ size: "icon",
310
+ className: "size-8",
311
+ "aria-label": label,
312
+ "data-testid": "fluidgrids-card-export-menu",
313
+ disabled: exporting !== null,
314
+ children: /* @__PURE__ */ jsx(
315
+ Icon,
316
+ {
317
+ icon: exporting ? "mynaui:spinner" : "mynaui:download",
318
+ className: exporting ? "size-4 animate-spin" : "size-4"
319
+ }
320
+ )
321
+ }
322
+ ) }) }),
323
+ /* @__PURE__ */ jsx(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx("p", { children: label }) })
324
+ ] }),
325
+ /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", "data-card-action": "", children: CODE_EXPORT_FORMATS.map((format) => /* @__PURE__ */ jsxs(
326
+ DropdownMenuItem,
327
+ {
328
+ onClick: () => onExportAs(format),
329
+ "data-testid": `fluidgrids-card-export-${format.toLowerCase()}`,
330
+ children: [
331
+ /* @__PURE__ */ jsx(Icon, { icon: FORMAT_ICON[format], className: "mr-2 size-4" }),
332
+ exportFormatLabel(tr, format)
333
+ ]
334
+ },
335
+ format
336
+ )) })
337
+ ] });
338
+ }
272
339
  function relativeTime(iso) {
273
340
  if (!iso) return "\u2014";
274
341
  const then = new Date(iso).getTime();
@@ -289,7 +356,9 @@ function relativeTime(iso) {
289
356
  var CardWorkflow = ({
290
357
  workflow,
291
358
  onUpdateSuccess,
292
- onExport
359
+ onExport,
360
+ onExportAs,
361
+ exportingFormat = null
293
362
  }) => {
294
363
  const tr = useTr();
295
364
  const navigate = useNavigate();
@@ -492,7 +561,13 @@ var CardWorkflow = ({
492
561
  "Sharing is not available for workflows yet"
493
562
  ) }) })
494
563
  ] }),
495
- onExport && /* @__PURE__ */ jsxs(Tooltip, { children: [
564
+ onExportAs ? /* @__PURE__ */ jsx("span", { "data-card-action": "", children: /* @__PURE__ */ jsx(
565
+ WorkflowExportMenu,
566
+ {
567
+ onExportAs,
568
+ exporting: exportingFormat
569
+ }
570
+ ) }) : onExport ? /* @__PURE__ */ jsxs(Tooltip, { children: [
496
571
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
497
572
  Button,
498
573
  {
@@ -511,7 +586,7 @@ var CardWorkflow = ({
511
586
  "fluidgrids.builder.cardWorkflow.exportWorkflow",
512
587
  "Export workflow JSON"
513
588
  ) }) })
514
- ] }),
589
+ ] }) : null,
515
590
  /* @__PURE__ */ jsx(
516
591
  SdkPermissionGate,
517
592
  {
@@ -621,91 +696,89 @@ var CardWorkflow = ({
621
696
  )
622
697
  ] });
623
698
  };
624
-
625
- // src/utils/workflow-transfer.ts
626
- function asRecord(value) {
627
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
699
+ function formatMiB(bytes) {
700
+ const mib = bytes / (1024 * 1024);
701
+ return Number.isInteger(mib) ? String(mib) : mib.toFixed(1);
628
702
  }
629
- function collectWorkflowNodeReferences(definition) {
630
- const root = asRecord(definition);
631
- const data = asRecord(root?.data);
632
- const nodes = data?.nodes;
633
- if (!Array.isArray(nodes)) return [];
634
- const seen = /* @__PURE__ */ new Set();
635
- const references = [];
636
- for (const candidate of nodes) {
637
- const node = asRecord(candidate);
638
- const nodeData = asRecord(node?.data);
639
- const nodeId = typeof node?.id === "string" ? node.id : null;
640
- const nodeType = typeof nodeData?.nodeId === "string" ? nodeData.nodeId : node?.type;
641
- if (!nodeId || typeof nodeType !== "string") continue;
642
- const nodeVersion = typeof nodeData?.nodeVersion === "string" ? nodeData.nodeVersion : null;
643
- const key = `${nodeId}:${nodeType}:${nodeVersion ?? ""}`;
644
- if (seen.has(key)) continue;
645
- seen.add(key);
646
- references.push({ nodeId, nodeType, nodeVersion });
703
+ function describeImportSourceProblem(tr, problem) {
704
+ switch (problem.code) {
705
+ case "too-large":
706
+ return tr(
707
+ "fluidgrids.workflowsList.importSourceTooLarge",
708
+ "The workflow sources are larger than {{size}} MiB, the import limit.",
709
+ { size: formatMiB(problem.limitBytes) }
710
+ );
711
+ case "too-many-files":
712
+ return tr(
713
+ "fluidgrids.workflowsList.importSourceTooManyFiles",
714
+ "An import holds at most {{limit}} files.",
715
+ { limit: String(problem.limit) }
716
+ );
717
+ case "invalid-archive":
718
+ return tr(
719
+ "fluidgrids.workflowsList.importSourceInvalidArchive",
720
+ "The file is not a readable .zip archive."
721
+ );
722
+ case "no-module":
723
+ return tr(
724
+ "fluidgrids.workflowsList.importSourceNoModule",
725
+ "No TypeScript workflow module (workflows/*.ts) was found."
726
+ );
727
+ case "python-needs-cli":
728
+ return tr(
729
+ "fluidgrids.workflowsList.importSourcePythonNeedsCli",
730
+ "Python workflows are imported with the FluidGrids CLI (fluidgrids push)."
731
+ );
732
+ case "needs-node-clients":
733
+ return tr(
734
+ "fluidgrids.workflowsList.importSourceNeedsNodeClients",
735
+ "{{path}} imports the generated node clients (.fluidgrids/nodes.ts). Import the .zip that Export as TypeScript downloads, which includes them.",
736
+ { path: problem.path }
737
+ );
647
738
  }
648
- return references;
649
739
  }
650
- function createWorkflowExportPayload(workflow) {
651
- const definitionJson = asRecord(workflow.definitionJson);
652
- if (!definitionJson)
653
- throw new Error("Workflow definition JSON must be an object");
654
- return {
655
- format: "fluidgrids.workflow",
656
- schemaVersion: 1,
657
- exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
658
- workflowKey: workflow.workflowKey,
659
- version: workflow.version,
660
- name: workflow.name,
661
- description: workflow.description ?? null,
662
- definitionJson,
663
- context: asRecord(workflow.context),
664
- tags: workflow.tags,
665
- tagsV2: workflow.tagsV2,
666
- icon: workflow.icon ?? null,
667
- category: workflow.category ?? null,
668
- nodeReferences: collectWorkflowNodeReferences(definitionJson)
669
- };
670
- }
671
- function downloadWorkflowExport(workflow) {
672
- const payload = createWorkflowExportPayload(workflow);
673
- const blob = new Blob([JSON.stringify(payload, null, 2)], {
674
- type: "application/json"
675
- });
676
- const url = URL.createObjectURL(blob);
677
- const anchor = document.createElement("a");
678
- anchor.href = url;
679
- anchor.download = `${workflow.workflowKey}-${workflow.version}.fluidgrids.json`;
680
- anchor.click();
681
- URL.revokeObjectURL(url);
740
+ function WorkflowImportIssues({
741
+ issues
742
+ }) {
743
+ const tr = useTr();
744
+ const errors = issues.errors.length > 0 ? issues.errors : issues.message ? [issues.message] : [];
745
+ if (errors.length === 0 && issues.warnings.length === 0) return null;
746
+ return /* @__PURE__ */ jsxs(
747
+ "div",
748
+ {
749
+ role: "alert",
750
+ "data-testid": "fluidgrids-import-issues",
751
+ className: "space-y-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bg)] p-3 text-xs",
752
+ children: [
753
+ errors.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
754
+ /* @__PURE__ */ jsx("p", { className: "font-semibold text-[var(--color-status-error-text)]", children: tr(
755
+ "fluidgrids.workflowsList.importErrorsTitle",
756
+ "The workflow could not be imported"
757
+ ) }),
758
+ /* @__PURE__ */ jsx("ul", { className: "mt-1 list-disc space-y-0.5 pl-4 text-[var(--color-status-error-text)]", children: errors.map((error, index) => /* @__PURE__ */ jsx("li", { className: "break-words", children: error }, `e-${index}`)) })
759
+ ] }),
760
+ issues.warnings.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
761
+ /* @__PURE__ */ jsx("p", { className: "font-semibold text-[var(--color-status-warning-text)]", children: tr("fluidgrids.workflowsList.importWarningsTitle", "Warnings") }),
762
+ /* @__PURE__ */ jsx("ul", { className: "mt-1 list-disc space-y-0.5 pl-4 text-[var(--color-status-warning-text)]", children: issues.warnings.map((warning, index) => /* @__PURE__ */ jsx("li", { className: "break-words", children: warning }, `w-${index}`)) })
763
+ ] })
764
+ ]
765
+ }
766
+ );
682
767
  }
683
- function parseWorkflowImport(raw) {
684
- const parsed = JSON.parse(raw);
685
- const root = asRecord(parsed);
686
- if (!root) throw new Error("Import must contain a JSON object");
687
- const definitionJson = asRecord(root.definitionJson) ?? root;
688
- const name = typeof root.name === "string" && root.name.trim() ? root.name : "Imported workflow";
689
- return {
690
- definitionJson,
691
- name,
692
- workflowKey: typeof root.workflowKey === "string" ? root.workflowKey : void 0,
693
- version: typeof root.version === "string" ? root.version : void 0,
694
- description: typeof root.description === "string" ? root.description : void 0,
695
- context: asRecord(root.context) ?? void 0,
696
- tags: Array.isArray(root.tags) && root.tags.every((tag) => typeof tag === "string") ? root.tags : void 0,
697
- tagsV2: root.tagsV2,
698
- icon: typeof root.icon === "string" ? root.icon : void 0,
699
- category: typeof root.category === "string" ? root.category : void 0,
700
- nodeReferences: Array.isArray(root.nodeReferences) ? root.nodeReferences : collectWorkflowNodeReferences(definitionJson)
701
- };
768
+ function declaredLength(response) {
769
+ const value = Number(response.headers.get("content-length"));
770
+ return Number.isFinite(value) && value > 0 ? value : null;
702
771
  }
703
772
  function WorkflowImportDialog({
704
773
  onImported
705
774
  }) {
706
775
  const tr = useTr();
707
- const { importWorkflow, loading } = useImportWorkflow();
776
+ const codeFirst = useFluidGridsFlag(FLUIDGRIDS_FLAGS.codeFirst);
777
+ const { importWorkflow, loading, error: importError } = useImportWorkflow();
708
778
  const [open, setOpen] = useState(false);
779
+ const [typeScript, setTypeScript] = useState(null);
780
+ const [issues, setIssues] = useState(null);
781
+ const [submitted, setSubmitted] = useState(false);
709
782
  const [rawJson, setRawJson] = useState("");
710
783
  const [url, setUrl] = useState("");
711
784
  const [parsed, setParsed] = useState(null);
@@ -720,8 +793,53 @@ function WorkflowImportDialog({
720
793
  setName("");
721
794
  setWorkflowKey("");
722
795
  setVersion("");
796
+ setTypeScript(null);
797
+ setIssues(null);
798
+ setSubmitted(false);
799
+ };
800
+ const selectTypeScript = (label, files) => {
801
+ setParsed(null);
802
+ setIssues(null);
803
+ setSubmitted(false);
804
+ setTypeScript({ label, files });
805
+ setName(workflowNameFromFile(primaryImportModule(files)?.path ?? label));
806
+ setWorkflowKey("");
807
+ setVersion("");
808
+ };
809
+ const refuseSource = (error) => {
810
+ setTypeScript(null);
811
+ setParsed(null);
812
+ setSubmitted(false);
813
+ setIssues({
814
+ errors: [describeImportSourceProblem(tr, error.problem)],
815
+ warnings: []
816
+ });
817
+ };
818
+ const readCodeSource = async (kind, label, size, read) => {
819
+ if (!codeFirst || kind === null || kind === "json") return false;
820
+ try {
821
+ if (kind === "python") {
822
+ throw new ImportSourceError({ code: "python-needs-cli" });
823
+ }
824
+ if (kind === "archive") {
825
+ if (size !== null) assertImportByteSize(size, MAX_IMPORT_ARCHIVE_BYTES);
826
+ const files2 = await readProjectArchive(await read.bytes());
827
+ selectTypeScript(label, checkTypeScriptImport(files2));
828
+ return true;
829
+ }
830
+ if (size !== null) assertImportByteSize(size, MAX_IMPORT_SOURCE_BYTES);
831
+ const files = [typeScriptModuleFile(label, await read.text())];
832
+ selectTypeScript(label, checkTypeScriptImport(files));
833
+ } catch (error) {
834
+ if (!(error instanceof ImportSourceError)) throw error;
835
+ refuseSource(error);
836
+ }
837
+ return true;
723
838
  };
724
839
  const parse = (value) => {
840
+ setTypeScript(null);
841
+ setIssues(null);
842
+ setSubmitted(false);
725
843
  try {
726
844
  const next = parseWorkflowImport(value);
727
845
  setParsed(next);
@@ -741,10 +859,19 @@ function WorkflowImportDialog({
741
859
  const handleFile = async (file) => {
742
860
  if (!file) return;
743
861
  try {
744
- parse(await file.text());
862
+ const handled = await readCodeSource(
863
+ importFileKind(file.name),
864
+ file.name,
865
+ file.size,
866
+ { text: () => file.text(), bytes: () => file.arrayBuffer() }
867
+ );
868
+ if (!handled) parse(await file.text());
745
869
  } catch (error) {
746
870
  toast.error(
747
- error instanceof Error ? error.message : "Unable to read import file"
871
+ error instanceof Error ? error.message : tr(
872
+ "fluidgrids.workflowsList.importReadFileFailed",
873
+ "Unable to read import file"
874
+ )
748
875
  );
749
876
  }
750
877
  };
@@ -754,10 +881,18 @@ function WorkflowImportDialog({
754
881
  try {
755
882
  parsedUrl = new URL(url.trim());
756
883
  if (parsedUrl.protocol !== "https:")
757
- throw new Error("Import URLs must use HTTPS");
884
+ throw new Error(
885
+ tr(
886
+ "fluidgrids.workflowsList.importUrlNotHttps",
887
+ "Import URLs must use HTTPS"
888
+ )
889
+ );
758
890
  } catch (error) {
759
891
  toast.error(
760
- error instanceof Error ? error.message : "Invalid import URL"
892
+ error instanceof Error ? error.message : tr(
893
+ "fluidgrids.workflowsList.importUrlInvalid",
894
+ "Invalid import URL"
895
+ )
761
896
  );
762
897
  return;
763
898
  }
@@ -765,18 +900,72 @@ function WorkflowImportDialog({
765
900
  try {
766
901
  const response = await fetch(parsedUrl, { credentials: "omit" });
767
902
  if (!response.ok)
768
- throw new Error(`Import URL returned HTTP ${response.status}`);
769
- parse(await response.text());
903
+ throw new Error(
904
+ tr(
905
+ "fluidgrids.workflowsList.importUrlHttpError",
906
+ "Import URL returned HTTP {{status}}",
907
+ { status: String(response.status) }
908
+ )
909
+ );
910
+ const segments = parsedUrl.pathname.split("/").filter(Boolean);
911
+ const handled = await readCodeSource(
912
+ importFileKind(parsedUrl.pathname),
913
+ decodeURIComponent(segments[segments.length - 1] ?? "workflow.ts"),
914
+ declaredLength(response),
915
+ { text: () => response.text(), bytes: () => response.arrayBuffer() }
916
+ );
917
+ if (!handled) parse(await response.text());
770
918
  } catch (error) {
771
919
  toast.error(
772
- error instanceof Error ? error.message : "Unable to fetch import URL"
920
+ error instanceof Error ? error.message : tr(
921
+ "fluidgrids.workflowsList.importUrlFetchFailed",
922
+ "Unable to fetch import URL"
923
+ )
773
924
  );
774
925
  } finally {
775
926
  setLoadingUrl(false);
776
927
  }
777
928
  };
929
+ const handleTypeScriptImport = async (source) => {
930
+ setIssues(null);
931
+ setSubmitted(true);
932
+ const input = {
933
+ workflowKey: workflowKey.trim() || void 0,
934
+ version: version.trim() || void 0,
935
+ name: name.trim(),
936
+ source: typeScriptImportSource(source.files),
937
+ allowMissingNodes: false
938
+ };
939
+ const imported = await importWorkflow(input);
940
+ if (!imported) return;
941
+ if (!imported.success || !imported.workflow) {
942
+ setIssues({
943
+ errors: imported.validation.errors,
944
+ warnings: imported.validation.warnings,
945
+ message: imported.message || tr(
946
+ "fluidgrids.workflowsList.importValidationFailed",
947
+ "Workflow import validation failed"
948
+ )
949
+ });
950
+ return;
951
+ }
952
+ toast.success(
953
+ tr("fluidgrids.workflowsList.importSuccess", "Workflow imported")
954
+ );
955
+ setOpen(false);
956
+ reset();
957
+ onImported();
958
+ };
778
959
  const handleImport = async () => {
960
+ if (typeScript && name.trim()) {
961
+ await handleTypeScriptImport(typeScript);
962
+ return;
963
+ }
779
964
  if (!parsed || !name.trim()) return;
965
+ if (codeFirst) {
966
+ setIssues(null);
967
+ setSubmitted(true);
968
+ }
780
969
  const input = {
781
970
  workflowKey: workflowKey.trim() || `${name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${Date.now()}`,
782
971
  version: version.trim() || "v1",
@@ -793,6 +982,17 @@ function WorkflowImportDialog({
793
982
  const imported = await importWorkflow(input);
794
983
  if (!imported) return;
795
984
  if (!imported.success || !imported.workflow) {
985
+ if (codeFirst) {
986
+ setIssues({
987
+ errors: imported.validation.errors,
988
+ warnings: imported.validation.warnings,
989
+ message: imported.message || tr(
990
+ "fluidgrids.workflowsList.importValidationFailed",
991
+ "Workflow import validation failed"
992
+ )
993
+ });
994
+ return;
995
+ }
796
996
  const validationMessage = [
797
997
  ...imported.validation.errors,
798
998
  ...imported.validation.warnings
@@ -836,8 +1036,17 @@ function WorkflowImportDialog({
836
1036
  ),
837
1037
  /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-[90vh] overflow-y-auto sm:max-w-2xl", children: [
838
1038
  /* @__PURE__ */ jsxs(DialogHeader, { children: [
839
- /* @__PURE__ */ jsx(DialogTitle, { children: tr("fluidgrids.workflowsList.importTitle", "Import workflow JSON") }),
840
- /* @__PURE__ */ jsx(DialogDescription, { children: tr(
1039
+ /* @__PURE__ */ jsx(DialogTitle, { children: codeFirst ? tr(
1040
+ "fluidgrids.workflowsList.importTitleCode",
1041
+ "Import workflow"
1042
+ ) : tr(
1043
+ "fluidgrids.workflowsList.importTitle",
1044
+ "Import workflow JSON"
1045
+ ) }),
1046
+ /* @__PURE__ */ jsx(DialogDescription, { children: codeFirst ? tr(
1047
+ "fluidgrids.workflowsList.importDescriptionCode",
1048
+ "Import a workflow JSON file, a TypeScript workflow module, or the .zip that Export as TypeScript downloads, from a file, an HTTPS URL, or pasted JSON. Credentials remain references and must be configured in this workspace."
1049
+ ) : tr(
841
1050
  "fluidgrids.workflowsList.importDescription",
842
1051
  "Import from a file, an HTTPS URL, or pasted JSON. Credentials remain references and must be configured in this workspace."
843
1052
  ) })
@@ -851,7 +1060,10 @@ function WorkflowImportDialog({
851
1060
  className: "inline-flex cursor-pointer items-center gap-2 rounded-md border border-border px-3 py-2 text-sm hover:bg-accent",
852
1061
  children: [
853
1062
  /* @__PURE__ */ jsx(Icon, { icon: "mynaui:upload", className: "size-4" }),
854
- tr(
1063
+ codeFirst ? tr(
1064
+ "fluidgrids.workflowsList.importChooseFileCode",
1065
+ "Choose JSON, TypeScript or .zip file"
1066
+ ) : tr(
855
1067
  "fluidgrids.workflowsList.importChooseFile",
856
1068
  "Choose JSON file"
857
1069
  )
@@ -863,7 +1075,7 @@ function WorkflowImportDialog({
863
1075
  {
864
1076
  id: "fluidgrids-workflow-import-file",
865
1077
  type: "file",
866
- accept: "application/json,.json",
1078
+ accept: importAcceptAttribute(codeFirst),
867
1079
  className: "sr-only",
868
1080
  onChange: (event) => void handleFile(event.currentTarget.files?.[0])
869
1081
  }
@@ -876,7 +1088,10 @@ function WorkflowImportDialog({
876
1088
  value: url,
877
1089
  onChange: (event) => setUrl(event.target.value),
878
1090
  placeholder: "https://example.com/workflow.json",
879
- "aria-label": tr(
1091
+ "aria-label": codeFirst ? tr(
1092
+ "fluidgrids.workflowsList.importUrlLabelCode",
1093
+ "Workflow JSON, TypeScript or .zip URL"
1094
+ ) : tr(
880
1095
  "fluidgrids.workflowsList.importUrlLabel",
881
1096
  "Workflow JSON URL"
882
1097
  )
@@ -888,7 +1103,7 @@ function WorkflowImportDialog({
888
1103
  variant: "outline",
889
1104
  onClick: () => void handleUrl(),
890
1105
  disabled: loadingUrl || !url.trim(),
891
- children: loadingUrl ? "Loading\u2026" : tr("fluidgrids.workflowsList.importFetchUrl", "Fetch")
1106
+ children: loadingUrl ? tr("fluidgrids.workflowsList.importFetchingUrl", "Loading\u2026") : tr("fluidgrids.workflowsList.importFetchUrl", "Fetch")
892
1107
  }
893
1108
  )
894
1109
  ] }),
@@ -912,15 +1127,33 @@ function WorkflowImportDialog({
912
1127
  )
913
1128
  }
914
1129
  ),
915
- parsed && /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border border-border bg-muted/30 p-3", children: [
916
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1130
+ (parsed || typeScript) && /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-lg border border-border bg-muted/30 p-3", children: [
1131
+ parsed ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
917
1132
  parsed.nodeReferences.length,
918
1133
  " ",
919
1134
  tr(
920
1135
  "fluidgrids.workflowsList.importNodeReferences",
921
1136
  "node references found"
922
1137
  )
923
- ] }),
1138
+ ] }) : typeScript ? /* @__PURE__ */ jsx(
1139
+ "p",
1140
+ {
1141
+ className: "text-xs text-muted-foreground",
1142
+ "data-testid": "fluidgrids-import-typescript",
1143
+ children: typeScript.files.length > 1 ? tr(
1144
+ "fluidgrids.workflowsList.importTypeScriptProject",
1145
+ "{{file}}: {{count}} project files. The service builds the TypeScript and checks the workflow it defines before creating a draft.",
1146
+ {
1147
+ file: typeScript.label,
1148
+ count: String(typeScript.files.length)
1149
+ }
1150
+ ) : tr(
1151
+ "fluidgrids.workflowsList.importTypeScriptFile",
1152
+ "TypeScript module {{file}}: the service builds it and checks the workflow it defines before creating a draft.",
1153
+ { file: typeScript.label }
1154
+ )
1155
+ }
1156
+ ) : null,
924
1157
  /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [
925
1158
  /* @__PURE__ */ jsxs("div", { className: "space-y-1 sm:col-span-3", children: [
926
1159
  /* @__PURE__ */ jsx(Label, { htmlFor: "fluidgrids-import-name", children: tr("fluidgrids.workflowsList.importName", "Name") }),
@@ -960,7 +1193,17 @@ function WorkflowImportDialog({
960
1193
  )
961
1194
  ] })
962
1195
  ] })
963
- ] })
1196
+ ] }),
1197
+ codeFirst && !loading && (issues || submitted && importError) && /* @__PURE__ */ jsx(
1198
+ WorkflowImportIssues,
1199
+ {
1200
+ issues: issues ?? {
1201
+ errors: [],
1202
+ warnings: [],
1203
+ message: importError?.message ?? null
1204
+ }
1205
+ }
1206
+ )
964
1207
  ] }),
965
1208
  /* @__PURE__ */ jsxs(DialogFooter, { children: [
966
1209
  /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setOpen(false), children: tr("fluidgrids.workflowsList.importCancel", "Cancel") }),
@@ -968,7 +1211,7 @@ function WorkflowImportDialog({
968
1211
  Button,
969
1212
  {
970
1213
  onClick: () => void handleImport(),
971
- disabled: loading || !parsed || !name.trim(),
1214
+ disabled: loading || !parsed && !typeScript || !name.trim(),
972
1215
  children: loading ? tr("fluidgrids.workflowsList.importing", "Importing\u2026") : tr("fluidgrids.workflowsList.importConfirm", "Import workflow")
973
1216
  }
974
1217
  )
@@ -1267,6 +1510,22 @@ function WorkflowsList() {
1267
1510
  ]
1268
1511
  );
1269
1512
  useFluidGridsPageControls(assistantControls);
1513
+ const codeFirst = useFluidGridsFlag(FLUIDGRIDS_FLAGS.codeFirst);
1514
+ const { exportAs, exporting } = useExportWorkflowAs();
1515
+ const [exportingKey, setExportingKey] = useState(null);
1516
+ const handleExportAs = async (workflowKey, version, format) => {
1517
+ setExportingKey(workflowKey);
1518
+ try {
1519
+ await exportAs({ workflowKey, version, format });
1520
+ } catch (error2) {
1521
+ toast.error(
1522
+ tr("fluidgrids.workflowsList.exportFailed", "Export failed"),
1523
+ { description: describeOperationError(error2) }
1524
+ );
1525
+ } finally {
1526
+ setExportingKey(null);
1527
+ }
1528
+ };
1270
1529
  const handleExport = async (id) => {
1271
1530
  const workflow = await getWorkflow(void 0, id);
1272
1531
  if (!workflow) return;
@@ -1507,7 +1766,13 @@ function WorkflowsList() {
1507
1766
  }))
1508
1767
  },
1509
1768
  onUpdateSuccess: () => refetch(),
1510
- onExport: () => void handleExport(workflow.id)
1769
+ onExport: () => void handleExport(workflow.id),
1770
+ onExportAs: codeFirst ? (format) => void handleExportAs(
1771
+ workflow.workflowKey,
1772
+ workflow.version,
1773
+ format
1774
+ ) : void 0,
1775
+ exportingFormat: exportingKey === workflow.workflowKey ? exporting : null
1511
1776
  },
1512
1777
  workflow.id
1513
1778
  )) })