mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 14:43:54 -05:00
Yaml language basics
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Download, FileText } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useWorkflowYamlStore } from '@/stores/workflows/yaml/store'
|
||||
|
||||
const logger = createLogger('ExportControls')
|
||||
|
||||
interface ExportControlsProps {
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export function ExportControls({ disabled = false }: ExportControlsProps) {
|
||||
const [isExporting, setIsExporting] = useState(false)
|
||||
const workflowState = useWorkflowStore()
|
||||
const { workflows, activeWorkflowId } = useWorkflowRegistry()
|
||||
const getYaml = useWorkflowYamlStore(state => state.getYaml)
|
||||
|
||||
const currentWorkflow = activeWorkflowId ? workflows[activeWorkflowId] : null
|
||||
|
||||
const downloadFile = (content: string, filename: string, mimeType: string) => {
|
||||
try {
|
||||
const blob = new Blob([content], { type: mimeType })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = filename
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
} catch (error) {
|
||||
logger.error('Failed to download file:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleExportJson = async () => {
|
||||
if (!currentWorkflow || !activeWorkflowId) {
|
||||
logger.warn('No active workflow to export')
|
||||
return
|
||||
}
|
||||
|
||||
setIsExporting(true)
|
||||
try {
|
||||
const exportData = {
|
||||
workflow: {
|
||||
id: activeWorkflowId,
|
||||
name: currentWorkflow.name,
|
||||
description: currentWorkflow.description,
|
||||
color: currentWorkflow.color,
|
||||
},
|
||||
state: {
|
||||
blocks: workflowState.blocks,
|
||||
edges: workflowState.edges,
|
||||
loops: workflowState.loops,
|
||||
parallels: workflowState.parallels,
|
||||
},
|
||||
exportedAt: new Date().toISOString(),
|
||||
version: '1.0'
|
||||
}
|
||||
|
||||
const jsonContent = JSON.stringify(exportData, null, 2)
|
||||
const filename = `${currentWorkflow.name.replace(/[^a-z0-9]/gi, '_')}_workflow.json`
|
||||
|
||||
downloadFile(jsonContent, filename, 'application/json')
|
||||
logger.info('Workflow exported as JSON')
|
||||
} catch (error) {
|
||||
logger.error('Failed to export workflow as JSON:', error)
|
||||
} finally {
|
||||
setIsExporting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleExportYaml = async () => {
|
||||
if (!currentWorkflow || !activeWorkflowId) {
|
||||
logger.warn('No active workflow to export')
|
||||
return
|
||||
}
|
||||
|
||||
setIsExporting(true)
|
||||
try {
|
||||
const yamlContent = getYaml()
|
||||
const filename = `${currentWorkflow.name.replace(/[^a-z0-9]/gi, '_')}_workflow.yaml`
|
||||
|
||||
downloadFile(yamlContent, filename, 'text/yaml')
|
||||
logger.info('Workflow exported as YAML')
|
||||
} catch (error) {
|
||||
logger.error('Failed to export workflow as YAML:', error)
|
||||
} finally {
|
||||
setIsExporting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='icon'
|
||||
disabled={disabled || isExporting || !currentWorkflow}
|
||||
className='hover:text-foreground'
|
||||
>
|
||||
<Download className='h-5 w-5' />
|
||||
<span className='sr-only'>Export Workflow</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{disabled
|
||||
? 'Export not available'
|
||||
: !currentWorkflow
|
||||
? 'No workflow to export'
|
||||
: 'Export Workflow'
|
||||
}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<DropdownMenuContent align='end' className='w-48'>
|
||||
<DropdownMenuItem
|
||||
onClick={handleExportJson}
|
||||
disabled={isExporting || !currentWorkflow}
|
||||
className='flex items-center gap-2 cursor-pointer'
|
||||
>
|
||||
<FileText className='h-4 w-4' />
|
||||
<div className='flex flex-col'>
|
||||
<span>Export as JSON</span>
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
Full workflow data
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={handleExportYaml}
|
||||
disabled={isExporting || !currentWorkflow}
|
||||
className='flex items-center gap-2 cursor-pointer'
|
||||
>
|
||||
<FileText className='h-4 w-4' />
|
||||
<div className='flex flex-col'>
|
||||
<span>Export as YAML</span>
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
Condensed workflow language
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
} from '../../../hooks/use-keyboard-shortcuts'
|
||||
import { useWorkflowExecution } from '../../hooks/use-workflow-execution'
|
||||
import { DeploymentControls } from './components/deployment-controls/deployment-controls'
|
||||
import { ExportControls } from './components/export-controls/export-controls'
|
||||
import { HistoryDropdownItem } from './components/history-dropdown-item/history-dropdown-item'
|
||||
import { MarketplaceModal } from './components/marketplace-modal/marketplace-modal'
|
||||
import { NotificationDropdownItem } from './components/notification-dropdown-item/notification-dropdown-item'
|
||||
@@ -1287,6 +1288,7 @@ export function ControlBar({ hasValidationErrors = false }: ControlBarProps) {
|
||||
{renderDuplicateButton()}
|
||||
{renderAutoLayoutButton()}
|
||||
{renderDebugModeToggle()}
|
||||
<ExportControls disabled={!userPermissions.canRead} />
|
||||
{/* {renderPublishButton()} */}
|
||||
{renderDeployButton()}
|
||||
{renderRunButton()}
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.6",
|
||||
"@react-email/components": "^0.0.34",
|
||||
"@sentry/nextjs": "^9.15.0",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/three": "0.177.0",
|
||||
"@vercel/og": "^0.6.5",
|
||||
"@vercel/speed-insights": "^1.2.0",
|
||||
@@ -86,6 +87,7 @@
|
||||
"input-otp": "^1.4.2",
|
||||
"ioredis": "^5.6.0",
|
||||
"jose": "6.0.11",
|
||||
"js-yaml": "4.1.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lenis": "^1.2.3",
|
||||
"lucide-react": "^0.479.0",
|
||||
|
||||
143
apps/sim/stores/workflows/yaml/README.md
Normal file
143
apps/sim/stores/workflows/yaml/README.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Workflow YAML Store
|
||||
|
||||
This store dynamically generates a condensed YAML representation of workflows from the JSON workflow state. It extracts input values, connections, and block relationships to create a clean, readable format.
|
||||
|
||||
## Features
|
||||
|
||||
- **Dynamic Input Extraction**: Automatically reads input values from block configurations and subblock stores
|
||||
- **Connection Mapping**: Determines preceding and following blocks from workflow edges
|
||||
- **Type-Aware Processing**: Handles different input types (text, numbers, booleans, objects) appropriately
|
||||
- **Auto-Refresh**: Automatically updates when workflow state or input values change
|
||||
- **Clean Format**: Generates well-formatted YAML with proper indentation
|
||||
|
||||
## YAML Structure
|
||||
|
||||
```yaml
|
||||
version: "1.0"
|
||||
blocks:
|
||||
block-id-1:
|
||||
type: "starter"
|
||||
name: "Start"
|
||||
inputs:
|
||||
startWorkflow: "manual"
|
||||
following:
|
||||
- "block-id-2"
|
||||
|
||||
block-id-2:
|
||||
type: "agent"
|
||||
name: "AI Agent"
|
||||
inputs:
|
||||
systemPrompt: "You are a helpful assistant"
|
||||
userPrompt: "Process the input data"
|
||||
model: "gpt-4"
|
||||
temperature: 0.7
|
||||
preceding:
|
||||
- "block-id-1"
|
||||
following:
|
||||
- "block-id-3"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { useWorkflowYamlStore } from '@/stores/workflows/yaml/store'
|
||||
|
||||
function WorkflowYamlViewer() {
|
||||
const yaml = useWorkflowYamlStore(state => state.getYaml())
|
||||
|
||||
return (
|
||||
<pre>
|
||||
<code>{yaml}</code>
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Manual Refresh
|
||||
|
||||
```typescript
|
||||
import { useWorkflowYamlStore } from '@/stores/workflows/yaml/store'
|
||||
|
||||
function WorkflowControls() {
|
||||
const refreshYaml = useWorkflowYamlStore(state => state.refreshYaml)
|
||||
|
||||
return (
|
||||
<button onClick={refreshYaml}>
|
||||
Refresh YAML
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
```typescript
|
||||
import { useWorkflowYamlStore } from '@/stores/workflows/yaml/store'
|
||||
|
||||
function WorkflowExporter() {
|
||||
const { yaml, lastGenerated, generateYaml } = useWorkflowYamlStore()
|
||||
|
||||
const exportToFile = () => {
|
||||
const blob = new Blob([yaml], { type: 'text/yaml' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'workflow.yaml'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Last generated: {lastGenerated ? new Date(lastGenerated).toLocaleString() : 'Never'}</p>
|
||||
<button onClick={generateYaml}>Regenerate</button>
|
||||
<button onClick={exportToFile}>Export YAML</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Input Types Handled
|
||||
|
||||
The store intelligently processes different subblock input types:
|
||||
|
||||
- **Text Inputs** (`short-input`, `long-input`): Trimmed strings
|
||||
- **Dropdowns/Combobox** (`dropdown`, `combobox`): Selected values
|
||||
- **Tables** (`table`): Arrays of objects (only if non-empty)
|
||||
- **Code Blocks** (`code`): Preserves formatting for strings and objects
|
||||
- **Switches** (`switch`): Boolean values
|
||||
- **Sliders** (`slider`): Numeric values
|
||||
- **Checkbox Lists** (`checkbox-list`): Arrays of selected items
|
||||
|
||||
## Auto-Refresh Behavior
|
||||
|
||||
The store automatically refreshes in these scenarios:
|
||||
|
||||
1. **Workflow Structure Changes**: When blocks are added, removed, or connections change
|
||||
2. **Input Value Changes**: When any subblock input values are modified
|
||||
3. **Debounced Updates**: Changes are debounced to prevent excessive regeneration
|
||||
|
||||
## Performance
|
||||
|
||||
- **Lazy Generation**: YAML is only generated when requested
|
||||
- **Caching**: Results are cached and only regenerated when data changes
|
||||
- **Debouncing**: Rapid changes are debounced to improve performance
|
||||
- **Selective Updates**: Only regenerates when meaningful changes occur
|
||||
|
||||
## Error Handling
|
||||
|
||||
If YAML generation fails, the store returns an error message in YAML comment format:
|
||||
|
||||
```yaml
|
||||
# Error generating YAML: [error message]
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `js-yaml`: For YAML serialization
|
||||
- `zustand`: For state management
|
||||
- `@/blocks`: For block configuration access
|
||||
- `@/stores/workflows/workflow/store`: For workflow state
|
||||
- `@/stores/workflows/subblock/store`: For input values
|
||||
268
apps/sim/stores/workflows/yaml/store.ts
Normal file
268
apps/sim/stores/workflows/yaml/store.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import { dump as yamlDump } from 'js-yaml'
|
||||
import { getBlock } from '@/blocks'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { useWorkflowStore } from '../workflow/store'
|
||||
import { useSubBlockStore } from '../subblock/store'
|
||||
import type { BlockState, WorkflowState } from '../workflow/types'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
|
||||
const logger = createLogger('WorkflowYamlStore')
|
||||
|
||||
interface YamlBlock {
|
||||
type: string
|
||||
name: string
|
||||
inputs?: Record<string, any>
|
||||
preceding?: string[]
|
||||
following?: string[]
|
||||
}
|
||||
|
||||
interface YamlWorkflow {
|
||||
version: string
|
||||
blocks: Record<string, YamlBlock>
|
||||
}
|
||||
|
||||
interface WorkflowYamlState {
|
||||
yaml: string
|
||||
lastGenerated?: number
|
||||
}
|
||||
|
||||
interface WorkflowYamlActions {
|
||||
generateYaml: () => void
|
||||
getYaml: () => string
|
||||
refreshYaml: () => void
|
||||
}
|
||||
|
||||
type WorkflowYamlStore = WorkflowYamlState & WorkflowYamlActions
|
||||
|
||||
/**
|
||||
* Extract input values from a block's subBlocks based on its configuration
|
||||
*/
|
||||
function extractBlockInputs(blockState: BlockState, blockId: string): Record<string, any> {
|
||||
const blockConfig = getBlock(blockState.type)
|
||||
const subBlockStore = useSubBlockStore.getState()
|
||||
const inputs: Record<string, any> = {}
|
||||
|
||||
if (!blockConfig) {
|
||||
// For custom blocks like loops/parallels, extract available subBlock values
|
||||
Object.entries(blockState.subBlocks || {}).forEach(([subBlockId, subBlockState]) => {
|
||||
const value = subBlockStore.getValue(blockId, subBlockId) ?? subBlockState.value
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
inputs[subBlockId] = value
|
||||
}
|
||||
})
|
||||
return inputs
|
||||
}
|
||||
|
||||
// Process each subBlock configuration
|
||||
blockConfig.subBlocks.forEach((subBlockConfig: SubBlockConfig) => {
|
||||
const subBlockId = subBlockConfig.id
|
||||
|
||||
// Skip hidden or conditional fields that aren't active
|
||||
if (subBlockConfig.hidden) return
|
||||
|
||||
// Get value from subblock store or fallback to block state
|
||||
const value = subBlockStore.getValue(blockId, subBlockId) ??
|
||||
blockState.subBlocks[subBlockId]?.value
|
||||
|
||||
// Include value if it exists and isn't empty
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
// Handle different input types appropriately
|
||||
switch (subBlockConfig.type) {
|
||||
case 'table':
|
||||
// Tables are arrays of objects
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
inputs[subBlockId] = value
|
||||
}
|
||||
break
|
||||
|
||||
case 'checkbox-list':
|
||||
// Checkbox lists return arrays
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
inputs[subBlockId] = value
|
||||
}
|
||||
break
|
||||
|
||||
case 'code':
|
||||
// Code blocks should preserve formatting
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
inputs[subBlockId] = value
|
||||
} else if (typeof value === 'object') {
|
||||
inputs[subBlockId] = value
|
||||
}
|
||||
break
|
||||
|
||||
case 'switch':
|
||||
// Boolean values
|
||||
inputs[subBlockId] = Boolean(value)
|
||||
break
|
||||
|
||||
case 'slider':
|
||||
// Numeric values
|
||||
if (typeof value === 'number' || (typeof value === 'string' && !isNaN(Number(value)))) {
|
||||
inputs[subBlockId] = Number(value)
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
// Text inputs, dropdowns, etc.
|
||||
if (typeof value === 'string' && value.trim()) {
|
||||
inputs[subBlockId] = value.trim()
|
||||
} else if (typeof value === 'object' || typeof value === 'number' || typeof value === 'boolean') {
|
||||
inputs[subBlockId] = value
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return inputs
|
||||
}
|
||||
|
||||
/**
|
||||
* Find preceding blocks for a given block ID
|
||||
*/
|
||||
function findPrecedingBlocks(blockId: string, edges: any[]): string[] {
|
||||
return edges
|
||||
.filter(edge => edge.target === blockId)
|
||||
.map(edge => edge.source)
|
||||
.filter((source, index, arr) => arr.indexOf(source) === index) // Remove duplicates
|
||||
}
|
||||
|
||||
/**
|
||||
* Find following blocks for a given block ID
|
||||
*/
|
||||
function findFollowingBlocks(blockId: string, edges: any[]): string[] {
|
||||
return edges
|
||||
.filter(edge => edge.source === blockId)
|
||||
.map(edge => edge.target)
|
||||
.filter((target, index, arr) => arr.indexOf(target) === index) // Remove duplicates
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate YAML representation of the workflow
|
||||
*/
|
||||
function generateWorkflowYaml(workflowState: WorkflowState): string {
|
||||
try {
|
||||
const yamlWorkflow: YamlWorkflow = {
|
||||
version: '1.0',
|
||||
blocks: {}
|
||||
}
|
||||
|
||||
// Process each block
|
||||
Object.entries(workflowState.blocks).forEach(([blockId, blockState]) => {
|
||||
const inputs = extractBlockInputs(blockState, blockId)
|
||||
const preceding = findPrecedingBlocks(blockId, workflowState.edges)
|
||||
const following = findFollowingBlocks(blockId, workflowState.edges)
|
||||
|
||||
const yamlBlock: YamlBlock = {
|
||||
type: blockState.type,
|
||||
name: blockState.name
|
||||
}
|
||||
|
||||
// Only include inputs if they exist
|
||||
if (Object.keys(inputs).length > 0) {
|
||||
yamlBlock.inputs = inputs
|
||||
}
|
||||
|
||||
// Only include connections if they exist
|
||||
if (preceding.length > 0) {
|
||||
yamlBlock.preceding = preceding
|
||||
}
|
||||
|
||||
if (following.length > 0) {
|
||||
yamlBlock.following = following
|
||||
}
|
||||
|
||||
yamlWorkflow.blocks[blockId] = yamlBlock
|
||||
})
|
||||
|
||||
// Convert to YAML with clean formatting
|
||||
return yamlDump(yamlWorkflow, {
|
||||
indent: 2,
|
||||
lineWidth: -1, // Disable line wrapping
|
||||
noRefs: true,
|
||||
sortKeys: false
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Failed to generate workflow YAML:', error)
|
||||
return `# Error generating YAML: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
}
|
||||
}
|
||||
|
||||
export const useWorkflowYamlStore = create<WorkflowYamlStore>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
yaml: '',
|
||||
lastGenerated: undefined,
|
||||
|
||||
generateYaml: () => {
|
||||
const workflowState = useWorkflowStore.getState()
|
||||
const yaml = generateWorkflowYaml(workflowState)
|
||||
|
||||
set({
|
||||
yaml,
|
||||
lastGenerated: Date.now()
|
||||
})
|
||||
},
|
||||
|
||||
getYaml: () => {
|
||||
const currentTime = Date.now()
|
||||
const { yaml, lastGenerated } = get()
|
||||
|
||||
// Auto-refresh if data is stale (older than 1 second) or never generated
|
||||
if (!lastGenerated || currentTime - lastGenerated > 1000) {
|
||||
get().generateYaml()
|
||||
return get().yaml
|
||||
}
|
||||
|
||||
return yaml
|
||||
},
|
||||
|
||||
refreshYaml: () => {
|
||||
get().generateYaml()
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: 'workflow-yaml-store'
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// Auto-refresh YAML when workflow state changes
|
||||
let lastWorkflowState: { blockCount: number; edgeCount: number } | null = null
|
||||
|
||||
useWorkflowStore.subscribe((state) => {
|
||||
const currentState = {
|
||||
blockCount: Object.keys(state.blocks).length,
|
||||
edgeCount: state.edges.length
|
||||
}
|
||||
|
||||
// Only refresh if the structure has changed
|
||||
if (!lastWorkflowState ||
|
||||
lastWorkflowState.blockCount !== currentState.blockCount ||
|
||||
lastWorkflowState.edgeCount !== currentState.edgeCount) {
|
||||
|
||||
lastWorkflowState = currentState
|
||||
|
||||
// Debounce the refresh to avoid excessive updates
|
||||
const refreshYaml = useWorkflowYamlStore.getState().refreshYaml
|
||||
setTimeout(refreshYaml, 100)
|
||||
}
|
||||
})
|
||||
|
||||
// Subscribe to subblock store changes
|
||||
let lastSubBlockChangeTime = 0
|
||||
|
||||
useSubBlockStore.subscribe((state) => {
|
||||
const currentTime = Date.now()
|
||||
|
||||
// Debounce rapid changes
|
||||
if (currentTime - lastSubBlockChangeTime > 100) {
|
||||
lastSubBlockChangeTime = currentTime
|
||||
|
||||
const refreshYaml = useWorkflowYamlStore.getState().refreshYaml
|
||||
setTimeout(refreshYaml, 100)
|
||||
}
|
||||
})
|
||||
12
bun.lock
12
bun.lock
@@ -95,6 +95,7 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.6",
|
||||
"@react-email/components": "^0.0.34",
|
||||
"@sentry/nextjs": "^9.15.0",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/three": "0.177.0",
|
||||
"@vercel/og": "^0.6.5",
|
||||
"@vercel/speed-insights": "^1.2.0",
|
||||
@@ -116,6 +117,7 @@
|
||||
"input-otp": "^1.4.2",
|
||||
"ioredis": "^5.6.0",
|
||||
"jose": "6.0.11",
|
||||
"js-yaml": "4.1.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lenis": "^1.2.3",
|
||||
"lucide-react": "^0.479.0",
|
||||
@@ -1324,6 +1326,8 @@
|
||||
|
||||
"@types/jest": ["@types/jest@26.0.24", "", { "dependencies": { "jest-diff": "^26.0.0", "pretty-format": "^26.0.0" } }, "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w=="],
|
||||
|
||||
"@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
|
||||
|
||||
"@types/jsdom": ["@types/jsdom@21.1.7", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^7.0.0" } }, "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
@@ -1482,7 +1486,7 @@
|
||||
|
||||
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
|
||||
"argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
|
||||
|
||||
@@ -3472,8 +3476,6 @@
|
||||
|
||||
"jest-diff/pretty-format": ["pretty-format@26.6.2", "", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="],
|
||||
|
||||
"js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"jsondiffpatch/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||
|
||||
"linebreak/base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="],
|
||||
@@ -3498,6 +3500,8 @@
|
||||
|
||||
"loose-envify/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"mammoth/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
|
||||
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
@@ -3844,6 +3848,8 @@
|
||||
|
||||
"openai/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"openapi/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
|
||||
"openapi/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"ora/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
|
||||
|
||||
Reference in New Issue
Block a user