@revenexx/studio-integrations 2.8.1 → 2.9.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/CHANGELOG.md +22 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/ExpressionInput.vue +31 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +5 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +72 -64
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +11 -5
- package/dist/runtime/components/Integrations/TemplateInstallStep.vue +2 -2
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +4 -2
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +147 -27
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +36 -12
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +162 -13
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +34 -12
- package/dist/runtime/components/Integrations/WorkflowTriggerView.d.vue.ts +8 -1
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +3 -1
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue.d.ts +8 -1
- package/dist/runtime/components/Integrations/canvasSides.d.ts +27 -0
- package/dist/runtime/components/Integrations/canvasSides.js +22 -0
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +4 -1
- package/dist/runtime/composables/useIntegrationsCredentials.js +8 -3
- package/dist/runtime/data/canvasShapes.d.ts +139 -0
- package/dist/runtime/data/canvasShapes.js +130 -0
- package/dist/runtime/data/connectionRules.d.ts +110 -0
- package/dist/runtime/data/connectionRules.js +110 -0
- package/dist/runtime/data/graphShapes.d.ts +175 -0
- package/dist/runtime/data/graphShapes.js +359 -0
- package/dist/runtime/data/handleId.d.ts +12 -0
- package/dist/runtime/data/handleId.js +3 -0
- package/dist/runtime/data/refusalMessage.d.ts +32 -0
- package/dist/runtime/data/refusalMessage.js +42 -0
- package/dist/runtime/data/saveRefusal.d.ts +31 -0
- package/dist/runtime/data/saveRefusal.js +25 -0
- package/dist/runtime/i18n/messages/de.json +36 -1
- package/dist/runtime/i18n/messages/en.json +36 -1
- package/dist/runtime/pages/integrations/schedules.vue +1 -1
- package/dist/runtime/pages/integrations/workflows/[id].vue +38 -3
- package/dist/runtime/types/integrations.d.ts +13 -4
- package/dist/runtime/utils/skippedStep.d.ts +39 -0
- package/dist/runtime/utils/skippedStep.js +22 -0
- package/package.json +1 -1
|
@@ -77,7 +77,13 @@
|
|
|
77
77
|
"integration.canvas.node.badge.loopBody": "Loop — nothing is wired to Item yet, so nothing repeats | Loop — repeats the one step wired to Item, once per element | Loop — repeats the {n} steps wired to Item, once per element",
|
|
78
78
|
"integration.canvas.node.badge.noBuiltCode": "Version {version} is not ready to use — this step cannot be tested",
|
|
79
79
|
"integration.canvas.node.badge.repeats": "Repeats — runs once per element of {iterators} | Repeats — runs once per element of each of the {n} loops it sits inside: {iterators}",
|
|
80
|
+
"integration.canvas.node.handle.converging": "Converging input: the branch that arrives is handed on as the input",
|
|
81
|
+
"integration.canvas.node.handle.convergingKeyed": "Branches of one split arrive together, each under the step it ends in",
|
|
80
82
|
"integration.canvas.node.handle.input": "Input",
|
|
83
|
+
"integration.canvas.node.handle.merger": "Merger: waits for every branch and hands each one on under the step it ends in",
|
|
84
|
+
"integration.canvas.node.handle.splitter": "Splitter: all branches run",
|
|
85
|
+
"integration.canvas.node.handle.virtualConverging": "Converging input, drawn only: whichever of these branches arrives",
|
|
86
|
+
"integration.canvas.node.handle.virtualMerger": "Merger, drawn only: waits for every branch",
|
|
81
87
|
"integration.canvas.node.meta.versionTitle": "Version {version}",
|
|
82
88
|
"integration.canvas.node.note.heading": "Note",
|
|
83
89
|
"integration.canvas.node.port.outputFallback": "out",
|
|
@@ -385,6 +391,14 @@
|
|
|
385
391
|
"integration.editor.clipboard.error.read": "Could not read the clipboard",
|
|
386
392
|
"integration.editor.clipboard.error.write": "Could not write to the clipboard",
|
|
387
393
|
"integration.editor.clipboard.toast.copied": "Node copied | {n} nodes copied",
|
|
394
|
+
"integration.editor.connection.refused.copied": "{n} connection between the copies was left out because it would cross a loop's boundary. | {n} connections between the copies were left out because they would cross a loop's boundary.",
|
|
395
|
+
"integration.editor.connection.refused.copiedTitle": "Copied without some connections",
|
|
396
|
+
"integration.editor.connection.refused.cycle": "{source} already runs after {target}, so this connection would close a cycle.",
|
|
397
|
+
"integration.editor.connection.refused.cycleUnnamed": "This would close a cycle: a step would run after itself.",
|
|
398
|
+
"integration.editor.connection.refused.loopBoundary": "{inside} is inside the loop {loop}; only its Item output may lead into the loop's steps.",
|
|
399
|
+
"integration.editor.connection.refused.loopBoundaryItem": "{outside} is reached from outside the loop {loop} too, so the loop's Item output cannot lead to it.",
|
|
400
|
+
"integration.editor.connection.refused.loopBoundaryOut": "{inside} is inside the loop {loop}, and {outside} is reached from outside it too; only its Done output may lead out of the loop.",
|
|
401
|
+
"integration.editor.connection.refused.title": "Connection not possible",
|
|
388
402
|
"integration.editor.deactivate.dialog.cancel": "Cancel",
|
|
389
403
|
"integration.editor.deactivate.dialog.confirm": "Save as paused",
|
|
390
404
|
"integration.editor.deactivate.dialog.description.paused": "paused",
|
|
@@ -581,14 +595,23 @@
|
|
|
581
595
|
"integration.nodeConfig.expression.picker.filterPlaceholder": "Filter…",
|
|
582
596
|
"integration.nodeConfig.expression.pill.inputPath": "Input · {rest}",
|
|
583
597
|
"integration.nodeConfig.expression.pill.inputPayload": "Input payload",
|
|
598
|
+
"integration.nodeConfig.expression.pill.runId": "Run ID",
|
|
599
|
+
"integration.nodeConfig.expression.pill.runUrl": "Run link",
|
|
584
600
|
"integration.nodeConfig.expression.pill.triggerPath": "Trigger · {rest}",
|
|
585
601
|
"integration.nodeConfig.expression.pill.triggerPayload": "Trigger payload",
|
|
586
602
|
"integration.nodeConfig.expression.pill.variable": "Variable · {path}",
|
|
603
|
+
"integration.nodeConfig.expression.pill.workflowId": "Workflow ID",
|
|
604
|
+
"integration.nodeConfig.expression.pill.workflowName": "Workflow name",
|
|
587
605
|
"integration.nodeConfig.expression.preview.missTitle": "Parts of this expression have no sample data yet",
|
|
588
606
|
"integration.nodeConfig.expression.suggestion.heading.input": "INPUT",
|
|
589
607
|
"integration.nodeConfig.expression.suggestion.heading.nodeOutputs": "NODE OUTPUTS",
|
|
608
|
+
"integration.nodeConfig.expression.suggestion.heading.run": "RUN",
|
|
590
609
|
"integration.nodeConfig.expression.suggestion.heading.variables": "VARIABLES",
|
|
591
610
|
"integration.nodeConfig.expression.suggestion.inputPayload": "input payload",
|
|
611
|
+
"integration.nodeConfig.expression.suggestion.runId": "run ID",
|
|
612
|
+
"integration.nodeConfig.expression.suggestion.runUrl": "run link",
|
|
613
|
+
"integration.nodeConfig.expression.suggestion.workflowId": "workflow ID",
|
|
614
|
+
"integration.nodeConfig.expression.suggestion.workflowName": "workflow name",
|
|
592
615
|
"integration.nodeConfig.expression.toggle.off": "Back to the regular control (clears the expression)",
|
|
593
616
|
"integration.nodeConfig.expression.toggle.on": "Map this value from a previous step instead",
|
|
594
617
|
"integration.nodeConfig.expression.useFit": "Use:",
|
|
@@ -990,10 +1013,14 @@
|
|
|
990
1013
|
"integration.runs.response.title.starting": "Starting run",
|
|
991
1014
|
"integration.runs.response.title.sync": "Run response",
|
|
992
1015
|
"integration.runs.response.title.unconfirmed": "Start not confirmed",
|
|
1016
|
+
"integration.runs.skip.deadEnd": "Skipped: dead end",
|
|
1017
|
+
"integration.runs.skip.deadEndBranch": "Skipped: dead end in branch {branch}",
|
|
1018
|
+
"integration.runs.skip.plain": "Skipped",
|
|
993
1019
|
"integration.runs.status.cancelled": "cancelled",
|
|
994
1020
|
"integration.runs.status.failed": "failed",
|
|
995
1021
|
"integration.runs.status.queued": "queued",
|
|
996
1022
|
"integration.runs.status.running": "running",
|
|
1023
|
+
"integration.runs.status.skipped": "skipped",
|
|
997
1024
|
"integration.runs.status.succeeded": "succeeded",
|
|
998
1025
|
"integration.runs.table.column.duration": "Duration",
|
|
999
1026
|
"integration.runs.table.column.id": "ID",
|
|
@@ -1393,5 +1420,13 @@
|
|
|
1393
1420
|
"integration.nodeVersions.badge.offered": "Offered",
|
|
1394
1421
|
"integration.nodeVersions.badge.notBuilt": "Not built",
|
|
1395
1422
|
"integration.nodeVersions.action.retire": "Retire",
|
|
1396
|
-
"integration.nodeVersions.action.restore": "Restore"
|
|
1423
|
+
"integration.nodeVersions.action.restore": "Restore",
|
|
1424
|
+
"integration.editor.connection.joinRule.title": "This will not save",
|
|
1425
|
+
"integration.editor.connection.joinRule.crossing": "The connection from node “{from}” (output {output}) to node “{to}” joins two paths that must run separately. Delete this connection or lead it to a node on the same path.",
|
|
1426
|
+
"integration.editor.connection.joinRule.mergerAcrossSplitters": "Paths that split at different places come together in node “{step}”. First bring the paths of each split together in a node of their own.",
|
|
1427
|
+
"integration.editor.connection.joinRule.splitterBypassed": "Paths that split earlier lead to node “{step}” – and one connection also goes around that split. Delete this extra connection.",
|
|
1428
|
+
"integration.editor.connection.joinRule.undecomposable": "Paths that always run and paths that run only under a certain condition come together in node “{step}”. Bring these paths together in separate nodes.",
|
|
1429
|
+
"integration.editor.unsavable.dialog.back": "Back to the editor",
|
|
1430
|
+
"integration.editor.unsavable.dialog.description": "This workflow is currently invalid. It can’t be saved like this. Please fix these errors first:",
|
|
1431
|
+
"integration.editor.unsavable.dialog.title": "Can’t save this workflow"
|
|
1397
1432
|
}
|
|
@@ -140,7 +140,7 @@ const columns = computed(() => [
|
|
|
140
140
|
<Clock :size="14" />
|
|
141
141
|
</div>
|
|
142
142
|
<div class="min-w-0">
|
|
143
|
-
<div class="font-medium text-sm truncate">{{ e.workflowName }}</div>
|
|
143
|
+
<div class="font-medium text-sm truncate" :title="e.workflowName">{{ e.workflowName }}</div>
|
|
144
144
|
</div>
|
|
145
145
|
</div>
|
|
146
146
|
</template>
|
|
@@ -45,6 +45,7 @@ import { isTerminalRun, unwrapRun } from "../../../utils/runEnvelope";
|
|
|
45
45
|
import { findingsSummary, groupFindingsByNode, nodeDisplayLabel } from "../../../utils/validationFindings";
|
|
46
46
|
import { payloadFieldsFromBlob } from "../../../data/payloadForm";
|
|
47
47
|
import { eventTopicOf } from "../../../data/eventTrigger";
|
|
48
|
+
import { isShapeIssue, saveRefusalLines } from "../../../data/saveRefusal";
|
|
48
49
|
import WorkflowIconView from "../../../components/Integrations/WorkflowIconView.vue";
|
|
49
50
|
import { DEFAULT_RUN_DIRECTION, DEFAULT_RUN_SORT } from "../../../utils/runFeedQuery";
|
|
50
51
|
import { keepOpenBeside } from "../../../utils/dialog";
|
|
@@ -114,6 +115,12 @@ onMounted(() => {
|
|
|
114
115
|
});
|
|
115
116
|
const deactivationOpen = ref(false);
|
|
116
117
|
const deactivationIssues = ref([]);
|
|
118
|
+
const unsavableOpen = ref(false);
|
|
119
|
+
const unsavableLines = ref([]);
|
|
120
|
+
function showUnsavable(issues, blob) {
|
|
121
|
+
unsavableLines.value = saveRefusalLines(issues, blob.nodes, blob.edges, blob.triggers);
|
|
122
|
+
unsavableOpen.value = true;
|
|
123
|
+
}
|
|
117
124
|
const { load: loadCredentials, needsReauth } = useIntegrationsCredentials();
|
|
118
125
|
onMounted(() => {
|
|
119
126
|
void loadCredentials();
|
|
@@ -795,7 +802,9 @@ async function save() {
|
|
|
795
802
|
settleBuildStatus();
|
|
796
803
|
} catch (e) {
|
|
797
804
|
const issues = apiValidationIssues(e);
|
|
798
|
-
if (issues.
|
|
805
|
+
if (issues.some((i) => isShapeIssue(i.path))) {
|
|
806
|
+
showUnsavable(issues, payloadBlob(d));
|
|
807
|
+
} else if (issues.length && workflow.value.active) {
|
|
799
808
|
const nodes = workflow.value.blob.nodes;
|
|
800
809
|
deactivationIssues.value = issues.map((i) => {
|
|
801
810
|
const m = /^blob\.nodes\.(\d+)\b/.exec(i.path);
|
|
@@ -832,7 +841,9 @@ async function confirmDeactivate() {
|
|
|
832
841
|
settleBuildStatus();
|
|
833
842
|
} catch (e) {
|
|
834
843
|
deactivationOpen.value = false;
|
|
835
|
-
|
|
844
|
+
const issues = apiValidationIssues(e);
|
|
845
|
+
if (issues.length) showUnsavable(issues, payloadBlob(d));
|
|
846
|
+
else saveError.value = errorReason(e, t("integration.editor.save.error.generic"));
|
|
836
847
|
} finally {
|
|
837
848
|
saving.value = false;
|
|
838
849
|
}
|
|
@@ -1139,7 +1150,7 @@ async function confirmDeactivate() {
|
|
|
1139
1150
|
:draft="draft?.blob ?? null"
|
|
1140
1151
|
:run-busy="saving || running || syncLoading || waitingForBuild"
|
|
1141
1152
|
:run-saves="isDirty"
|
|
1142
|
-
:overlay-open="settingsOpen || duplicateOpen || importOpen || !!exporter?.open || deleteOpen || syncOpen || runPayloadOpen || deactivationOpen || leaveOpen"
|
|
1153
|
+
:overlay-open="settingsOpen || duplicateOpen || importOpen || !!exporter?.open || deleteOpen || syncOpen || runPayloadOpen || deactivationOpen || unsavableOpen || leaveOpen"
|
|
1143
1154
|
@change="onChange"
|
|
1144
1155
|
@run="runFromEditor"
|
|
1145
1156
|
@toggle-active="onToggleActive"
|
|
@@ -1433,5 +1444,29 @@ async function confirmDeactivate() {
|
|
|
1433
1444
|
</AlertDialogFooter>
|
|
1434
1445
|
</AlertDialogContent>
|
|
1435
1446
|
</AlertDialog>
|
|
1447
|
+
|
|
1448
|
+
<!-- A save refused whatever `active` says: nothing to offer but going back. -->
|
|
1449
|
+
<AlertDialog :open="unsavableOpen" @update:open="(v) => {
|
|
1450
|
+
if (!v) unsavableOpen = false;
|
|
1451
|
+
}">
|
|
1452
|
+
<AlertDialogContent>
|
|
1453
|
+
<AlertDialogHeader>
|
|
1454
|
+
<AlertDialogTitle>{{ t("integration.editor.unsavable.dialog.title") }}</AlertDialogTitle>
|
|
1455
|
+
<AlertDialogDescription>{{ t("integration.editor.unsavable.dialog.description") }}</AlertDialogDescription>
|
|
1456
|
+
</AlertDialogHeader>
|
|
1457
|
+
<ul class="max-h-48 space-y-0.5 overflow-y-auto rounded-md border border-red-500/20 bg-red-500/5 px-3 py-2 text-sm text-red-800">
|
|
1458
|
+
<li v-for="(line, i) in unsavableLines" :key="i" class="flex gap-1.5">
|
|
1459
|
+
<span class="select-none text-red-500">•</span>
|
|
1460
|
+
<span v-if="'key' in line">{{ t(line.key, line.params) }}</span>
|
|
1461
|
+
<span v-else><span v-if="line.where" class="font-medium">{{ line.where }}:</span> {{ line.message }}</span>
|
|
1462
|
+
</li>
|
|
1463
|
+
</ul>
|
|
1464
|
+
<AlertDialogFooter>
|
|
1465
|
+
<Button variant="outline" @click="unsavableOpen = false">
|
|
1466
|
+
{{ t("integration.editor.unsavable.dialog.back") }}
|
|
1467
|
+
</Button>
|
|
1468
|
+
</AlertDialogFooter>
|
|
1469
|
+
</AlertDialogContent>
|
|
1470
|
+
</AlertDialog>
|
|
1436
1471
|
</IntegrationsLayout>
|
|
1437
1472
|
</template>
|
|
@@ -696,11 +696,20 @@ export interface WorkflowRunNodeStep {
|
|
|
696
696
|
export interface RunStepRow {
|
|
697
697
|
node_id: string;
|
|
698
698
|
node_slug: string;
|
|
699
|
-
activity_type
|
|
699
|
+
activity_type?: string | null;
|
|
700
700
|
status: string;
|
|
701
|
-
attempt
|
|
702
|
-
scheduled_event_id
|
|
703
|
-
duration_ms
|
|
701
|
+
attempt?: number | null;
|
|
702
|
+
scheduled_event_id?: number | null;
|
|
703
|
+
duration_ms?: number | null;
|
|
704
|
+
/** Why a `skipped` step was skipped. Open-ended: unknown values are legal. */
|
|
705
|
+
skip_reason?: string | null;
|
|
706
|
+
/** `dead_end` only: the 1-based number of the splitter's branch that delivered nothing, in connection order. */
|
|
707
|
+
dead_end_branch?: number | null;
|
|
708
|
+
/** `dead_end` only: the splitter (node and output port) whose branch was a dead end. */
|
|
709
|
+
dead_end_splitter?: {
|
|
710
|
+
node_id: string;
|
|
711
|
+
port: string;
|
|
712
|
+
} | null;
|
|
704
713
|
branch?: string | null;
|
|
705
714
|
error?: string | null;
|
|
706
715
|
input?: unknown;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { RunStepRow } from '../types/integrations.js';
|
|
2
|
+
/**
|
|
3
|
+
* Why a run step was skipped (PO-750, from the worker's PO-502).
|
|
4
|
+
*
|
|
5
|
+
* A step the run never reached because a branch delivered nothing is reported as
|
|
6
|
+
* a synthetic row: `status: 'skipped'`, `skip_reason: 'dead_end'`, and the number
|
|
7
|
+
* of the splitter's branch that was the dead end. The reason list is open — the
|
|
8
|
+
* worker may name more later — so anything but `dead_end` reads as a plain skip
|
|
9
|
+
* rather than being shown raw or hidden.
|
|
10
|
+
*/
|
|
11
|
+
export type SkipReason = {
|
|
12
|
+
kind: 'dead_end';
|
|
13
|
+
branch: number | null;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'plain';
|
|
16
|
+
};
|
|
17
|
+
type StepLike = Pick<RunStepRow, 'status' | 'skip_reason' | 'dead_end_branch'>;
|
|
18
|
+
/** A step that never ran. Case-insensitive, like the rest of the status vocabulary. */
|
|
19
|
+
export declare function isSkippedStep(step: Pick<RunStepRow, 'status'>): boolean;
|
|
20
|
+
/** The reason a step was skipped, or null for a step that ran. */
|
|
21
|
+
export declare function skipReason(step: StepLike): SkipReason | null;
|
|
22
|
+
/**
|
|
23
|
+
* The message key and parameters that say why a step was skipped, or null for a
|
|
24
|
+
* step that ran or was skipped for a reason this studio does not name (the status
|
|
25
|
+
* badge already says "skipped" then).
|
|
26
|
+
*/
|
|
27
|
+
export declare function skipReasonMessage(step: StepLike): {
|
|
28
|
+
key: string;
|
|
29
|
+
params: Record<string, number>;
|
|
30
|
+
} | null;
|
|
31
|
+
/** Nodes a run actually executed — a skipped step is on the list but did not run. */
|
|
32
|
+
export declare function executedNodeIds(steps: ReadonlyArray<Pick<RunStepRow, 'node_id' | 'status'>>): Set<string>;
|
|
33
|
+
/**
|
|
34
|
+
* A step's status as its badge says it. A skipped step is a step's state, not a
|
|
35
|
+
* run's, so it is not among the run words `runStatusLabel` translates — and without
|
|
36
|
+
* this the badge printed the raw `SKIPPED` in a German studio (review of #297).
|
|
37
|
+
*/
|
|
38
|
+
export declare function stepStatusLabel(status: string | null | undefined): string;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useIntegrationsT } from "../composables/useIntegrationsT.js";
|
|
2
|
+
import { runStatusLabel } from "./statusStyle.js";
|
|
3
|
+
export function isSkippedStep(step) {
|
|
4
|
+
return String(step.status ?? "").toLowerCase() === "skipped";
|
|
5
|
+
}
|
|
6
|
+
export function skipReason(step) {
|
|
7
|
+
if (!isSkippedStep(step)) return null;
|
|
8
|
+
if (step.skip_reason !== "dead_end") return { kind: "plain" };
|
|
9
|
+
const n = step.dead_end_branch;
|
|
10
|
+
return { kind: "dead_end", branch: typeof n === "number" && Number.isInteger(n) && n >= 1 ? n : null };
|
|
11
|
+
}
|
|
12
|
+
export function skipReasonMessage(step) {
|
|
13
|
+
const reason = skipReason(step);
|
|
14
|
+
if (reason?.kind !== "dead_end") return null;
|
|
15
|
+
return reason.branch === null ? { key: "integration.runs.skip.deadEnd", params: {} } : { key: "integration.runs.skip.deadEndBranch", params: { branch: reason.branch } };
|
|
16
|
+
}
|
|
17
|
+
export function executedNodeIds(steps) {
|
|
18
|
+
return new Set(steps.filter((s) => !isSkippedStep(s)).map((s) => s.node_id));
|
|
19
|
+
}
|
|
20
|
+
export function stepStatusLabel(status) {
|
|
21
|
+
return isSkippedStep({ status: status ?? "" }) ? useIntegrationsT()("integration.runs.status.skipped") : runStatusLabel(status);
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenexx/studio-integrations",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Integration Studio (core: workflows, node editor, runs, schedules, credentials, secrets, audit) as a standalone, publishable Nuxt module.",
|
|
6
6
|
"repository": {
|