@editframe/vite-plugin 0.31.1-beta.0 → 0.31.2-beta.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.
@@ -270,9 +270,15 @@ function createJitTranscodeMiddleware(options, assetFunctions) {
270
270
  }
271
271
  return;
272
272
  }
273
+ const fragmentIndexResult = await assetFunctions.generateTrackFragmentIndex(options.cacheRoot, mediaPath);
274
+ const fragmentIndex = JSON.parse(await import("node:fs/promises").then((fs) => fs.readFile(fragmentIndexResult.cachePath, "utf-8")));
273
275
  const getTrackId = (renditionId) => {
274
- if (renditionId === "audio") return 2;
275
276
  if (renditionId === "scrub") return -1;
277
+ if (renditionId === "audio") {
278
+ for (const [trackIdStr, trackInfo] of Object.entries(fragmentIndex)) if (trackInfo.type === "audio") return Number.parseInt(trackIdStr, 10);
279
+ return 2;
280
+ }
281
+ for (const [trackIdStr, trackInfo] of Object.entries(fragmentIndex)) if (trackInfo.type === "video") return Number.parseInt(trackIdStr, 10);
276
282
  return 1;
277
283
  };
278
284
  const initMatch = endpoint?.match(/^init\.(mp4|m4s)$/);
@@ -288,11 +294,11 @@ function createJitTranscodeMiddleware(options, assetFunctions) {
288
294
  const trackUrl = `/@ef-track/${mediaPath}?trackId=${trackId}`;
289
295
  trackTaskResult = await assetFunctions.generateTrack(options.cacheRoot, mediaPath, trackUrl);
290
296
  }
291
- const fragmentIndexResult = await assetFunctions.generateTrackFragmentIndex(options.cacheRoot, mediaPath);
292
- const track = JSON.parse(await import("node:fs/promises").then((fs) => fs.readFile(fragmentIndexResult.cachePath, "utf-8")))[trackId];
297
+ const track = fragmentIndex[trackId];
293
298
  if (!track) {
299
+ const validTracks = Object.keys(fragmentIndex).join(", ");
294
300
  res.writeHead(404, { "Content-Type": "application/json" });
295
- res.end(JSON.stringify({ error: `Track ${trackId} not found` }));
301
+ res.end(JSON.stringify({ error: `Track ${trackId} not found (valid tracks: ${validTracks})` }));
296
302
  return;
297
303
  }
298
304
  const { offset, size } = track.initSegment;
@@ -327,11 +333,11 @@ function createJitTranscodeMiddleware(options, assetFunctions) {
327
333
  const trackUrl = `/@ef-track/${mediaPath}?trackId=${trackId}`;
328
334
  trackTaskResult = await assetFunctions.generateTrack(options.cacheRoot, mediaPath, trackUrl);
329
335
  }
330
- const fragmentIndexResult = await assetFunctions.generateTrackFragmentIndex(options.cacheRoot, mediaPath);
331
- const track = JSON.parse(await import("node:fs/promises").then((fs) => fs.readFile(fragmentIndexResult.cachePath, "utf-8")))[trackId];
336
+ const track = fragmentIndex[trackId];
332
337
  if (!track) {
338
+ const validTracks = Object.keys(fragmentIndex).join(", ");
333
339
  res.writeHead(404, { "Content-Type": "application/json" });
334
- res.end(JSON.stringify({ error: `Track ${trackId} not found` }));
340
+ res.end(JSON.stringify({ error: `Track ${trackId} not found (valid tracks: ${validTracks})` }));
335
341
  return;
336
342
  }
337
343
  const segmentIndex = segmentId - 1;
@@ -1 +1 @@
1
- {"version":3,"file":"jitTranscodeMiddleware.js","names":["fragmentIndex: Record<number, TrackFragmentIndex>"],"sources":["../src/jitTranscodeMiddleware.ts"],"sourcesContent":["import { createReadStream, statSync } from \"node:fs\";\nimport type { ServerResponse } from \"node:http\";\nimport path from \"node:path\";\n\nimport type { IncomingMessage, NextFunction } from \"connect\";\nimport debug from \"debug\";\nimport mime from \"mime\";\n\nimport { forbidRelativePaths } from \"./forbidRelativePaths.js\";\n\n/**\n * Type for the TrackFragmentIndex from assets package.\n * Duplicated here to avoid import issues between package and direct imports.\n */\nexport interface TrackFragmentIndex {\n type: \"video\" | \"audio\";\n codec: string;\n duration: number;\n timescale: number;\n startTimeOffsetMs: number;\n initSegment: { offset: number; size: number };\n segments: Array<{ offset: number; size: number; duration: number }>;\n width?: number;\n height?: number;\n channel_count?: number;\n sample_rate?: number;\n}\n\n/**\n * Asset functions required by the JIT middleware.\n * These are injected to support both package imports and direct imports.\n */\nexport interface AssetFunctions {\n generateTrack: (cacheRoot: string, absolutePath: string, trackUrl: string) => Promise<{ cachePath: string }>;\n generateScrubTrack: (cacheRoot: string, absolutePath: string) => Promise<{ cachePath: string }>;\n generateTrackFragmentIndex: (cacheRoot: string, absolutePath: string) => Promise<{ cachePath: string }>;\n}\n\nexport interface JitMiddlewareOptions {\n root: string;\n cacheRoot: string;\n}\n\n/**\n * Stream a specific byte range from a file.\n * This is used for JIT segment serving where the server extracts the correct bytes.\n */\nexport function sendByteRange(\n res: ServerResponse,\n filePath: string,\n offset: number,\n size: number,\n contentType?: string,\n) {\n const log = debug(\"ef:sendByteRange\");\n const stats = statSync(filePath);\n const end = offset + size - 1;\n\n if (end >= stats.size) {\n log(`Requested range ${offset}-${end} exceeds file size ${stats.size}`);\n res.writeHead(416, { \"Content-Range\": `bytes */${stats.size}` });\n res.end();\n return;\n }\n\n log(`Streaming bytes ${offset}-${end} (${size} bytes) from ${filePath}`);\n \n res.writeHead(200, {\n \"Content-Type\": contentType || mime.getType(filePath) || \"video/mp4\",\n \"Content-Length\": size,\n \"Cache-Control\": \"public, max-age=3600\",\n });\n\n const readStream = createReadStream(filePath, { start: offset, end });\n readStream.pipe(res);\n}\n\n/**\n * Stream multiple byte ranges from a file concatenated together.\n * Used for creating playable .mp4 files by combining init segment + media segment.\n */\nexport function sendMultipleByteRanges(\n res: ServerResponse,\n filePath: string,\n ranges: Array<{ offset: number; size: number }>,\n contentType?: string,\n) {\n const log = debug(\"ef:sendMultipleByteRanges\");\n const stats = statSync(filePath);\n \n // Validate all ranges\n for (const range of ranges) {\n const end = range.offset + range.size - 1;\n if (end >= stats.size) {\n log(`Requested range ${range.offset}-${end} exceeds file size ${stats.size}`);\n res.writeHead(416, { \"Content-Range\": `bytes */${stats.size}` });\n res.end();\n return;\n }\n }\n\n const totalSize = ranges.reduce((sum, r) => sum + r.size, 0);\n log(`Streaming ${ranges.length} ranges (${totalSize} total bytes) from ${filePath}`);\n \n res.writeHead(200, {\n \"Content-Type\": contentType || \"video/mp4\",\n \"Content-Length\": totalSize,\n \"Cache-Control\": \"public, max-age=3600\",\n });\n\n // Stream ranges sequentially\n let rangeIndex = 0;\n \n const streamNextRange = () => {\n if (rangeIndex >= ranges.length) {\n res.end();\n return;\n }\n \n const range = ranges[rangeIndex]!;\n const end = range.offset + range.size - 1;\n const readStream = createReadStream(filePath, { start: range.offset, end });\n \n readStream.on(\"end\", () => {\n rangeIndex++;\n streamNextRange();\n });\n \n readStream.on(\"error\", (err) => {\n log(`Error streaming range ${rangeIndex}: ${err}`);\n res.destroy();\n });\n \n readStream.pipe(res, { end: false });\n };\n \n streamNextRange();\n}\n\n/**\n * Check if a hostname refers to the local vite server.\n * Handles various local hostname patterns including worktree domains.\n */\nexport function isLocalHost(hostname: string): boolean {\n const localPatterns = [\n 'localhost',\n '127.0.0.1',\n '0.0.0.0',\n '.localhost', // Matches *.localhost (worktree domains like main.localhost)\n ];\n \n const lowerHost = hostname.toLowerCase();\n return localPatterns.some(pattern => \n pattern.startsWith('.') \n ? lowerHost.endsWith(pattern) || lowerHost === pattern.slice(1)\n : lowerHost === pattern || lowerHost.startsWith(pattern + ':')\n );\n}\n\n/**\n * Resolve a URL to either a remote URL (for ffprobe) or a local file path.\n * \n * - Remote URLs (different host): passed directly to ffprobe (it supports http/https)\n * - Local URLs (localhost, *.localhost): resolved to local file path\n * \n * @param urlParam - The URL from the query parameter\n * @param root - The vite plugin root directory\n * @returns The path/URL to pass to ffprobe\n */\nexport function resolveMediaPath(\n urlParam: string,\n root: string,\n): string {\n try {\n const url = new URL(urlParam);\n const hostname = url.hostname;\n \n // If NOT a local URL, pass directly to ffprobe - it supports http/https URLs\n if (!isLocalHost(hostname)) {\n return urlParam;\n }\n \n // Local URL - resolve to file path\n let filePath = decodeURIComponent(url.pathname);\n\n // Remove leading slash for path.join\n if (filePath.startsWith(\"/\")) {\n filePath = filePath.slice(1);\n }\n\n // The root is already dev-projects/src, so if the URL path starts with \"src/\",\n // we should remove it to avoid duplication (src/src/assets -> src/assets)\n if (filePath.startsWith(\"src/\")) {\n filePath = filePath.slice(4); // Remove \"src/\"\n }\n\n return path.join(root, filePath);\n } catch {\n // If not a valid URL, treat as relative path\n let filePath = urlParam;\n if (filePath.startsWith(\"src/\")) {\n filePath = filePath.slice(4);\n }\n return path.join(root, filePath);\n }\n}\n\n/**\n * Convert fragment index segments to millisecond durations array.\n */\nexport function getSegmentDurationsMs(track: TrackFragmentIndex): number[] {\n return track.segments.map(\n (segment) => (segment.duration / track.timescale) * 1000,\n );\n}\n\n/**\n * Generate a JIT manifest for a local file.\n * Uses actual fragment index data for accurate segment information.\n */\nexport async function generateLocalJitManifest(\n absolutePath: string,\n sourceUrl: string,\n baseUrl: string,\n cacheRoot: string,\n assetFunctions: AssetFunctions,\n) {\n const log = debug(\"ef:generateLocalJitManifest\");\n \n // Generate the fragment index (this also ensures tracks are generated)\n log(`Generating fragment index for ${absolutePath}`);\n const fragmentIndexResult = await assetFunctions.generateTrackFragmentIndex(cacheRoot, absolutePath);\n const fragmentIndex: Record<number, TrackFragmentIndex> = JSON.parse(\n await import(\"node:fs/promises\").then((fs) => fs.readFile(fragmentIndexResult.cachePath, \"utf-8\")),\n );\n\n // Find video track (track 1) and audio track (track 2) and scrub track (-1)\n const videoTrack = fragmentIndex[1];\n const audioTrack = fragmentIndex[2];\n const scrubTrack = fragmentIndex[-1];\n\n const hasVideo = videoTrack?.type === \"video\";\n const hasAudio = audioTrack?.type === \"audio\";\n\n // Get duration from the longest track\n let durationMs = 0;\n if (hasVideo && videoTrack) {\n durationMs = Math.max(durationMs, (videoTrack.duration / videoTrack.timescale) * 1000);\n }\n if (hasAudio && audioTrack) {\n durationMs = Math.max(durationMs, (audioTrack.duration / audioTrack.timescale) * 1000);\n }\n const durationSeconds = durationMs / 1000;\n\n // Get video dimensions from track\n const width = hasVideo && videoTrack && \"width\" in videoTrack ? videoTrack.width : 1920;\n const height = hasVideo && videoTrack && \"height\" in videoTrack ? videoTrack.height : 1080;\n const codec = hasVideo && videoTrack ? videoTrack.codec : \"avc1.640029\";\n\n // Get actual segment durations from fragment index\n const videoSegmentDurationsMs = hasVideo && videoTrack ? getSegmentDurationsMs(videoTrack) : [];\n const scrubSegmentDurationsMs = scrubTrack ? getSegmentDurationsMs(scrubTrack) : [];\n const audioSegmentDurationsMs = hasAudio && audioTrack ? getSegmentDurationsMs(audioTrack) : [];\n\n // Average segment duration for backward compatibility\n const avgVideoSegmentDurationMs = videoSegmentDurationsMs.length > 0\n ? videoSegmentDurationsMs.reduce((a, b) => a + b, 0) / videoSegmentDurationsMs.length\n : 2000;\n const avgScrubSegmentDurationMs = scrubSegmentDurationsMs.length > 0\n ? scrubSegmentDurationsMs.reduce((a, b) => a + b, 0) / scrubSegmentDurationsMs.length\n : 30000;\n const avgAudioSegmentDurationMs = audioSegmentDurationsMs.length > 0\n ? audioSegmentDurationsMs.reduce((a, b) => a + b, 0) / audioSegmentDurationsMs.length\n : 2000;\n\n log(`Video: ${videoSegmentDurationsMs.length} segments, Audio: ${audioSegmentDurationsMs.length} segments, Scrub: ${scrubSegmentDurationsMs.length} segments`);\n\n // Construct manifest matching ManifestResponse format\n const manifest = {\n version: \"1.0\",\n type: \"com.editframe/local-jit-manifest\",\n sourceUrl: sourceUrl,\n duration: durationSeconds,\n durationMs: durationMs,\n baseUrl: baseUrl,\n\n videoRenditions: hasVideo\n ? [\n {\n id: \"high\",\n width: width,\n height: height,\n bitrate: 5000000,\n codec: codec,\n container: \"video/mp4\",\n mimeType: `video/mp4; codecs=\"${codec}\"`,\n segmentDuration: avgVideoSegmentDurationMs / 1000,\n segmentDurationMs: avgVideoSegmentDurationMs,\n segmentDurationsMs: videoSegmentDurationsMs,\n startTimeOffsetMs: videoTrack!.startTimeOffsetMs,\n frameRate: 30,\n profile: \"High\",\n level: \"4.1\",\n },\n ...(scrubTrack\n ? [\n {\n id: \"scrub\",\n width: 320,\n height: Math.round((320 * (height ?? 1080)) / (width ?? 1920)),\n bitrate: 100000,\n codec: scrubTrack.codec,\n container: \"video/mp4\",\n mimeType: `video/mp4; codecs=\"${scrubTrack.codec}\"`,\n segmentDuration: avgScrubSegmentDurationMs / 1000,\n segmentDurationMs: avgScrubSegmentDurationMs,\n segmentDurationsMs: scrubSegmentDurationsMs,\n startTimeOffsetMs: scrubTrack.startTimeOffsetMs,\n frameRate: 15,\n profile: \"High\",\n level: \"4.1\",\n },\n ]\n : []),\n ]\n : [],\n\n audioRenditions: hasAudio && audioTrack\n ? [\n {\n id: \"audio\",\n channels: \"channel_count\" in audioTrack ? audioTrack.channel_count : 2,\n sampleRate: \"sample_rate\" in audioTrack ? audioTrack.sample_rate : 48000,\n bitrate: 128000,\n codec: audioTrack.codec,\n container: \"audio/mp4\",\n mimeType: `audio/mp4; codecs=\"${audioTrack.codec}\"`,\n segmentDuration: avgAudioSegmentDurationMs / 1000,\n segmentDurationMs: avgAudioSegmentDurationMs,\n segmentDurationsMs: audioSegmentDurationsMs,\n language: \"en\",\n },\n ]\n : [],\n\n endpoints: {\n initSegment: `${baseUrl}/api/v1/transcode/{rendition}/init.mp4?url=${encodeURIComponent(sourceUrl)}`,\n mediaSegment: `${baseUrl}/api/v1/transcode/{rendition}/{segmentId}.mp4?url=${encodeURIComponent(sourceUrl)}`,\n },\n\n jitInfo: {\n parallelTranscodingSupported: true,\n expectedTranscodeLatency: 100, // Local is fast\n segmentCount: videoSegmentDurationsMs.length,\n scrubSegmentCount: scrubSegmentDurationsMs.length,\n },\n };\n\n return manifest;\n}\n\n/**\n * Create the JIT transcode middleware for /api/v1/transcode/* routes.\n * \n * @param options - The middleware options (root, cacheRoot)\n * @param assetFunctions - The asset functions to use (allows dependency injection)\n * @returns Express-compatible middleware function\n */\nexport function createJitTranscodeMiddleware(\n options: JitMiddlewareOptions,\n assetFunctions: AssetFunctions,\n) {\n return async (\n req: IncomingMessage,\n res: ServerResponse,\n next: NextFunction,\n ) => {\n const log = debug(\"ef:vite-plugin:jit\");\n\n if (!req.url?.startsWith(\"/api/v1/transcode/\")) {\n return next();\n }\n\n forbidRelativePaths(req);\n log(`Handling JIT transcode request: ${req.url}`);\n\n const url = new URL(req.url, `http://${req.headers.host}`);\n const sourceUrl = url.searchParams.get(\"url\");\n\n if (!sourceUrl) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"url parameter is required\" }));\n return;\n }\n\n // Resolve URL to either local file path or keep as remote URL (ffprobe supports both)\n const mediaPath = resolveMediaPath(sourceUrl, options.root);\n\n // Handle CORS\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n try {\n // Parse the path to determine which endpoint\n const pathMatch = url.pathname.match(\n /^\\/api\\/v1\\/transcode\\/(?:([^/]+)\\/)?(.+)$/,\n );\n\n if (!pathMatch) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid transcode endpoint\" }));\n return;\n }\n\n const [, rendition, endpoint] = pathMatch;\n\n // Handle manifest.json endpoint\n if (endpoint === \"manifest.json\") {\n log(`Generating manifest for ${mediaPath}`);\n const baseUrl = `${url.protocol}//${url.host}`;\n\n try {\n const manifest = await generateLocalJitManifest(\n mediaPath,\n sourceUrl,\n baseUrl,\n options.cacheRoot,\n assetFunctions,\n );\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"public, max-age=300\",\n });\n res.end(JSON.stringify(manifest, null, 2));\n } catch (error) {\n log(`Error generating manifest: ${error}`);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: \"Failed to generate manifest\",\n details: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n return;\n }\n\n // Helper: Map rendition ID to track ID\n const getTrackId = (renditionId: string): number => {\n if (renditionId === \"audio\") return 2;\n if (renditionId === \"scrub\") return -1;\n return 1; // high, medium, low all use video track 1\n };\n\n // Handle init segment endpoint (e.g., /api/v1/transcode/high/init.mp4 or init.m4s)\n const initMatch = endpoint?.match(/^init\\.(mp4|m4s)$/);\n if (initMatch && rendition) {\n const extension = initMatch[1];\n const contentType = extension === \"m4s\" ? \"video/iso.segment\" : \"video/mp4\";\n log(`Serving init segment (${extension}) for ${mediaPath}, rendition: ${rendition}`);\n\n try {\n const trackId = getTrackId(rendition);\n \n // Generate/get the track file\n let trackTaskResult;\n if (trackId === -1) {\n trackTaskResult = await assetFunctions.generateScrubTrack(options.cacheRoot, mediaPath);\n } else {\n const trackUrl = `/@ef-track/${mediaPath}?trackId=${trackId}`;\n trackTaskResult = await assetFunctions.generateTrack(options.cacheRoot, mediaPath, trackUrl);\n }\n\n // Get the fragment index to find init segment byte range\n const fragmentIndexResult = await assetFunctions.generateTrackFragmentIndex(options.cacheRoot, mediaPath);\n const fragmentIndex: Record<number, TrackFragmentIndex> = JSON.parse(\n await import(\"node:fs/promises\").then((fs) => fs.readFile(fragmentIndexResult.cachePath, \"utf-8\")),\n );\n\n const track = fragmentIndex[trackId];\n if (!track) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Track ${trackId} not found` }));\n return;\n }\n\n // Stream only the init segment bytes\n const { offset, size } = track.initSegment;\n log(`Init segment: offset=${offset}, size=${size}`);\n sendByteRange(res, trackTaskResult.cachePath, offset, size, contentType);\n } catch (error) {\n log(`Error serving init segment: ${error}`);\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n res.writeHead(404, { \"Content-Type\": \"text/plain\" });\n res.end(\"File not found\");\n } else {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: \"Failed to generate init segment\",\n details: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n }\n return;\n }\n\n // Handle media segment endpoint\n // - .m4s: moof+mdat only (fragment for streaming)\n // - .mp4: init+moof+mdat (playable standalone file for testing)\n const segmentMatch = endpoint?.match(/^(\\d+)\\.(mp4|m4s)$/);\n if (segmentMatch?.[1] && segmentMatch?.[2] && rendition) {\n const segmentId = Number.parseInt(segmentMatch[1], 10);\n const extension = segmentMatch[2];\n const includeInit = extension === \"mp4\";\n log(`Serving media segment ${segmentId}.${extension} for ${mediaPath}, rendition: ${rendition}, includeInit: ${includeInit}`);\n\n try {\n const trackId = getTrackId(rendition);\n \n // Generate/get the track file\n let trackTaskResult;\n if (trackId === -1) {\n trackTaskResult = await assetFunctions.generateScrubTrack(options.cacheRoot, mediaPath);\n } else {\n const trackUrl = `/@ef-track/${mediaPath}?trackId=${trackId}`;\n trackTaskResult = await assetFunctions.generateTrack(options.cacheRoot, mediaPath, trackUrl);\n }\n\n // Get the fragment index to find segment byte range\n const fragmentIndexResult = await assetFunctions.generateTrackFragmentIndex(options.cacheRoot, mediaPath);\n const fragmentIndex: Record<number, TrackFragmentIndex> = JSON.parse(\n await import(\"node:fs/promises\").then((fs) => fs.readFile(fragmentIndexResult.cachePath, \"utf-8\")),\n );\n\n const track = fragmentIndex[trackId];\n if (!track) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Track ${trackId} not found` }));\n return;\n }\n\n // JIT uses 1-based segment IDs, fragment index uses 0-based\n const segmentIndex = segmentId - 1;\n const segment = track.segments[segmentIndex];\n if (!segment) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ \n error: `Segment ${segmentId} not found`,\n availableSegments: track.segments.length,\n }));\n return;\n }\n\n const contentType = extension === \"m4s\" ? \"video/iso.segment\" : \"video/mp4\";\n\n if (includeInit) {\n // .mp4: Stream init segment + media segment (playable file)\n const initSegment = track.initSegment;\n log(`Media segment ${segmentId}.mp4: init(offset=${initSegment.offset}, size=${initSegment.size}) + segment(offset=${segment.offset}, size=${segment.size})`);\n sendMultipleByteRanges(res, trackTaskResult.cachePath, [\n { offset: initSegment.offset, size: initSegment.size },\n { offset: segment.offset, size: segment.size },\n ], contentType);\n } else {\n // .m4s: Stream only this segment's bytes (moof+mdat)\n const { offset, size } = segment;\n log(`Media segment ${segmentId}.m4s: offset=${offset}, size=${size}`);\n sendByteRange(res, trackTaskResult.cachePath, offset, size, contentType);\n }\n } catch (error) {\n log(`Error serving media segment: ${error}`);\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n res.writeHead(404, { \"Content-Type\": \"text/plain\" });\n res.end(\"File not found\");\n } else {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: \"Failed to generate media segment\",\n details: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n }\n return;\n }\n\n // Unknown endpoint\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Unknown transcode endpoint\" }));\n } catch (error) {\n log(`Unexpected error: ${error}`);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: \"Internal server error\",\n details: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"jitTranscodeMiddleware.js","names":["fragmentIndex: Record<number, TrackFragmentIndex>"],"sources":["../src/jitTranscodeMiddleware.ts"],"sourcesContent":["import { createReadStream, statSync } from \"node:fs\";\nimport type { ServerResponse } from \"node:http\";\nimport path from \"node:path\";\n\nimport type { IncomingMessage, NextFunction } from \"connect\";\nimport debug from \"debug\";\nimport mime from \"mime\";\n\nimport { forbidRelativePaths } from \"./forbidRelativePaths.js\";\n\n/**\n * Type for the TrackFragmentIndex from assets package.\n * Duplicated here to avoid import issues between package and direct imports.\n */\nexport interface TrackFragmentIndex {\n type: \"video\" | \"audio\";\n codec: string;\n duration: number;\n timescale: number;\n startTimeOffsetMs: number;\n initSegment: { offset: number; size: number };\n segments: Array<{ offset: number; size: number; duration: number }>;\n width?: number;\n height?: number;\n channel_count?: number;\n sample_rate?: number;\n}\n\n/**\n * Asset functions required by the JIT middleware.\n * These are injected to support both package imports and direct imports.\n */\nexport interface AssetFunctions {\n generateTrack: (cacheRoot: string, absolutePath: string, trackUrl: string) => Promise<{ cachePath: string }>;\n generateScrubTrack: (cacheRoot: string, absolutePath: string) => Promise<{ cachePath: string }>;\n generateTrackFragmentIndex: (cacheRoot: string, absolutePath: string) => Promise<{ cachePath: string }>;\n}\n\nexport interface JitMiddlewareOptions {\n root: string;\n cacheRoot: string;\n}\n\n/**\n * Stream a specific byte range from a file.\n * This is used for JIT segment serving where the server extracts the correct bytes.\n */\nexport function sendByteRange(\n res: ServerResponse,\n filePath: string,\n offset: number,\n size: number,\n contentType?: string,\n) {\n const log = debug(\"ef:sendByteRange\");\n const stats = statSync(filePath);\n const end = offset + size - 1;\n\n if (end >= stats.size) {\n log(`Requested range ${offset}-${end} exceeds file size ${stats.size}`);\n res.writeHead(416, { \"Content-Range\": `bytes */${stats.size}` });\n res.end();\n return;\n }\n\n log(`Streaming bytes ${offset}-${end} (${size} bytes) from ${filePath}`);\n \n res.writeHead(200, {\n \"Content-Type\": contentType || mime.getType(filePath) || \"video/mp4\",\n \"Content-Length\": size,\n \"Cache-Control\": \"public, max-age=3600\",\n });\n\n const readStream = createReadStream(filePath, { start: offset, end });\n readStream.pipe(res);\n}\n\n/**\n * Stream multiple byte ranges from a file concatenated together.\n * Used for creating playable .mp4 files by combining init segment + media segment.\n */\nexport function sendMultipleByteRanges(\n res: ServerResponse,\n filePath: string,\n ranges: Array<{ offset: number; size: number }>,\n contentType?: string,\n) {\n const log = debug(\"ef:sendMultipleByteRanges\");\n const stats = statSync(filePath);\n \n // Validate all ranges\n for (const range of ranges) {\n const end = range.offset + range.size - 1;\n if (end >= stats.size) {\n log(`Requested range ${range.offset}-${end} exceeds file size ${stats.size}`);\n res.writeHead(416, { \"Content-Range\": `bytes */${stats.size}` });\n res.end();\n return;\n }\n }\n\n const totalSize = ranges.reduce((sum, r) => sum + r.size, 0);\n log(`Streaming ${ranges.length} ranges (${totalSize} total bytes) from ${filePath}`);\n \n res.writeHead(200, {\n \"Content-Type\": contentType || \"video/mp4\",\n \"Content-Length\": totalSize,\n \"Cache-Control\": \"public, max-age=3600\",\n });\n\n // Stream ranges sequentially\n let rangeIndex = 0;\n \n const streamNextRange = () => {\n if (rangeIndex >= ranges.length) {\n res.end();\n return;\n }\n \n const range = ranges[rangeIndex]!;\n const end = range.offset + range.size - 1;\n const readStream = createReadStream(filePath, { start: range.offset, end });\n \n readStream.on(\"end\", () => {\n rangeIndex++;\n streamNextRange();\n });\n \n readStream.on(\"error\", (err) => {\n log(`Error streaming range ${rangeIndex}: ${err}`);\n res.destroy();\n });\n \n readStream.pipe(res, { end: false });\n };\n \n streamNextRange();\n}\n\n/**\n * Check if a hostname refers to the local vite server.\n * Handles various local hostname patterns including worktree domains.\n */\nexport function isLocalHost(hostname: string): boolean {\n const localPatterns = [\n 'localhost',\n '127.0.0.1',\n '0.0.0.0',\n '.localhost', // Matches *.localhost (worktree domains like main.localhost)\n ];\n \n const lowerHost = hostname.toLowerCase();\n return localPatterns.some(pattern => \n pattern.startsWith('.') \n ? lowerHost.endsWith(pattern) || lowerHost === pattern.slice(1)\n : lowerHost === pattern || lowerHost.startsWith(pattern + ':')\n );\n}\n\n/**\n * Resolve a URL to either a remote URL (for ffprobe) or a local file path.\n * \n * - Remote URLs (different host): passed directly to ffprobe (it supports http/https)\n * - Local URLs (localhost, *.localhost): resolved to local file path\n * \n * @param urlParam - The URL from the query parameter\n * @param root - The vite plugin root directory\n * @returns The path/URL to pass to ffprobe\n */\nexport function resolveMediaPath(\n urlParam: string,\n root: string,\n): string {\n try {\n const url = new URL(urlParam);\n const hostname = url.hostname;\n \n // If NOT a local URL, pass directly to ffprobe - it supports http/https URLs\n if (!isLocalHost(hostname)) {\n return urlParam;\n }\n \n // Local URL - resolve to file path\n let filePath = decodeURIComponent(url.pathname);\n\n // Remove leading slash for path.join\n if (filePath.startsWith(\"/\")) {\n filePath = filePath.slice(1);\n }\n\n // The root is already dev-projects/src, so if the URL path starts with \"src/\",\n // we should remove it to avoid duplication (src/src/assets -> src/assets)\n if (filePath.startsWith(\"src/\")) {\n filePath = filePath.slice(4); // Remove \"src/\"\n }\n\n return path.join(root, filePath);\n } catch {\n // If not a valid URL, treat as relative path\n let filePath = urlParam;\n if (filePath.startsWith(\"src/\")) {\n filePath = filePath.slice(4);\n }\n return path.join(root, filePath);\n }\n}\n\n/**\n * Convert fragment index segments to millisecond durations array.\n */\nexport function getSegmentDurationsMs(track: TrackFragmentIndex): number[] {\n return track.segments.map(\n (segment) => (segment.duration / track.timescale) * 1000,\n );\n}\n\n/**\n * Generate a JIT manifest for a local file.\n * Uses actual fragment index data for accurate segment information.\n */\nexport async function generateLocalJitManifest(\n absolutePath: string,\n sourceUrl: string,\n baseUrl: string,\n cacheRoot: string,\n assetFunctions: AssetFunctions,\n) {\n const log = debug(\"ef:generateLocalJitManifest\");\n \n // Generate the fragment index (this also ensures tracks are generated)\n log(`Generating fragment index for ${absolutePath}`);\n const fragmentIndexResult = await assetFunctions.generateTrackFragmentIndex(cacheRoot, absolutePath);\n const fragmentIndex: Record<number, TrackFragmentIndex> = JSON.parse(\n await import(\"node:fs/promises\").then((fs) => fs.readFile(fragmentIndexResult.cachePath, \"utf-8\")),\n );\n\n // Find video track (track 1) and audio track (track 2) and scrub track (-1)\n const videoTrack = fragmentIndex[1];\n const audioTrack = fragmentIndex[2];\n const scrubTrack = fragmentIndex[-1];\n\n const hasVideo = videoTrack?.type === \"video\";\n const hasAudio = audioTrack?.type === \"audio\";\n\n // Get duration from the longest track\n let durationMs = 0;\n if (hasVideo && videoTrack) {\n durationMs = Math.max(durationMs, (videoTrack.duration / videoTrack.timescale) * 1000);\n }\n if (hasAudio && audioTrack) {\n durationMs = Math.max(durationMs, (audioTrack.duration / audioTrack.timescale) * 1000);\n }\n const durationSeconds = durationMs / 1000;\n\n // Get video dimensions from track\n const width = hasVideo && videoTrack && \"width\" in videoTrack ? videoTrack.width : 1920;\n const height = hasVideo && videoTrack && \"height\" in videoTrack ? videoTrack.height : 1080;\n const codec = hasVideo && videoTrack ? videoTrack.codec : \"avc1.640029\";\n\n // Get actual segment durations from fragment index\n const videoSegmentDurationsMs = hasVideo && videoTrack ? getSegmentDurationsMs(videoTrack) : [];\n const scrubSegmentDurationsMs = scrubTrack ? getSegmentDurationsMs(scrubTrack) : [];\n const audioSegmentDurationsMs = hasAudio && audioTrack ? getSegmentDurationsMs(audioTrack) : [];\n\n // Average segment duration for backward compatibility\n const avgVideoSegmentDurationMs = videoSegmentDurationsMs.length > 0\n ? videoSegmentDurationsMs.reduce((a, b) => a + b, 0) / videoSegmentDurationsMs.length\n : 2000;\n const avgScrubSegmentDurationMs = scrubSegmentDurationsMs.length > 0\n ? scrubSegmentDurationsMs.reduce((a, b) => a + b, 0) / scrubSegmentDurationsMs.length\n : 30000;\n const avgAudioSegmentDurationMs = audioSegmentDurationsMs.length > 0\n ? audioSegmentDurationsMs.reduce((a, b) => a + b, 0) / audioSegmentDurationsMs.length\n : 2000;\n\n log(`Video: ${videoSegmentDurationsMs.length} segments, Audio: ${audioSegmentDurationsMs.length} segments, Scrub: ${scrubSegmentDurationsMs.length} segments`);\n\n // Construct manifest matching ManifestResponse format\n const manifest = {\n version: \"1.0\",\n type: \"com.editframe/local-jit-manifest\",\n sourceUrl: sourceUrl,\n duration: durationSeconds,\n durationMs: durationMs,\n baseUrl: baseUrl,\n\n videoRenditions: hasVideo\n ? [\n {\n id: \"high\",\n width: width,\n height: height,\n bitrate: 5000000,\n codec: codec,\n container: \"video/mp4\",\n mimeType: `video/mp4; codecs=\"${codec}\"`,\n segmentDuration: avgVideoSegmentDurationMs / 1000,\n segmentDurationMs: avgVideoSegmentDurationMs,\n segmentDurationsMs: videoSegmentDurationsMs,\n startTimeOffsetMs: videoTrack!.startTimeOffsetMs,\n frameRate: 30,\n profile: \"High\",\n level: \"4.1\",\n },\n ...(scrubTrack\n ? [\n {\n id: \"scrub\",\n width: 320,\n height: Math.round((320 * (height ?? 1080)) / (width ?? 1920)),\n bitrate: 100000,\n codec: scrubTrack.codec,\n container: \"video/mp4\",\n mimeType: `video/mp4; codecs=\"${scrubTrack.codec}\"`,\n segmentDuration: avgScrubSegmentDurationMs / 1000,\n segmentDurationMs: avgScrubSegmentDurationMs,\n segmentDurationsMs: scrubSegmentDurationsMs,\n startTimeOffsetMs: scrubTrack.startTimeOffsetMs,\n frameRate: 15,\n profile: \"High\",\n level: \"4.1\",\n },\n ]\n : []),\n ]\n : [],\n\n audioRenditions: hasAudio && audioTrack\n ? [\n {\n id: \"audio\",\n channels: \"channel_count\" in audioTrack ? audioTrack.channel_count : 2,\n sampleRate: \"sample_rate\" in audioTrack ? audioTrack.sample_rate : 48000,\n bitrate: 128000,\n codec: audioTrack.codec,\n container: \"audio/mp4\",\n mimeType: `audio/mp4; codecs=\"${audioTrack.codec}\"`,\n segmentDuration: avgAudioSegmentDurationMs / 1000,\n segmentDurationMs: avgAudioSegmentDurationMs,\n segmentDurationsMs: audioSegmentDurationsMs,\n language: \"en\",\n },\n ]\n : [],\n\n endpoints: {\n initSegment: `${baseUrl}/api/v1/transcode/{rendition}/init.mp4?url=${encodeURIComponent(sourceUrl)}`,\n mediaSegment: `${baseUrl}/api/v1/transcode/{rendition}/{segmentId}.mp4?url=${encodeURIComponent(sourceUrl)}`,\n },\n\n jitInfo: {\n parallelTranscodingSupported: true,\n expectedTranscodeLatency: 100, // Local is fast\n segmentCount: videoSegmentDurationsMs.length,\n scrubSegmentCount: scrubSegmentDurationsMs.length,\n },\n };\n\n return manifest;\n}\n\n/**\n * Create the JIT transcode middleware for /api/v1/transcode/* routes.\n * \n * @param options - The middleware options (root, cacheRoot)\n * @param assetFunctions - The asset functions to use (allows dependency injection)\n * @returns Express-compatible middleware function\n */\nexport function createJitTranscodeMiddleware(\n options: JitMiddlewareOptions,\n assetFunctions: AssetFunctions,\n) {\n return async (\n req: IncomingMessage,\n res: ServerResponse,\n next: NextFunction,\n ) => {\n const log = debug(\"ef:vite-plugin:jit\");\n\n if (!req.url?.startsWith(\"/api/v1/transcode/\")) {\n return next();\n }\n\n forbidRelativePaths(req);\n log(`Handling JIT transcode request: ${req.url}`);\n\n const url = new URL(req.url, `http://${req.headers.host}`);\n const sourceUrl = url.searchParams.get(\"url\");\n\n if (!sourceUrl) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"url parameter is required\" }));\n return;\n }\n\n // Resolve URL to either local file path or keep as remote URL (ffprobe supports both)\n const mediaPath = resolveMediaPath(sourceUrl, options.root);\n\n // Handle CORS\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n try {\n // Parse the path to determine which endpoint\n const pathMatch = url.pathname.match(\n /^\\/api\\/v1\\/transcode\\/(?:([^/]+)\\/)?(.+)$/,\n );\n\n if (!pathMatch) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Invalid transcode endpoint\" }));\n return;\n }\n\n const [, rendition, endpoint] = pathMatch;\n\n // Handle manifest.json endpoint\n if (endpoint === \"manifest.json\") {\n log(`Generating manifest for ${mediaPath}`);\n const baseUrl = `${url.protocol}//${url.host}`;\n\n try {\n const manifest = await generateLocalJitManifest(\n mediaPath,\n sourceUrl,\n baseUrl,\n options.cacheRoot,\n assetFunctions,\n );\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"public, max-age=300\",\n });\n res.end(JSON.stringify(manifest, null, 2));\n } catch (error) {\n log(`Error generating manifest: ${error}`);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: \"Failed to generate manifest\",\n details: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n return;\n }\n\n // Get fragment index first - we need it to determine track IDs\n const fragmentIndexResult = await assetFunctions.generateTrackFragmentIndex(options.cacheRoot, mediaPath);\n const fragmentIndex: Record<number, TrackFragmentIndex> = JSON.parse(\n await import(\"node:fs/promises\").then((fs) => fs.readFile(fragmentIndexResult.cachePath, \"utf-8\")),\n );\n\n // Helper: Map rendition ID to track ID, using fragment index to find correct track\n // For video files: track 1 = video, track 2 = audio\n // For audio-only files: track 1 = audio (no track 2)\n const getTrackId = (renditionId: string): number => {\n if (renditionId === \"scrub\") return -1;\n \n if (renditionId === \"audio\") {\n // Find the audio track - could be track 1 (audio-only) or track 2 (video+audio)\n for (const [trackIdStr, trackInfo] of Object.entries(fragmentIndex)) {\n if (trackInfo.type === \"audio\") {\n return Number.parseInt(trackIdStr, 10);\n }\n }\n // Fallback to track 2 if no audio track found by type\n return 2;\n }\n \n // For video renditions (high, medium, low), find the video track\n for (const [trackIdStr, trackInfo] of Object.entries(fragmentIndex)) {\n if (trackInfo.type === \"video\") {\n return Number.parseInt(trackIdStr, 10);\n }\n }\n // Fallback to track 1\n return 1;\n };\n\n // Handle init segment endpoint (e.g., /api/v1/transcode/high/init.mp4 or init.m4s)\n const initMatch = endpoint?.match(/^init\\.(mp4|m4s)$/);\n if (initMatch && rendition) {\n const extension = initMatch[1];\n const contentType = extension === \"m4s\" ? \"video/iso.segment\" : \"video/mp4\";\n log(`Serving init segment (${extension}) for ${mediaPath}, rendition: ${rendition}`);\n\n try {\n const trackId = getTrackId(rendition);\n \n // Generate/get the track file\n let trackTaskResult;\n if (trackId === -1) {\n trackTaskResult = await assetFunctions.generateScrubTrack(options.cacheRoot, mediaPath);\n } else {\n const trackUrl = `/@ef-track/${mediaPath}?trackId=${trackId}`;\n trackTaskResult = await assetFunctions.generateTrack(options.cacheRoot, mediaPath, trackUrl);\n }\n\n const track = fragmentIndex[trackId];\n if (!track) {\n const validTracks = Object.keys(fragmentIndex).join(\", \");\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Track ${trackId} not found (valid tracks: ${validTracks})` }));\n return;\n }\n\n // Stream only the init segment bytes\n const { offset, size } = track.initSegment;\n log(`Init segment: offset=${offset}, size=${size}`);\n sendByteRange(res, trackTaskResult.cachePath, offset, size, contentType);\n } catch (error) {\n log(`Error serving init segment: ${error}`);\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n res.writeHead(404, { \"Content-Type\": \"text/plain\" });\n res.end(\"File not found\");\n } else {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: \"Failed to generate init segment\",\n details: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n }\n return;\n }\n\n // Handle media segment endpoint\n // - .m4s: moof+mdat only (fragment for streaming)\n // - .mp4: init+moof+mdat (playable standalone file for testing)\n const segmentMatch = endpoint?.match(/^(\\d+)\\.(mp4|m4s)$/);\n if (segmentMatch?.[1] && segmentMatch?.[2] && rendition) {\n const segmentId = Number.parseInt(segmentMatch[1], 10);\n const extension = segmentMatch[2];\n const includeInit = extension === \"mp4\";\n log(`Serving media segment ${segmentId}.${extension} for ${mediaPath}, rendition: ${rendition}, includeInit: ${includeInit}`);\n\n try {\n const trackId = getTrackId(rendition);\n \n // Generate/get the track file\n let trackTaskResult;\n if (trackId === -1) {\n trackTaskResult = await assetFunctions.generateScrubTrack(options.cacheRoot, mediaPath);\n } else {\n const trackUrl = `/@ef-track/${mediaPath}?trackId=${trackId}`;\n trackTaskResult = await assetFunctions.generateTrack(options.cacheRoot, mediaPath, trackUrl);\n }\n\n const track = fragmentIndex[trackId];\n if (!track) {\n const validTracks = Object.keys(fragmentIndex).join(\", \");\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: `Track ${trackId} not found (valid tracks: ${validTracks})` }));\n return;\n }\n\n // JIT uses 1-based segment IDs, fragment index uses 0-based\n const segmentIndex = segmentId - 1;\n const segment = track.segments[segmentIndex];\n if (!segment) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ \n error: `Segment ${segmentId} not found`,\n availableSegments: track.segments.length,\n }));\n return;\n }\n\n const contentType = extension === \"m4s\" ? \"video/iso.segment\" : \"video/mp4\";\n\n if (includeInit) {\n // .mp4: Stream init segment + media segment (playable file)\n const initSegment = track.initSegment;\n log(`Media segment ${segmentId}.mp4: init(offset=${initSegment.offset}, size=${initSegment.size}) + segment(offset=${segment.offset}, size=${segment.size})`);\n sendMultipleByteRanges(res, trackTaskResult.cachePath, [\n { offset: initSegment.offset, size: initSegment.size },\n { offset: segment.offset, size: segment.size },\n ], contentType);\n } else {\n // .m4s: Stream only this segment's bytes (moof+mdat)\n const { offset, size } = segment;\n log(`Media segment ${segmentId}.m4s: offset=${offset}, size=${size}`);\n sendByteRange(res, trackTaskResult.cachePath, offset, size, contentType);\n }\n } catch (error) {\n log(`Error serving media segment: ${error}`);\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n res.writeHead(404, { \"Content-Type\": \"text/plain\" });\n res.end(\"File not found\");\n } else {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: \"Failed to generate media segment\",\n details: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n }\n return;\n }\n\n // Unknown endpoint\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Unknown transcode endpoint\" }));\n } catch (error) {\n log(`Unexpected error: ${error}`);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: \"Internal server error\",\n details: error instanceof Error ? error.message : String(error),\n }),\n );\n }\n };\n}\n"],"mappings":";;;;;;;;;;;AA+CA,SAAgB,cACd,KACA,UACA,QACA,MACA,aACA;CACA,MAAM,MAAM,MAAM,mBAAmB;CACrC,MAAM,QAAQ,SAAS,SAAS;CAChC,MAAM,MAAM,SAAS,OAAO;AAE5B,KAAI,OAAO,MAAM,MAAM;AACrB,MAAI,mBAAmB,OAAO,GAAG,IAAI,qBAAqB,MAAM,OAAO;AACvE,MAAI,UAAU,KAAK,EAAE,iBAAiB,WAAW,MAAM,QAAQ,CAAC;AAChE,MAAI,KAAK;AACT;;AAGF,KAAI,mBAAmB,OAAO,GAAG,IAAI,IAAI,KAAK,eAAe,WAAW;AAExE,KAAI,UAAU,KAAK;EACjB,gBAAgB,eAAe,KAAK,QAAQ,SAAS,IAAI;EACzD,kBAAkB;EAClB,iBAAiB;EAClB,CAAC;AAGF,CADmB,iBAAiB,UAAU;EAAE,OAAO;EAAQ;EAAK,CAAC,CAC1D,KAAK,IAAI;;;;;;AAOtB,SAAgB,uBACd,KACA,UACA,QACA,aACA;CACA,MAAM,MAAM,MAAM,4BAA4B;CAC9C,MAAM,QAAQ,SAAS,SAAS;AAGhC,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,MAAM,MAAM,SAAS,MAAM,OAAO;AACxC,MAAI,OAAO,MAAM,MAAM;AACrB,OAAI,mBAAmB,MAAM,OAAO,GAAG,IAAI,qBAAqB,MAAM,OAAO;AAC7E,OAAI,UAAU,KAAK,EAAE,iBAAiB,WAAW,MAAM,QAAQ,CAAC;AAChE,OAAI,KAAK;AACT;;;CAIJ,MAAM,YAAY,OAAO,QAAQ,KAAK,MAAM,MAAM,EAAE,MAAM,EAAE;AAC5D,KAAI,aAAa,OAAO,OAAO,WAAW,UAAU,qBAAqB,WAAW;AAEpF,KAAI,UAAU,KAAK;EACjB,gBAAgB,eAAe;EAC/B,kBAAkB;EAClB,iBAAiB;EAClB,CAAC;CAGF,IAAI,aAAa;CAEjB,MAAM,wBAAwB;AAC5B,MAAI,cAAc,OAAO,QAAQ;AAC/B,OAAI,KAAK;AACT;;EAGF,MAAM,QAAQ,OAAO;EACrB,MAAM,MAAM,MAAM,SAAS,MAAM,OAAO;EACxC,MAAM,aAAa,iBAAiB,UAAU;GAAE,OAAO,MAAM;GAAQ;GAAK,CAAC;AAE3E,aAAW,GAAG,aAAa;AACzB;AACA,oBAAiB;IACjB;AAEF,aAAW,GAAG,UAAU,QAAQ;AAC9B,OAAI,yBAAyB,WAAW,IAAI,MAAM;AAClD,OAAI,SAAS;IACb;AAEF,aAAW,KAAK,KAAK,EAAE,KAAK,OAAO,CAAC;;AAGtC,kBAAiB;;;;;;AAOnB,SAAgB,YAAY,UAA2B;CACrD,MAAM,gBAAgB;EACpB;EACA;EACA;EACA;EACD;CAED,MAAM,YAAY,SAAS,aAAa;AACxC,QAAO,cAAc,MAAK,YACxB,QAAQ,WAAW,IAAI,GACnB,UAAU,SAAS,QAAQ,IAAI,cAAc,QAAQ,MAAM,EAAE,GAC7D,cAAc,WAAW,UAAU,WAAW,UAAU,IAAI,CACjE;;;;;;;;;;;;AAaH,SAAgB,iBACd,UACA,MACQ;AACR,KAAI;EACF,MAAM,MAAM,IAAI,IAAI,SAAS;EAC7B,MAAM,WAAW,IAAI;AAGrB,MAAI,CAAC,YAAY,SAAS,CACxB,QAAO;EAIT,IAAI,WAAW,mBAAmB,IAAI,SAAS;AAG/C,MAAI,SAAS,WAAW,IAAI,CAC1B,YAAW,SAAS,MAAM,EAAE;AAK9B,MAAI,SAAS,WAAW,OAAO,CAC7B,YAAW,SAAS,MAAM,EAAE;AAG9B,SAAO,KAAK,KAAK,MAAM,SAAS;SAC1B;EAEN,IAAI,WAAW;AACf,MAAI,SAAS,WAAW,OAAO,CAC7B,YAAW,SAAS,MAAM,EAAE;AAE9B,SAAO,KAAK,KAAK,MAAM,SAAS;;;;;;AAOpC,SAAgB,sBAAsB,OAAqC;AACzE,QAAO,MAAM,SAAS,KACnB,YAAa,QAAQ,WAAW,MAAM,YAAa,IACrD;;;;;;AAOH,eAAsB,yBACpB,cACA,WACA,SACA,WACA,gBACA;CACA,MAAM,MAAM,MAAM,8BAA8B;AAGhD,KAAI,iCAAiC,eAAe;CACpD,MAAM,sBAAsB,MAAM,eAAe,2BAA2B,WAAW,aAAa;CACpG,MAAMA,gBAAoD,KAAK,MAC7D,MAAM,OAAO,oBAAoB,MAAM,OAAO,GAAG,SAAS,oBAAoB,WAAW,QAAQ,CAAC,CACnG;CAGD,MAAM,aAAa,cAAc;CACjC,MAAM,aAAa,cAAc;CACjC,MAAM,aAAa,cAAc;CAEjC,MAAM,WAAW,YAAY,SAAS;CACtC,MAAM,WAAW,YAAY,SAAS;CAGtC,IAAI,aAAa;AACjB,KAAI,YAAY,WACd,cAAa,KAAK,IAAI,YAAa,WAAW,WAAW,WAAW,YAAa,IAAK;AAExF,KAAI,YAAY,WACd,cAAa,KAAK,IAAI,YAAa,WAAW,WAAW,WAAW,YAAa,IAAK;CAExF,MAAM,kBAAkB,aAAa;CAGrC,MAAM,QAAQ,YAAY,cAAc,WAAW,aAAa,WAAW,QAAQ;CACnF,MAAM,SAAS,YAAY,cAAc,YAAY,aAAa,WAAW,SAAS;CACtF,MAAM,QAAQ,YAAY,aAAa,WAAW,QAAQ;CAG1D,MAAM,0BAA0B,YAAY,aAAa,sBAAsB,WAAW,GAAG,EAAE;CAC/F,MAAM,0BAA0B,aAAa,sBAAsB,WAAW,GAAG,EAAE;CACnF,MAAM,0BAA0B,YAAY,aAAa,sBAAsB,WAAW,GAAG,EAAE;CAG/F,MAAM,4BAA4B,wBAAwB,SAAS,IAC/D,wBAAwB,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,wBAAwB,SAC7E;CACJ,MAAM,4BAA4B,wBAAwB,SAAS,IAC/D,wBAAwB,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,wBAAwB,SAC7E;CACJ,MAAM,4BAA4B,wBAAwB,SAAS,IAC/D,wBAAwB,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,wBAAwB,SAC7E;AAEJ,KAAI,UAAU,wBAAwB,OAAO,oBAAoB,wBAAwB,OAAO,oBAAoB,wBAAwB,OAAO,WAAW;AAmF9J,QAhFiB;EACf,SAAS;EACT,MAAM;EACK;EACX,UAAU;EACE;EACH;EAET,iBAAiB,WACb,CACE;GACE,IAAI;GACG;GACC;GACR,SAAS;GACF;GACP,WAAW;GACX,UAAU,sBAAsB,MAAM;GACtC,iBAAiB,4BAA4B;GAC7C,mBAAmB;GACnB,oBAAoB;GACpB,mBAAmB,WAAY;GAC/B,WAAW;GACX,SAAS;GACT,OAAO;GACR,EACD,GAAI,aACA,CACE;GACE,IAAI;GACJ,OAAO;GACP,QAAQ,KAAK,MAAO,OAAO,UAAU,SAAU,SAAS,MAAM;GAC9D,SAAS;GACT,OAAO,WAAW;GAClB,WAAW;GACX,UAAU,sBAAsB,WAAW,MAAM;GACjD,iBAAiB,4BAA4B;GAC7C,mBAAmB;GACnB,oBAAoB;GACpB,mBAAmB,WAAW;GAC9B,WAAW;GACX,SAAS;GACT,OAAO;GACR,CACF,GACD,EAAE,CACP,GACD,EAAE;EAEN,iBAAiB,YAAY,aACzB,CACE;GACE,IAAI;GACJ,UAAU,mBAAmB,aAAa,WAAW,gBAAgB;GACrE,YAAY,iBAAiB,aAAa,WAAW,cAAc;GACnE,SAAS;GACT,OAAO,WAAW;GAClB,WAAW;GACX,UAAU,sBAAsB,WAAW,MAAM;GACjD,iBAAiB,4BAA4B;GAC7C,mBAAmB;GACnB,oBAAoB;GACpB,UAAU;GACX,CACF,GACD,EAAE;EAEN,WAAW;GACT,aAAa,GAAG,QAAQ,6CAA6C,mBAAmB,UAAU;GAClG,cAAc,GAAG,QAAQ,oDAAoD,mBAAmB,UAAU;GAC3G;EAED,SAAS;GACP,8BAA8B;GAC9B,0BAA0B;GAC1B,cAAc,wBAAwB;GACtC,mBAAmB,wBAAwB;GAC5C;EACF;;;;;;;;;AAYH,SAAgB,6BACd,SACA,gBACA;AACA,QAAO,OACL,KACA,KACA,SACG;EACH,MAAM,MAAM,MAAM,qBAAqB;AAEvC,MAAI,CAAC,IAAI,KAAK,WAAW,qBAAqB,CAC5C,QAAO,MAAM;AAGf,sBAAoB,IAAI;AACxB,MAAI,mCAAmC,IAAI,MAAM;EAEjD,MAAM,MAAM,IAAI,IAAI,IAAI,KAAK,UAAU,IAAI,QAAQ,OAAO;EAC1D,MAAM,YAAY,IAAI,aAAa,IAAI,MAAM;AAE7C,MAAI,CAAC,WAAW;AACd,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,EAAE,OAAO,6BAA6B,CAAC,CAAC;AAC/D;;EAIF,MAAM,YAAY,iBAAiB,WAAW,QAAQ,KAAK;AAG3D,MAAI,UAAU,+BAA+B,IAAI;AACjD,MAAI,UAAU,gCAAgC,eAAe;AAC7D,MAAI,UAAU,gCAAgC,8BAA8B;AAE5E,MAAI,IAAI,WAAW,WAAW;AAC5B,OAAI,UAAU,IAAI;AAClB,OAAI,KAAK;AACT;;AAGF,MAAI;GAEF,MAAM,YAAY,IAAI,SAAS,MAC7B,6CACD;AAED,OAAI,CAAC,WAAW;AACd,QAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,QAAI,IAAI,KAAK,UAAU,EAAE,OAAO,8BAA8B,CAAC,CAAC;AAChE;;GAGF,MAAM,GAAG,WAAW,YAAY;AAGhC,OAAI,aAAa,iBAAiB;AAChC,QAAI,2BAA2B,YAAY;IAC3C,MAAM,UAAU,GAAG,IAAI,SAAS,IAAI,IAAI;AAExC,QAAI;KACF,MAAM,WAAW,MAAM,yBACrB,WACA,WACA,SACA,QAAQ,WACR,eACD;AAED,SAAI,UAAU,KAAK;MACjB,gBAAgB;MAChB,iBAAiB;MAClB,CAAC;AACF,SAAI,IAAI,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC;aACnC,OAAO;AACd,SAAI,8BAA8B,QAAQ;AAC1C,SAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,SAAI,IACF,KAAK,UAAU;MACb,OAAO;MACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;MAChE,CAAC,CACH;;AAEH;;GAIF,MAAM,sBAAsB,MAAM,eAAe,2BAA2B,QAAQ,WAAW,UAAU;GACzG,MAAMA,gBAAoD,KAAK,MAC7D,MAAM,OAAO,oBAAoB,MAAM,OAAO,GAAG,SAAS,oBAAoB,WAAW,QAAQ,CAAC,CACnG;GAKD,MAAM,cAAc,gBAAgC;AAClD,QAAI,gBAAgB,QAAS,QAAO;AAEpC,QAAI,gBAAgB,SAAS;AAE3B,UAAK,MAAM,CAAC,YAAY,cAAc,OAAO,QAAQ,cAAc,CACjE,KAAI,UAAU,SAAS,QACrB,QAAO,OAAO,SAAS,YAAY,GAAG;AAI1C,YAAO;;AAIT,SAAK,MAAM,CAAC,YAAY,cAAc,OAAO,QAAQ,cAAc,CACjE,KAAI,UAAU,SAAS,QACrB,QAAO,OAAO,SAAS,YAAY,GAAG;AAI1C,WAAO;;GAIT,MAAM,YAAY,UAAU,MAAM,oBAAoB;AACtD,OAAI,aAAa,WAAW;IAC1B,MAAM,YAAY,UAAU;IAC5B,MAAM,cAAc,cAAc,QAAQ,sBAAsB;AAChE,QAAI,yBAAyB,UAAU,QAAQ,UAAU,eAAe,YAAY;AAEpF,QAAI;KACF,MAAM,UAAU,WAAW,UAAU;KAGrC,IAAI;AACJ,SAAI,YAAY,GACd,mBAAkB,MAAM,eAAe,mBAAmB,QAAQ,WAAW,UAAU;UAClF;MACL,MAAM,WAAW,cAAc,UAAU,WAAW;AACpD,wBAAkB,MAAM,eAAe,cAAc,QAAQ,WAAW,WAAW,SAAS;;KAG9F,MAAM,QAAQ,cAAc;AAC5B,SAAI,CAAC,OAAO;MACV,MAAM,cAAc,OAAO,KAAK,cAAc,CAAC,KAAK,KAAK;AACzD,UAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,SAAS,QAAQ,4BAA4B,YAAY,IAAI,CAAC,CAAC;AAC/F;;KAIF,MAAM,EAAE,QAAQ,SAAS,MAAM;AAC/B,SAAI,wBAAwB,OAAO,SAAS,OAAO;AACnD,mBAAc,KAAK,gBAAgB,WAAW,QAAQ,MAAM,YAAY;aACjE,OAAO;AACd,SAAI,+BAA+B,QAAQ;AAC3C,SAAK,MAAgC,SAAS,UAAU;AACtD,UAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,UAAI,IAAI,iBAAiB;YACpB;AACL,UAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,UAAI,IACF,KAAK,UAAU;OACb,OAAO;OACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;OAChE,CAAC,CACH;;;AAGL;;GAMF,MAAM,eAAe,UAAU,MAAM,qBAAqB;AAC1D,OAAI,eAAe,MAAM,eAAe,MAAM,WAAW;IACvD,MAAM,YAAY,OAAO,SAAS,aAAa,IAAI,GAAG;IACtD,MAAM,YAAY,aAAa;IAC/B,MAAM,cAAc,cAAc;AAClC,QAAI,yBAAyB,UAAU,GAAG,UAAU,OAAO,UAAU,eAAe,UAAU,iBAAiB,cAAc;AAE7H,QAAI;KACF,MAAM,UAAU,WAAW,UAAU;KAGrC,IAAI;AACJ,SAAI,YAAY,GACd,mBAAkB,MAAM,eAAe,mBAAmB,QAAQ,WAAW,UAAU;UAClF;MACL,MAAM,WAAW,cAAc,UAAU,WAAW;AACpD,wBAAkB,MAAM,eAAe,cAAc,QAAQ,WAAW,WAAW,SAAS;;KAG9F,MAAM,QAAQ,cAAc;AAC5B,SAAI,CAAC,OAAO;MACV,MAAM,cAAc,OAAO,KAAK,cAAc,CAAC,KAAK,KAAK;AACzD,UAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,SAAS,QAAQ,4BAA4B,YAAY,IAAI,CAAC,CAAC;AAC/F;;KAIF,MAAM,eAAe,YAAY;KACjC,MAAM,UAAU,MAAM,SAAS;AAC/B,SAAI,CAAC,SAAS;AACZ,UAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,UAAI,IAAI,KAAK,UAAU;OACrB,OAAO,WAAW,UAAU;OAC5B,mBAAmB,MAAM,SAAS;OACnC,CAAC,CAAC;AACH;;KAGF,MAAM,cAAc,cAAc,QAAQ,sBAAsB;AAEhE,SAAI,aAAa;MAEf,MAAM,cAAc,MAAM;AAC1B,UAAI,iBAAiB,UAAU,oBAAoB,YAAY,OAAO,SAAS,YAAY,KAAK,qBAAqB,QAAQ,OAAO,SAAS,QAAQ,KAAK,GAAG;AAC7J,6BAAuB,KAAK,gBAAgB,WAAW,CACrD;OAAE,QAAQ,YAAY;OAAQ,MAAM,YAAY;OAAM,EACtD;OAAE,QAAQ,QAAQ;OAAQ,MAAM,QAAQ;OAAM,CAC/C,EAAE,YAAY;YACV;MAEL,MAAM,EAAE,QAAQ,SAAS;AACzB,UAAI,iBAAiB,UAAU,eAAe,OAAO,SAAS,OAAO;AACrE,oBAAc,KAAK,gBAAgB,WAAW,QAAQ,MAAM,YAAY;;aAEnE,OAAO;AACd,SAAI,gCAAgC,QAAQ;AAC5C,SAAK,MAAgC,SAAS,UAAU;AACtD,UAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,UAAI,IAAI,iBAAiB;YACpB;AACL,UAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,UAAI,IACF,KAAK,UAAU;OACb,OAAO;OACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;OAChE,CAAC,CACH;;;AAGL;;AAIF,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU,EAAE,OAAO,8BAA8B,CAAC,CAAC;WACzD,OAAO;AACd,OAAI,qBAAqB,QAAQ;AACjC,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IACF,KAAK,UAAU;IACb,OAAO;IACP,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAChE,CAAC,CACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editframe/vite-plugin",
3
- "version": "0.31.1-beta.0",
3
+ "version": "0.31.2-beta.0",
4
4
  "description": "Editframe vite plugin",
5
5
  "exports": {
6
6
  ".": {
@@ -20,8 +20,8 @@
20
20
  "author": "",
21
21
  "license": "UNLICENSED",
22
22
  "dependencies": {
23
- "@editframe/api": "0.31.1-beta.0",
24
- "@editframe/assets": "0.31.1-beta.0",
23
+ "@editframe/api": "0.31.2-beta.0",
24
+ "@editframe/assets": "0.31.2-beta.0",
25
25
  "connect": "^3.7.0",
26
26
  "debug": "^4.3.5",
27
27
  "mime": "^4.0.3",