From 547c79123dba9619114143088b36b60aaf54745f Mon Sep 17 00:00:00 2001 From: Jerry Tian Date: Sun, 6 Apr 2025 22:18:16 -0400 Subject: [PATCH] feat: add API for adding watermark image to video, with tests and read-me file --- .../api_scripts/hello_test.py | 85 ++++++++++ .../api_scripts/video_add_watermark.md | 131 +++++++++++++++ .../api_scripts/video_add_watermark.py | 101 ++++++++++++ .../api_scripts/video_add_watermark.sh | 114 ++++++++++++- .../app/api/script/queue/route.tsx | 12 ++ .../app/api/script/route.tsx | 26 ++- .../services/bashExecService.ts | 150 ++++++++++++++---- package-lock.json | 34 +++- package.json | 3 +- 9 files changed, 605 insertions(+), 51 deletions(-) create mode 100644 apps/demo-nextjs-app-router/api_scripts/hello_test.py create mode 100644 apps/demo-nextjs-app-router/api_scripts/video_add_watermark.md create mode 100644 apps/demo-nextjs-app-router/api_scripts/video_add_watermark.py create mode 100644 apps/demo-nextjs-app-router/app/api/script/queue/route.tsx diff --git a/apps/demo-nextjs-app-router/api_scripts/hello_test.py b/apps/demo-nextjs-app-router/api_scripts/hello_test.py new file mode 100644 index 0000000..93dcfc8 --- /dev/null +++ b/apps/demo-nextjs-app-router/api_scripts/hello_test.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +import requests +import json +import time +import sys + +# Base URL for the API +BASE_URL = "http://localhost:4200/api/script" + +# Arguments to pass to the script +ARG1 = "hello" +ARG2 = "world" + +def main(): + print("Starting script execution...") + + # Start script execution and get token + try: + response = requests.post( + BASE_URL, + json={ + "scriptPath": "/tmp/api_scripts/hello_test.sh", + "args": [ARG1, ARG2] + }, + headers={"Content-Type": "application/json"} + ) + response.raise_for_status() # Raise exception for non-200 status codes + token_data = response.json() + token = token_data.get("token") + + if not token: + print(f"Failed to get token. Response: {response.text}") + sys.exit(1) + + print(f"Received token: {token}") + print("Polling for results...") + + except requests.exceptions.RequestException as e: + print(f"Error starting script execution: {e}") + sys.exit(1) + + # Poll for results with timeout + MAX_ATTEMPTS = 30 + SLEEP_SECONDS = 1 + status = "pending" + + for attempt in range(1, MAX_ATTEMPTS + 1): + if status not in ["pending", "running"]: + break + + print(f"Polling attempt {attempt} of {MAX_ATTEMPTS}...") + + try: + result_response = requests.get(f"{BASE_URL}?token={token}") + result_response.raise_for_status() + result_data = result_response.json() + status = result_data.get("status") + + if status in ["pending", "running"]: + print(f"Script is still {status}. Waiting {SLEEP_SECONDS}s...") + time.sleep(SLEEP_SECONDS) + else: + break + + except requests.exceptions.RequestException as e: + print(f"Error polling for results: {e}") + sys.exit(1) + + if status in ["pending", "running"]: + print("Timeout waiting for script to complete") + sys.exit(1) + + # Print results + print(f"Script execution completed with status: {status}") + print(f"Result: {json.dumps(result_data, indent=2)}") + + # Pretty print stdout if available + if result_data.get("result", {}).get("stdout"): + stdout = result_data["result"]["stdout"] + print("\nScript output:") + print(stdout) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/apps/demo-nextjs-app-router/api_scripts/video_add_watermark.md b/apps/demo-nextjs-app-router/api_scripts/video_add_watermark.md new file mode 100644 index 0000000..2e43173 --- /dev/null +++ b/apps/demo-nextjs-app-router/api_scripts/video_add_watermark.md @@ -0,0 +1,131 @@ +# Video Watermark API Usage Guide + +## Overview + +The Video Watermark API allows you to add watermarks to video files asynchronously. The process involves two steps: + +1. Submit a watermark job and receive a token +2. Use the token to check the job status and get the result + +## Base URL + +``` +https://animator-gg-api.ca2324.servep2p.com:8443 +``` + +## Step 1: Submit a Video Watermark Job + +**Request:** + +```bash +curl -X POST "https://animator-gg-api.ca2324.servep2p.com:8443/api/script" \ + -H "Content-Type: application/json" \ + -d '{ + "scriptPath": "/tmp/api_scripts/video_add_watermark.sh", + "args": [ + "https://example.com/input.mp4", + "https://example.com/watermark.png", + "30", + "30" + ] + }' +``` + +**Parameters:** + +- `scriptPath`: Must be video_add_watermark.sh +- `args`: Array containing: + - Input video URL (required) + - Watermark image URL (required) + - X position in pixels (required) + - Y position in pixels (required) + - Width for watermark resize (optional, 0 for no resize) + - Height for watermark resize (optional, 0 for no resize) + +**Response:** + +```json +{ + "token": "550e8400-e29b-41d4-a716-446655440000" +} +``` + +## Step 2: Check Job Status and Get Result + +**Request:** + +```bash +curl -X GET "https://animator-gg-api.ca2324.servep2p.com:8443/api/script?token=550e8400-e29b-41d4-a716-446655440000" +``` + +**Response:** + +```json +{ + "status": "completed", + "result": { + "success": true, + "stdout": "{\n \"input_video_url\": \"https://example.com/input.mp4\",\n \"watermark_png_url\": \"https://example.com/watermark.png\", \n \"pos_x\": 30,\n \"pos_y\": 30,\n \"size_w\": 0,\n \"size_h\": 0,\n \"output_file_name\": \"/var/www/html/watermarked_videos/bcc55e77-f1b4-472c-8a1b-0f3c852bd73a.mp4\",\n \"output_file_url\": \"https://example.com/watermarked_videos/bcc55e77-f1b4-472c-8a1b-0f3c852bd73a.mp4\"\n}", + "stderr": "..." + }, + "timestamp": 1743991194237 +} +``` + +When the job is complete, the `stdout` field will contain a JSON string with the output video URL. You'll need to parse this string to get the actual URL. + +## Example: Complete Process + +1. **Submit the job:** + +```bash +TOKEN=$(curl -s -X POST "https://animator-gg-api.ca2324.servep2p.com:8443/api/script" \ + -H "Content-Type: application/json" \ + -d '{ + "scriptPath": "/tmp/api_scripts/video_add_watermark.sh", + "args": [ + "https://ca2324.ddns.net:8443/jerry/_seed847064922.mp4", + "https://ca2324.ddns.net:8443/jerry/0a97d84a70fec3991cf05ee99ceb6469.png", + "30", + "30" + ] + }' | jq -r '.token') + +echo "Job submitted with token: $TOKEN" +``` + +2. **Check status until complete:** + +```bash +while true; do + RESULT=$(curl -s -X GET "https://animator-gg-api.ca2324.servep2p.com:8443/api/script?token=$TOKEN") + STATUS=$(echo $RESULT | jq -r '.status') + echo "Status: $STATUS" + + if [ "$STATUS" = "completed" ] || [ "$STATUS" = "failed" ]; then + break + fi + + sleep 2 +done + +# Extract the watermarked video URL +if [ "$STATUS" = "completed" ]; then + OUTPUT_JSON=$(echo $RESULT | jq -r '.result.stdout') + OUTPUT_URL=$(echo $OUTPUT_JSON | jq -r '.output_file_url') + echo "Watermarked video URL: $OUTPUT_URL" +fi +``` + +## Status Codes + +- `pending`: Job is waiting in the queue +- `running`: Job is currently processing +- `completed`: Job finished successfully +- `failed`: Job failed to complete + +## Notes + +- Results are stored for 24 hours after completion +- Processing large videos may take several minutes +- The API is rate-limited to 2 concurrent jobs diff --git a/apps/demo-nextjs-app-router/api_scripts/video_add_watermark.py b/apps/demo-nextjs-app-router/api_scripts/video_add_watermark.py new file mode 100644 index 0000000..b94f38d --- /dev/null +++ b/apps/demo-nextjs-app-router/api_scripts/video_add_watermark.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + + +#bash video_add_watermark.sh "$VID" "$PNG" 30 30 + +import requests +import json +import time +import sys + +# Base URL for the API +BASE_URL = "https://animator-gg-api.ca2324.servep2p.com:8443/api/script" + +# Arguments to pass to the script + +VID="https://ca2324.ddns.net:8443/jerry/_seed847064922.mp4" +PNG="https://ca2324.ddns.net:8443/jerry/0a97d84a70fec3991cf05ee99ceb6469.png" + +SAMPLE_OUPUT = """ +{ + "status": "completed", + "result": { + "success": true, + "stdout": "{\n \"input_video_url\": \"https://ca2324.ddns.net:8443/jerry/_seed847064922.mp4\",\n \"watermark_png_url\": \"https://ca2324.ddns.net:8443/jerry/0a97d84a70fec3991cf05ee99ceb6469.png\", \n \"pos_x\": 30,\n \"pos_y\": 30,\n \"size_w\": 0,\n \"size_h\": 0,\n \"output_file_name\": \"/var/www/html/watermarked_videos//bcc55e77-f1b4-472c-8a1b-0f3c852bd73a.mp4\",\n \"output_file_url\": \"https://ca2324.ddns.net:8443/watermarked_videos/bcc55e77-f1b4-472c-8a1b-0f3c852bd73a.mp4\"\n}", + "stderr": "x265 [info]: HEVC encoder version 4.1+1-1d117be\nx265 [info]: build info [Linux][GCC 11.4.0][64 bit] 8bit+10bit+12bit\nx265 [info]: using cpu capabilities: MMX2 SSE2Fast LZCNT SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2\nx265 [info]: Main profile, Level-4 (Main tier)\nx265 [info]: Thread pool created using 56 threads\nx265 [info]: Slices : 1\nx265 [info]: frame threads / pool features : 5 / wpp(25 rows)\nx265 [info]: Coding QT: max CU size, min CU size : 64 / 8\nx265 [info]: Residual QT: max TU size, max depth : 32 / 1 inter / 1 intra\nx265 [info]: ME / range / subpel / merge : hex / 57 / 1 / 2\nx265 [info]: Keyframe min / max / scenecut / bias : 24 / 250 / 40 / 5.00 \nx265 [info]: Lookahead / bframes / badapt : 15 / 4 / 0\nx265 [info]: b-pyramid / weightp / weightb : 1 / 1 / 0\nx265 [info]: References / ref-limit cu / depth : 2 / on / on\nx265 [info]: AQ: mode / str / qg-size / cu-tree : 2 / 1.0 / 32 / 1\nx265 [info]: Rate Control / qCompress : CRF-24.0 / 0.60\nx265 [info]: tools: rd=2 psy-rd=2.00 early-skip rskip mode=1 signhide tmvp\nx265 [info]: tools: fast-intra strong-intra-smoothing lslices=8 deblock sao\nx265 [info]: frame I: 2, Avg QP:24.88 kb/s: 14848.22\nx265 [info]: frame P: 95, Avg QP:24.31 kb/s: 8688.46 \nx265 [info]: frame B: 384, Avg QP:29.93 kb/s: 2459.11 \nx265 [info]: Weighted P-Frames: Y:8.4% UV:5.3%\n\nencoded 481 frames in 8.94s (53.79 fps), 3740.95 kb/s, Avg QP:28.80" + }, + "timestamp": 1743991194237 +} +""" + +def main(): + print("Starting script execution...") + + # Start script execution and get token + try: + response = requests.post( + BASE_URL, + json={ + "scriptPath": "/tmp/api_scripts/video_add_watermark.sh", + "args": [VID, PNG, 30, 30] + }, + headers={"Content-Type": "application/json"} + ) + response.raise_for_status() # Raise exception for non-200 status codes + token_data = response.json() + token = token_data.get("token") + + if not token: + print(f"Failed to get token. Response: {response.text}") + sys.exit(1) + + print(f"Received token: {token}") + print("Polling for results...") + + except requests.exceptions.RequestException as e: + print(f"Error starting script execution: {e}") + sys.exit(1) + + # Poll for results with timeout + MAX_ATTEMPTS = 30 + SLEEP_SECONDS = 1 + status = "pending" + + for attempt in range(1, MAX_ATTEMPTS + 1): + if status not in ["pending", "running"]: + break + + print(f"Polling attempt {attempt} of {MAX_ATTEMPTS}...") + + try: + result_response = requests.get(f"{BASE_URL}?token={token}") + result_response.raise_for_status() + result_data = result_response.json() + status = result_data.get("status") + + if status in ["pending", "running"]: + print(f"Script is still {status}. Waiting {SLEEP_SECONDS}s...") + time.sleep(SLEEP_SECONDS) + else: + break + + except requests.exceptions.RequestException as e: + print(f"Error polling for results: {e}") + sys.exit(1) + + if status in ["pending", "running"]: + print("Timeout waiting for script to complete") + sys.exit(1) + + # Print results + print(f"Script execution completed with status: {status}") + print(f"Result: {json.dumps(result_data, indent=2)}") + + # Pretty print stdout if available + if result_data.get("result", {}).get("stdout"): + stdout = result_data["result"]["stdout"] + print("\nScript output:") + print(stdout) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/apps/demo-nextjs-app-router/api_scripts/video_add_watermark.sh b/apps/demo-nextjs-app-router/api_scripts/video_add_watermark.sh index c578b8a..3627201 100644 --- a/apps/demo-nextjs-app-router/api_scripts/video_add_watermark.sh +++ b/apps/demo-nextjs-app-router/api_scripts/video_add_watermark.sh @@ -1,4 +1,112 @@ #!/bin/bash -echo "Script executed with args: $1 $2" -echo "Current time: $(date)" -echo "Current directory: $(pwd)" \ No newline at end of file + +output_folder="/var/www/html/watermarked_videos/" +output_base_url="https://ca2324.ddns.net:8443/watermarked_videos/" +# Input parameters +input_video_url="$1" +watermark_png_url="$2" +pos_x="${3:-10}" # Default to 10 if not provided +pos_y="${4:-10}" # Default to 10 if not provided +size_w="${5:-0}" # Default to 0 (keep aspect ratio) if not provided +size_h="${6:-0}" # Default to 0 (keep aspect ratio) if not provided + +# Validate required inputs +if [ -z "$input_video_url" ] || [ -z "$watermark_png_url" ]; then + echo "Error: Missing required parameters" >&2 + echo "Usage: $0 input_video_url watermark_png_url [pos_x] [pos_y] [size_w] [size_h]" >&2 + exit 1 +fi + +# Create temp directory +temp_dir="/tmp/watermark_$$" +mkdir -p "$temp_dir" || { + echo "Error: Failed to create temporary directory" >&2 + exit 1 +} + +# Download input video +video_filename=$(basename "$input_video_url") +local_video_path="$temp_dir/$video_filename" +if ! curl -s -L -o "$local_video_path" "$input_video_url"; then + echo "Error: Failed to download input video from $input_video_url" >&2 + rm -rf "$temp_dir" + exit 1 +fi + +# Download watermark image +watermark_filename=$(basename "$watermark_png_url") +local_watermark_path="$temp_dir/$watermark_filename" +if ! curl -s -L -o "$local_watermark_path" "$watermark_png_url"; then + echo "Error: Failed to download watermark image from $watermark_png_url" >&2 + rm -rf "$temp_dir" + exit 1 +fi + +# Validate downloaded files +if [ ! -f "$local_video_path" ]; then + echo "Error: Downloaded video file not found" >&2 + rm -rf "$temp_dir" + exit 1 +fi + +if [ ! -f "$local_watermark_path" ]; then + echo "Error: Downloaded watermark file not found" >&2 + rm -rf "$temp_dir" + exit 1 +fi + +# Generate a UUID-style output filename +output_uuid=$(uuidgen) +output_file="$temp_dir/$output_uuid.mp4" + +# Prepare ffmpeg filter for watermark +if [ "$size_w" -eq 0 ] && [ "$size_h" -eq 0 ]; then + # No resizing + filter_complex="overlay=$pos_x:$pos_y" +else + # Resize watermark before overlay + filter_complex="overlay=$pos_x:$pos_y:scale=$size_w:$size_h" +fi + +# Add watermark using ffmpeg and encode to HEVC (H.265) +if ! ffmpeg -y -loglevel error -i "$local_video_path" -i "$local_watermark_path" \ + -filter_complex "$filter_complex" \ + -c:v libx265 -tag:v hvc1 -crf 24 -preset veryfast \ + -c:a copy \ + -movflags +faststart \ + "${output_file}" 1>&2 ; then + echo "Error: Failed to add watermark to video" >&2 + rm -rf "$temp_dir" + exit 1 +fi + +# Move output file to the output folder +if ! mv "$output_file" "$output_folder"; then + echo "Error: Failed to move output file to $output_folder" >&2 + rm -rf "$temp_dir" + exit 1 +fi +# Clean up temporary files +rm -rf "$temp_dir" +# Generate the final output file path +output_file="$output_folder/$output_uuid.mp4" +# Generate the final output URL +output_file_url="${output_base_url}${output_uuid}.mp4" + +# Return JSON with parameters and output filename +cat << EOF +{ + "input_video_url": "$input_video_url", + "watermark_png_url": "$watermark_png_url", + "pos_x": $pos_x, + "pos_y": $pos_y, + "size_w": $size_w, + "size_h": $size_h, + "output_file_name": "$output_file", + "output_file_url": "$output_file_url" +} +EOF + +# Note: We're not deleting the temp directory so that the output file remains available + +exit 0 \ No newline at end of file diff --git a/apps/demo-nextjs-app-router/app/api/script/queue/route.tsx b/apps/demo-nextjs-app-router/app/api/script/queue/route.tsx new file mode 100644 index 0000000..203db58 --- /dev/null +++ b/apps/demo-nextjs-app-router/app/api/script/queue/route.tsx @@ -0,0 +1,12 @@ +import { NextResponse } from "next/server"; +import { getQueueStats } from "../../../../services/bashExecService"; + +// Endpoint to get queue statistics +export async function GET() { + try { + const stats = getQueueStats(); + return NextResponse.json(stats); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/apps/demo-nextjs-app-router/app/api/script/route.tsx b/apps/demo-nextjs-app-router/app/api/script/route.tsx index 23ebeed..dafc76a 100644 --- a/apps/demo-nextjs-app-router/app/api/script/route.tsx +++ b/apps/demo-nextjs-app-router/app/api/script/route.tsx @@ -1,9 +1,10 @@ import { NextRequest, NextResponse } from "next/server"; import { executeScript, - getQueueStats, + getScriptExecution, } from "../../../services/bashExecService"; +// Modified POST method that returns a token instead of waiting for execution result export async function POST(request: NextRequest) { try { const body = await request.json(); @@ -16,22 +17,33 @@ export async function POST(request: NextRequest) { ); } - const result = await executeScript({ + const token = executeScript({ scriptPath, args, }); - return NextResponse.json(result); + return NextResponse.json({ token }); } catch (error: any) { return NextResponse.json({ error: error.message }, { status: 500 }); } } -// Endpoint to get queue statistics -export async function GET() { +// API to query execution result by token from path parameter +export async function GET(request: NextRequest) { try { - const stats = getQueueStats(); - return NextResponse.json(stats); + // Get token from the search params in the URL + const { searchParams } = new URL(request.url); + const token = searchParams.get("token"); + + if (!token) { + return NextResponse.json( + { error: "Token is required as a query parameter" }, + { status: 400 }, + ); + } + + const execution = getScriptExecution(token); + return NextResponse.json(execution); } catch (error: any) { return NextResponse.json({ error: error.message }, { status: 500 }); } diff --git a/apps/demo-nextjs-app-router/services/bashExecService.ts b/apps/demo-nextjs-app-router/services/bashExecService.ts index d565f33..f0c6f01 100644 --- a/apps/demo-nextjs-app-router/services/bashExecService.ts +++ b/apps/demo-nextjs-app-router/services/bashExecService.ts @@ -1,8 +1,13 @@ import { execa } from "execa"; import PQueue from "p-queue"; +import { v4 as uuidv4 } from "uuid"; // Make sure to install this package // Configure the queue with a concurrency limit -const scriptQueue = new PQueue({ concurrency: 5 }); +const scriptQueue = new PQueue({ concurrency: 2 }); + +// Cleanup interval (24 hours in ms) +const CLEANUP_INTERVAL = 24 * 60 * 60 * 1000; +const RESULT_EXPIRY = 24 * 60 * 60 * 1000; // 24 hours in ms export type ScriptParams = { scriptPath: string; @@ -17,37 +22,76 @@ export type ScriptResult = { error?: string; }; +export type ScriptQueueState = { + status: "pending" | "running" | "completed" | "failed"; + result?: ScriptResult; + timestamp: number; +}; + +// Store execution results by token +const executionResults = new Map(); /** - * Executes a bash script with queue management + * Clean up expired execution results */ -export async function executeScript( - params: ScriptParams, -): Promise { - // Add the script execution to the queue - - // hardcoded the allowed script path for security reasons - var allowedScriptPaths = [ - "/tmp/api_scripts/hello_test.sh", - "/tmp/api_scripts/video_add_watermark.sh", - ]; - - if (!allowedScriptPaths.includes(params.scriptPath)) { - return { - success: false, - stdout: "", - stderr: "", - error: "Script path is not allowed", - }; +function cleanupExpiredResults() { + const now = Date.now(); + for (const [token, data] of executionResults.entries()) { + if (now - data.timestamp > RESULT_EXPIRY) { + executionResults.delete(token); + } } +} - const result = await scriptQueue.add(async () => { +// Set up periodic cleanup +setInterval(cleanupExpiredResults, CLEANUP_INTERVAL); + +/** + * Executes a bash script with queue management and returns a token immediately + */ +export function executeScript(params: ScriptParams): string { + const token = uuidv4(); + + // Initialize result as pending with current timestamp + executionResults.set(token, { + status: "pending", + timestamp: Date.now(), + }); + + // Add the script execution to the queue + scriptQueue.add(async () => { try { + // hardcoded the allowed script path for security reasons + var allowedScriptPaths = [ + "/tmp/api_scripts/hello_test.sh", + "/tmp/api_scripts/video_add_watermark.sh", + ]; + + if (!allowedScriptPaths.includes(params.scriptPath)) { + executionResults.set(token, { + status: "failed", + result: { + success: false, + stdout: "", + stderr: "", + error: "Script path is not allowed", + }, + timestamp: Date.now(), + }); + return; + } + + // Update status to running + executionResults.set(token, { + status: "running", + timestamp: Date.now(), + }); + // Execute the bash script const { stdout, stderr } = await execa( "bash", [params.scriptPath, ...(params.args || [])], { - buffer: true, // Changed to true to ensure we get the complete output + buffer: true, // Ensure we get the complete output }, ); @@ -56,23 +100,60 @@ export async function executeScript( params.onProgress(stdout.toString()); } - return { - success: true, - stdout: stdout ? stdout.toString() : "", - stderr: stderr ? stderr.toString() : "", - }; + // Store successful result + executionResults.set(token, { + status: "completed", + result: { + success: true, + stdout: stdout ? stdout.toString() : "", + stderr: stderr ? stderr.toString() : "", + }, + timestamp: Date.now(), + }); } catch (error: any) { // Handle script execution errors - return { - success: false, - stdout: error.stdout ? error.stdout.toString() : "", - stderr: error.stderr ? error.stderr.toString() : "", - error: error.message || "Failed to execute script", - }; + executionResults.set(token, { + status: "failed", + result: { + success: false, + stdout: error.stdout ? error.stdout.toString() : "", + stderr: error.stderr ? error.stderr.toString() : "", + error: error.message || "Failed to execute script", + }, + timestamp: Date.now(), + }); } }); - return result as ScriptResult; + return token; +} + +/** + * Query the status and result of a script execution + * @param token The token returned by executeScript + * @returns An object containing execution status and result (if available) + */ +export function getScriptExecution(token: string): ScriptQueueState { + const execution = executionResults.get(token); + + if (!execution) { + return { + status: "failed", + result: { + success: false, + stdout: "", + stderr: "", + error: "Invalid execution token or result expired", + }, + timestamp: Date.now(), + }; + } + + return { + status: execution.status, + result: execution.result, + timestamp: Date.now(), + }; } // Get current queue size and pending tasks @@ -81,6 +162,7 @@ export function getQueueStats() { size: scriptQueue.size, pending: scriptQueue.pending, isPaused: scriptQueue.isPaused, + activeExecutions: executionResults.size, }; } diff --git a/package-lock.json b/package-lock.json index 5526870..6a3eac8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,8 @@ "regenerator-runtime": "0.13.7", "robot3": "^0.4.1", "ts-morph": "^17.0.1", - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "uuid": "^11.1.0" }, "devDependencies": { "@commitlint/cli": "^17.0.0", @@ -3014,6 +3015,15 @@ "node": ">= 0.12" } }, + "node_modules/@cypress/request/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", @@ -30831,6 +30841,15 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -33282,12 +33301,15 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/uvu": { diff --git a/package.json b/package.json index 86452b4..72ef9a4 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "regenerator-runtime": "0.13.7", "robot3": "^0.4.1", "ts-morph": "^17.0.1", - "tslib": "^2.3.0" + "tslib": "^2.3.0", + "uuid": "^11.1.0" }, "devDependencies": { "@commitlint/cli": "^17.0.0",