Переглянути джерело

feat: 整理编辑器功能

jiaxing.liao 1 тиждень тому
батько
коміт
e5136dde57

+ 5 - 0
apps/web/components.d.ts

@@ -16,6 +16,8 @@ declare module 'vue' {
     CustomDropdown: typeof import('./src/components/CustomDropdown/index.vue')['default']
     DatabaseSetter: typeof import('./src/components/setter/DatabaseSetter.vue')['default']
     ElAside: typeof import('element-plus/es')['ElAside']
+    ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
+    ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCard: typeof import('element-plus/es')['ElCard']
     ElCol: typeof import('element-plus/es')['ElCol']
@@ -25,6 +27,7 @@ declare module 'vue' {
     ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
     ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
     ElEmpty: typeof import('element-plus/es')['ElEmpty']
+    ElFooter: typeof import('element-plus/es')['ElFooter']
     ElForm: typeof import('element-plus/es')['ElForm']
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElHeader: typeof import('element-plus/es')['ElHeader']
@@ -39,6 +42,8 @@ declare module 'vue' {
     ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElSplitter: typeof import('element-plus/es')['ElSplitter']
+    ElSplitterPanel: typeof import('element-plus/es')['ElSplitterPanel']
     ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
     ElTable: typeof import('element-plus/es')['ElTable']
     ElTableColumn: typeof import('element-plus/es')['ElTableColumn']

+ 3 - 1
apps/web/package.json

@@ -9,15 +9,17 @@
     "preview": "vite preview"
   },
   "dependencies": {
-    "@repo/nodes": "workspace:^",
     "@element-plus/icons-vue": "^2.3.2",
+    "@repo/nodes": "workspace:^",
     "element-plus": "^2.13.1",
     "normalize.css": "^8.0.1",
+    "uuid": "^13.0.0",
     "vue": "^3.5.24",
     "vue-router": "4"
   },
   "devDependencies": {
     "@repo/nodes": "workspace:*",
+    "@repo/ui": "workspace:*",
     "@repo/workflow": "workspace:*",
     "@vitejs/plugin-vue": "^6.0.1",
     "@vue/tsconfig": "^0.8.1",

+ 30 - 0
apps/web/src/features/editorFooter/index.vue

@@ -0,0 +1,30 @@
+<template>
+	<div class="flex w-full h-full overflow-hidden">
+		<div class="flex-1 flex flex-col">
+			<div
+				class="h-32px shrink-0 px-12px flex items-center justify-between border border-solid border-gray-200"
+				@click="onClick"
+			>
+				<span class="text-12px">日志</span>
+				<IconButton :icon="open ? 'lucide:chevron-down' : 'lucide:chevron-up'" link></IconButton>
+			</div>
+			<div class="flex-1 text-12px p-12px">日志内容...</div>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { IconButton } from '@repo/ui'
+
+const emit = defineEmits<{
+	toggle: [open: boolean]
+}>()
+
+const open = ref(false)
+
+const onClick = () => {
+	open.value = !open.value
+	emit('toggle', open.value)
+}
+</script>

+ 10 - 2
apps/web/src/layouts/MainLayout.vue

@@ -34,7 +34,7 @@
 				</div>
 			</el-header>
 
-			<el-main style="padding: 16px; overflow: auto">
+			<el-main style="padding: 16px; overflow: auto" :style="mainStyle">
 				<router-view />
 			</el-main>
 		</el-container>
@@ -42,12 +42,14 @@
 </template>
 <script setup lang="ts">
 import { useRouter, useRoute } from 'vue-router'
-import { computed } from 'vue'
+import { computed, provide, ref, type CSSProperties } from 'vue'
 import Sidebar from '@/components/Sidebar/index.vue'
 
 const router = useRouter()
 const route = useRoute()
 
+const mainStyle = ref<CSSProperties>({})
+
 const isWorkflowPage = computed(() => {
 	return route.path.includes('/workflow/')
 })
@@ -55,6 +57,12 @@ const isWorkflowPage = computed(() => {
 const handleClick = () => {
 	router.push('/workflow/0')
 }
+
+provide('layout', {
+	setMainStyle(style: CSSProperties) {
+		mainStyle.value = style
+	}
+})
 </script>
 
 <style lang="less" scoped></style>

+ 216 - 207
apps/web/src/views/Editor.vue

@@ -1,154 +1,200 @@
 <template>
-	<div class="w-full h-full">
-		<Workflow
-			:workflow="workflow"
-			@click:node="handleNodeClick"
-			@create:node="handleNodeCreate"
-			@create:connection="onCreateConnection"
-			@drop="handleDrop"
-			@run="handleRunWorkflow"
-		/>
-		<RunWork v-model:visible="runVisible" />
-		<Setter :data="nodeID" v-model:visible="setterVisible" />
+	<div class="w-full h-full flex flex-col">
+		<div
+			class="h-60px border-b border-b-solid border-gray-200 flex items-center justify-between px-12px"
+		>
+			<div class="left flex items-center gap-4">
+				<el-breadcrumb separator="/">
+					<el-breadcrumb-item>Workspace</el-breadcrumb-item>
+					<el-breadcrumb-item>workflow_1</el-breadcrumb-item>
+				</el-breadcrumb>
+				<IconButton icon="iconoir:plus" type="primary" link>标签</IconButton>
+			</div>
+			<div class="right flex items-center gap-2">
+				<el-button type="default" size="small">发布</el-button>
+				<IconButton icon="lucide:history" type="default" link></IconButton>
+				<el-dropdown placement="bottom-end" popper-class="w-120px">
+					<IconButton icon="fluent-mdl2:more" type="default" link></IconButton>
+					<template #dropdown>
+						<el-dropdown-item>描述</el-dropdown-item>
+						<el-dropdown-item>复用</el-dropdown-item>
+						<el-dropdown-item>重命名</el-dropdown-item>
+						<el-dropdown-item divided>删除</el-dropdown-item>
+					</template>
+				</el-dropdown>
+			</div>
+		</div>
+		<el-splitter layout="vertical" class="flex-1">
+			<el-splitter-panel>
+				<Workflow
+					:workflow="workflow"
+					@click:node="handleNodeClick"
+					@create:node="handleNodeCreate"
+					@create:connection="onCreateConnection"
+					@drop="handleDrop"
+					@run="handleRunWorkflow"
+					class="bg-#f5f5f5"
+				/>
+				<RunWork v-model:visible="runVisible" />
+				<Setter :data="nodeID" v-model:visible="setterVisible" />
+			</el-splitter-panel>
+
+			<el-splitter-panel v-model:size.lazy="footerHeight" :min="32">
+				<EditorFooter @toggle="handleFooterToggle" />
+			</el-splitter-panel>
+		</el-splitter>
 	</div>
 </template>
 
 <script setup lang="ts">
+import { ref, inject, type CSSProperties } from 'vue'
 import { startNode, endNode, httpNode, conditionNode, databaseNode, codeNode } from '@repo/nodes'
 import { Workflow, type IWorkflow, type XYPosition, type Connection } from '@repo/workflow'
+
 import Setter from '@/components/setter/index.vue'
 import RunWork from '@/components/RunWork.vue'
+import EditorFooter from '@/features/editorFooter/index.vue'
+
+import { IconButton } from '@repo/ui'
+
 import type { SourceType } from '@repo/nodes'
-import { ref } from 'vue'
+
+const layout = inject<{ setMainStyle: (style: CSSProperties) => void }>('layout')
+
+layout?.setMainStyle({
+	padding: '0px'
+})
+
+const footerHeight = ref(32)
 
 const workflow = ref<IWorkflow>({
 	id: '1',
 	nodes: [
-		startNode, // 初始化节点,
-		endNode, // 初始化节点,
-		// httpNode,
-		// conditionNode,
-		// databaseNode,
-		// codeNode
-		{
-			id: 'node-1',
-			type: 'canvas-node',
-			position: { x: 100, y: 100 },
-			data: {
-				version: ['1.0.0'],
-				displayName: '用户输入',
-				name: 'chart',
-				description: '通过用户输入开启流程处理',
-				icon: 'fluent:comment-multiple-28-regular',
-				iconColor: '#296dff',
-				inputs: [],
-				outputs: [
-					{
-						index: 0,
-						type: 'main'
-					}
-				]
-			}
-		},
-		{
-			id: 'node-2',
-			type: 'canvas-node',
-			position: { x: 400, y: 100 },
-			data: {
-				version: ['1.0.0'],
-				displayName: '条件判断',
-				name: 'if',
-				description: '通过条件判断拆分多个流程分支',
-				icon: 'roentgen:guidepost',
-				iconColor: '#108e49',
-				inputs: [
-					{
-						index: 0,
-						type: 'main'
-					}
-				],
-				outputs: [
-					{
-						index: 0,
-						type: 'main',
-						label: 'true'
-					},
-					{
-						index: 1,
-						type: 'main',
-						label: 'false'
-					}
-				],
-				outputNames: ['true', 'false']
-			}
-		},
-		{
-			id: 'node-3',
-			type: 'canvas-node',
-			width: 96,
-			height: 96,
-			position: { x: 600, y: 300 },
-			data: {
-				version: ['1.0.0'],
-				displayName: '条件判断',
-				name: 'if',
-				description: '通过条件判断拆分多个流程分支',
-				icon: 'roentgen:guidepost',
-				iconColor: '#108e49',
-				inputs: [
-					{
-						index: 0,
-						type: 'main'
-					}
-				],
-				outputs: [
-					{
-						index: 0,
-						type: 'main',
-						label: 'true'
-					},
-					{
-						index: 1,
-						type: 'main',
-						label: 'false'
-					}
-				],
-				outputNames: ['true', 'false']
-			}
-		},
-		{
-			id: 'node-note',
-			type: 'canvas-node',
-			position: { x: 600, y: 300 },
-			data: {
-				version: ['1.0.0'],
-				displayName: '条件判断',
-				name: 'if',
-				description: '通过条件判断拆分多个流程分支',
-				icon: 'roentgen:guidepost',
-				iconColor: '#108e49',
-				inputs: [],
-				outputs: [],
-				// 便签数据
-				renderType: 'stickyNote',
-				content:
-					'# 标题\n\n这是一些便签内容,可以使用 **Markdown** 语法进行格式化。\n\n- 列表项 1\n- 列表项 2\n\n[链接](https://example.com)',
-				width: 400,
-				height: 200,
-				color: '#d6f5e3'
-			}
-		}
+		// startNode, // 初始化节点,
+		// endNode, // 初始化节点,
+		// // httpNode,
+		// // conditionNode,
+		// // databaseNode,
+		// // codeNode
+		// {
+		// 	id: 'node-1',
+		// 	type: 'canvas-node',
+		// 	position: { x: 100, y: 100 },
+		// 	data: {
+		// 		version: ['1.0.0'],
+		// 		displayName: '用户输入',
+		// 		name: 'chart',
+		// 		description: '通过用户输入开启流程处理',
+		// 		icon: 'fluent:comment-multiple-28-regular',
+		// 		iconColor: '#296dff',
+		// 		inputs: [],
+		// 		outputs: [
+		// 			{
+		// 				index: 0,
+		// 				type: 'main'
+		// 			}
+		// 		]
+		// 	}
+		// },
+		// {
+		// 	id: 'node-2',
+		// 	type: 'canvas-node',
+		// 	position: { x: 400, y: 100 },
+		// 	data: {
+		// 		version: ['1.0.0'],
+		// 		displayName: '条件判断',
+		// 		name: 'if',
+		// 		description: '通过条件判断拆分多个流程分支',
+		// 		icon: 'roentgen:guidepost',
+		// 		iconColor: '#108e49',
+		// 		inputs: [
+		// 			{
+		// 				index: 0,
+		// 				type: 'main'
+		// 			}
+		// 		],
+		// 		outputs: [
+		// 			{
+		// 				index: 0,
+		// 				type: 'main',
+		// 				label: 'true'
+		// 			},
+		// 			{
+		// 				index: 1,
+		// 				type: 'main',
+		// 				label: 'false'
+		// 			}
+		// 		],
+		// 		outputNames: ['true', 'false']
+		// 	}
+		// },
+		// {
+		// 	id: 'node-3',
+		// 	type: 'canvas-node',
+		// 	width: 96,
+		// 	height: 96,
+		// 	position: { x: 600, y: 300 },
+		// 	data: {
+		// 		version: ['1.0.0'],
+		// 		displayName: '条件判断',
+		// 		name: 'if',
+		// 		description: '通过条件判断拆分多个流程分支',
+		// 		icon: 'roentgen:guidepost',
+		// 		iconColor: '#108e49',
+		// 		inputs: [
+		// 			{
+		// 				index: 0,
+		// 				type: 'main'
+		// 			}
+		// 		],
+		// 		outputs: [
+		// 			{
+		// 				index: 0,
+		// 				type: 'main',
+		// 				label: 'true'
+		// 			},
+		// 			{
+		// 				index: 1,
+		// 				type: 'main',
+		// 				label: 'false'
+		// 			}
+		// 		],
+		// 		outputNames: ['true', 'false']
+		// 	}
+		// },
+		// {
+		// 	id: 'node-note',
+		// 	type: 'canvas-node',
+		// 	position: { x: 600, y: 300 },
+		// 	data: {
+		// 		version: ['1.0.0'],
+		// 		displayName: '条件判断',
+		// 		name: 'if',
+		// 		description: '通过条件判断拆分多个流程分支',
+		// 		icon: 'roentgen:guidepost',
+		// 		iconColor: '#108e49',
+		// 		inputs: [],
+		// 		outputs: [],
+		// 		// 便签数据
+		// 		renderType: 'stickyNote',
+		// 		content:
+		// 			'# 标题\n\n这是一些便签内容,可以使用 **Markdown** 语法进行格式化。\n\n- 列表项 1\n- 列表项 2\n\n[链接](https://example.com)',
+		// 		width: 400,
+		// 		height: 200,
+		// 		color: '#d6f5e3'
+		// 	}
+		// }
 	],
 	edges: [
-		{
-			id: 'edge-1-2',
-			source: 'node-1',
-			target: 'node-2',
-			type: 'canvas-edge',
-			data: {
-				label: 'Edge 1-2'
-			}
-		}
+		// {
+		// 	id: 'edge-1-2',
+		// 	source: 'node-1',
+		// 	target: 'node-2',
+		// 	type: 'canvas-edge',
+		// 	data: {
+		// 		label: 'Edge 1-2'
+		// 	}
+		// }
 		// {
 		//     id: 'edge-1-2',
 		//     source: 'start-node',
@@ -185,7 +231,6 @@ const workflow = ref<IWorkflow>({
 		//         label: 'Edge 1-2'
 		//     }
 		// },
-
 		// {
 		//     id: 'edge-1-4',
 		//     source: 'code-node',
@@ -196,77 +241,18 @@ const workflow = ref<IWorkflow>({
 		//     }
 		// }
 	]
-	// 	id: '1',
-	// 	nodes: [
-	// 		{
-	// 			id: 'node-1',
-	// 			type: 'canvas-node',
-	// 			position: { x: 100, y: 100 },
-	// 			width: 96,
-	// 			height: 96,
-	// 			data: {
-	// 				version: ['1.0.0'],
-	// 				displayName: '用户输入',
-	// 				name: 'chart',
-	// 				description: '通过用户输入开启流程处理',
-	// 				icon: 'fluent:comment-multiple-28-regular',
-	// 				iconColor: '#296dff',
-	// 				inputs: [],
-	// 				outputs: [
-	// 					{
-	// 						index: 0,
-	// 						type: 'main'
-	// 					}
-	// 				]
-	// 			}
-	// 		},
-	// 		{
-	// 			id: 'node-2',
-	// 			type: 'canvas-node',
-	// 			width: 96,
-	// 			height: 96,
-	// 			position: { x: 400, y: 100 },
-	// 			data: {
-	// 				version: ['1.0.0'],
-	// 				displayName: '条件判断',
-	// 				name: 'if',
-	// 				description: '通过条件判断拆分多个流程分支',
-	// 				icon: 'roentgen:guidepost',
-	// 				iconColor: '#108e49',
-	// 				inputs: [
-	// 					{
-	// 						index: 0,
-	// 						type: 'main'
-	// 					}
-	// 				],
-	// 				outputs: [
-	// 					{
-	// 						index: 0,
-	// 						type: 'main',
-	// 						label: 'true'
-	// 					},
-	// 					{
-	// 						index: 1,
-	// 						type: 'main',
-	// 						label: 'false'
-	// 					}
-	// 				],
-	// 				outputNames: ['true', 'false']
-	// 			}
-	// 		}
-	// 	],
-	// 	edges: [
-	// 		{
-	// 			id: 'edge-1-2',
-	// 			source: 'node-1',
-	// 			target: 'node-2',
-	// 			type: 'canvas-edge',
-	// 			data: {
-	// 				label: 'Edge 1-2'
-	// 			}
-	// 		}
-	// 	]
 })
+
+/**
+ * Editor
+ */
+const handleFooterToggle = (open: boolean) => {
+	footerHeight.value = open ? 200 : 32
+}
+
+/**
+ * Workflow
+ */
 const nodeID = ref('')
 const setterVisible = ref(false)
 const runVisible = ref(false)
@@ -274,10 +260,33 @@ const handleRunWorkflow = () => {
 	runVisible.value = true
 	console.log('run workflow')
 }
-const handleNodeCreate = (value: SourceType) => {
+const handleNodeCreate = (value: SourceType | string) => {
 	console.log(value)
 
+	if (typeof value === 'string') {
+		if (value === 'stickyNote') {
+			workflow.value.nodes.push({
+				id: 'node-note',
+				type: 'canvas-node',
+				position: { x: 600, y: 300 },
+				data: {
+					version: ['1.0.0'],
+					inputs: [],
+					outputs: [],
+					renderType: 'stickyNote',
+					content: '注释内容,可以使用 **Markdown** 语法进行格式化。',
+					width: 400,
+					height: 200,
+					color: '#fff5d6'
+				}
+			})
+		}
+		return
+	}
+
 	const nodeMap: Record<string, any> = {
+		start: startNode,
+		end: endNode,
 		http: httpNode,
 		condition: conditionNode,
 		code: codeNode,

+ 13 - 4
packages/ui/components/icon-button/IconButton.vue

@@ -1,6 +1,14 @@
 <template>
-	<ElButton :type="type" :loading="loading" :disabled="disabled" :size="size" :class="{ square }">
-		<Icon :name="icon" :color="iconColor"></Icon>
+	<ElButton
+		:type="type"
+		:loading="loading"
+		:disabled="disabled"
+		:size="size"
+		:class="{ square }"
+		v-bind="$attrs"
+	>
+		<Icon :icon="icon" :color="iconColor"></Icon>
+		<slot>{{ label }}</slot>
 	</ElButton>
 </template>
 
@@ -12,14 +20,15 @@ withDefaults(
 	defineProps<{
 		icon: string
 		iconColor?: string
-		size?: 'small' | 'medium' | 'large'
+		size?: 'small' | 'large' | 'default'
 		loading?: boolean
 		type?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'default'
 		disabled?: boolean
 		square?: boolean
+		label?: string
 	}>(),
 	{
-		size: 'medium',
+		size: 'default',
 		type: 'default',
 		loading: false
 	}

+ 9 - 3
packages/workflow/src/components/Canvas.vue

@@ -80,6 +80,7 @@ const props = withDefaults(
 	}
 )
 
+const showMinimap = ref(false)
 const vueFlow = useVueFlow(props.id)
 
 const { viewport, viewportRef, project, zoomIn, zoomOut, fitView, zoomTo } = vueFlow
@@ -133,10 +134,14 @@ const onZoomToFit = () => {
 const onResetZoom = () => {
 	zoomTo(1)
 }
-const onAddNode = (value: SourceType) => {
+const onAddNode = (value: SourceType | string) => {
 	emit('create:node', value)
 }
 
+const onToggleMinimap = () => {
+	showMinimap.value = !showMinimap.value
+}
+
 /**
  * Connections / Edges
  */
@@ -184,7 +189,6 @@ function onClickConnectionAdd(connection: Connection) {
 const handleRun = () => {
 	emit('run')
 }
-console.log(props.nodes)
 </script>
 
 <template>
@@ -245,10 +249,11 @@ console.log(props.nodes)
 		</template>
 
 		<MiniMap
+			v-show="showMinimap"
 			:height="120"
 			:width="180"
 			:node-border-radius="16"
-			class="bg-white bottom-40px!"
+			class="bottom-40px! bg-#f5f5f5 border border-solid border-gray-300"
 			position="bottom-left"
 			pannable
 			zoomable
@@ -261,6 +266,7 @@ console.log(props.nodes)
 			@reset-zoom="onResetZoom"
 			@add-node="onAddNode"
 			@run="handleRun"
+			@toggle-minimap="onToggleMinimap"
 		/>
 
 		<slot name="canvas-background" v-bind="{ viewport }">

+ 13 - 2
packages/workflow/src/components/elements/CanvasControlBar.vue

@@ -11,7 +11,8 @@ const emit = defineEmits<{
 	'zoom-to-fit': []
 	'tidy-up': []
 	'toggle-zoom-mode': []
-	'add-node': [value: SourceType]
+	'add-node': [value: SourceType | string]
+	'toggle-minimap': []
 	run: []
 }>()
 
@@ -31,12 +32,16 @@ function onZoomToFit() {
 	emit('zoom-to-fit')
 }
 
-function onAddNode(value: SourceType) {
+function onAddNode(value: SourceType | string) {
 	emit('add-node', value)
 }
 function onRun() {
 	emit('run')
 }
+
+function onToggleMinimap() {
+	emit('toggle-minimap')
+}
 </script>
 
 <template>
@@ -54,6 +59,12 @@ function onRun() {
 			<ElButton @click="onResetZoom">
 				<Icon icon="bx:reset" height="16" width="16" />
 			</ElButton>
+			<ElButton @click="onToggleMinimap" square>
+				<Icon icon="lucide:map" height="16" width="16" />
+			</ElButton>
+			<ElButton @click="onAddNode('stickyNote')" square>
+				<Icon icon="lucide:file-plus-corner" height="16" width="16" />
+			</ElButton>
 			<AddNode @add-node="onAddNode" />
 			<ElButton @click="onRun" type="success">
 				<Icon icon="lucide:play" height="16" width="16" class="mr-1" /> 执行

+ 7 - 3
packages/workflow/src/components/elements/nodes/render-types/NodeStickyNote.vue

@@ -7,6 +7,10 @@ import type { OnResize } from '@vue-flow/node-resizer'
 import type { NodeProps, XYPosition } from '@vue-flow/core'
 import type { IWorkflowNode } from '../../../../Interface'
 
+defineOptions({
+	inheritAttrs: false
+})
+
 const node = inject<{
 	props?: NodeProps<
 		IWorkflowNode['data'] & {
@@ -45,10 +49,9 @@ const modelValue = computed({
 
 const editMode = ref(false)
 
-const handleSetEditMode = (_link: HTMLElement, e: MouseEvent) => {
-	e.stopPropagation()
+const handleSetEditMode = (edit: boolean) => {
 	if (!isReadOnly.value) {
-		editMode.value = true
+		editMode.value = edit
 	}
 }
 
@@ -90,6 +93,7 @@ function onResize(event: OnResize) {
 			:readOnly="node?.props?.readOnly"
 			:editMode="editMode"
 			@dblclick.stop="handleSetEditMode"
+			@edit="handleSetEditMode"
 		/>
 	</div>
 </template>

+ 12 - 0
pnpm-lock.yaml

@@ -148,6 +148,9 @@ importers:
       normalize.css:
         specifier: ^8.0.1
         version: 8.0.1
+      uuid:
+        specifier: ^13.0.0
+        version: 13.0.0
       vue:
         specifier: ^3.5.24
         version: 3.5.27(typescript@5.9.3)
@@ -155,6 +158,9 @@ importers:
         specifier: '4'
         version: 4.6.4(vue@3.5.27(typescript@5.9.3))
     devDependencies:
+      '@repo/ui':
+        specifier: workspace:*
+        version: link:../../packages/ui
       '@repo/workflow':
         specifier: workspace:*
         version: link:../../packages/workflow
@@ -6142,6 +6148,10 @@ packages:
     resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
     hasBin: true
 
+  uuid@13.0.0:
+    resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
+    hasBin: true
+
   uuid@9.0.1:
     resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
     hasBin: true
@@ -13956,6 +13966,8 @@ snapshots:
 
   uuid@10.0.0: {}
 
+  uuid@13.0.0: {}
+
   uuid@9.0.1: {}
 
   v8-compile-cache-lib@3.0.1: {}