@editframe/vite-plugin 0.30.2-beta.0 → 0.31.0-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.
- package/dist/index.js +156 -12
- package/dist/index.js.map +1 -1
- package/dist/sendTaskResult.js +15 -7
- package/dist/sendTaskResult.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -3,10 +3,11 @@ import { sendTaskResult } from "./sendTaskResult.js";
|
|
|
3
3
|
import { rm } from "node:fs/promises";
|
|
4
4
|
import path, { join } from "node:path";
|
|
5
5
|
import { Client, createURLToken } from "@editframe/api";
|
|
6
|
-
import { cacheImage, findOrCreateCaptions, generateTrack, generateTrackFragmentIndex, md5FilePath } from "@editframe/assets";
|
|
6
|
+
import { cacheImage, findOrCreateCaptions, generateScrubTrack, generateTrack, generateTrackFragmentIndex, md5FilePath } from "@editframe/assets";
|
|
7
7
|
import debug from "debug";
|
|
8
8
|
|
|
9
9
|
//#region src/index.ts
|
|
10
|
+
const md5ToFilePathMap = /* @__PURE__ */ new Map();
|
|
10
11
|
const getEditframeClient = () => {
|
|
11
12
|
const token = process.env.EF_TOKEN;
|
|
12
13
|
const efHost = process.env.EF_HOST;
|
|
@@ -17,47 +18,190 @@ const vitePluginEditframe = (options) => {
|
|
|
17
18
|
return {
|
|
18
19
|
name: "vite-plugin-editframe",
|
|
19
20
|
configureServer(server) {
|
|
21
|
+
server.middlewares.use(async (req, res, next) => {
|
|
22
|
+
const log = debug("ef:vite-plugin");
|
|
23
|
+
const reqUrl = req.url || "";
|
|
24
|
+
if (!reqUrl.startsWith("/api/v1/assets/local/")) return next();
|
|
25
|
+
const url = new URL(reqUrl, `http://${req.headers.host}`);
|
|
26
|
+
const urlPath = url.pathname;
|
|
27
|
+
const src = url.searchParams.get("src");
|
|
28
|
+
if (!src) {
|
|
29
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
30
|
+
res.end(JSON.stringify({ error: "src parameter is required" }));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const absolutePath = src.startsWith("http") ? src : path.join(options.root, src).replace("dist/", "src/");
|
|
34
|
+
log(`Handling local assets API: ${urlPath} for ${absolutePath}`);
|
|
35
|
+
try {
|
|
36
|
+
if (urlPath === "/api/v1/assets/local/captions") {
|
|
37
|
+
log(`Serving captions for ${absolutePath}`);
|
|
38
|
+
sendTaskResult(req, res, await findOrCreateCaptions(options.cacheRoot, absolutePath));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (urlPath === "/api/v1/assets/local/image") {
|
|
42
|
+
log(`Serving image for ${absolutePath}`);
|
|
43
|
+
sendTaskResult(req, res, await cacheImage(options.cacheRoot, absolutePath));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
47
|
+
res.end(JSON.stringify({ error: "Unknown assets endpoint" }));
|
|
48
|
+
} catch (error) {
|
|
49
|
+
log(`Error handling assets request: ${error}`);
|
|
50
|
+
if (error.code === "ENOENT") {
|
|
51
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
52
|
+
res.end("File not found");
|
|
53
|
+
} else {
|
|
54
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
55
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
server.middlewares.use(async (req, res, next) => {
|
|
60
|
+
const log = debug("ef:vite-plugin");
|
|
61
|
+
const reqUrl = req.url || "";
|
|
62
|
+
if (!reqUrl.startsWith("/api/v1/isobmff_files/local/")) return next();
|
|
63
|
+
const url = new URL(reqUrl, `http://${req.headers.host}`);
|
|
64
|
+
const urlPath = url.pathname;
|
|
65
|
+
const src = url.searchParams.get("src");
|
|
66
|
+
if (!src) {
|
|
67
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
68
|
+
res.end(JSON.stringify({ error: "src parameter is required" }));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const absolutePath = src.startsWith("http") ? src : path.join(options.root, src).replace("dist/", "src/");
|
|
72
|
+
log(`Handling local isobmff API: ${urlPath} for ${absolutePath}`);
|
|
73
|
+
try {
|
|
74
|
+
if (urlPath === "/api/v1/isobmff_files/local/index") {
|
|
75
|
+
log(`Serving track fragment index for ${absolutePath}`);
|
|
76
|
+
sendTaskResult(req, res, await generateTrackFragmentIndex(options.cacheRoot, absolutePath));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (urlPath === "/api/v1/isobmff_files/local/md5") {
|
|
80
|
+
log(`Getting MD5 for ${absolutePath}`);
|
|
81
|
+
try {
|
|
82
|
+
const md5 = await md5FilePath(absolutePath);
|
|
83
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
84
|
+
res.end(JSON.stringify({ md5 }));
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error.code === "ENOENT") {
|
|
87
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
88
|
+
res.end("File not found");
|
|
89
|
+
} else {
|
|
90
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
91
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (urlPath === "/api/v1/isobmff_files/local/track") {
|
|
97
|
+
const trackIdStr = url.searchParams.get("trackId");
|
|
98
|
+
const segmentIdStr = url.searchParams.get("segmentId");
|
|
99
|
+
if (!trackIdStr) {
|
|
100
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
101
|
+
res.end(JSON.stringify({ error: "trackId parameter is required" }));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const trackId = parseInt(trackIdStr, 10);
|
|
105
|
+
if (trackId === -1) {
|
|
106
|
+
log(`Serving scrub track for ${absolutePath}`);
|
|
107
|
+
sendTaskResult(req, res, await generateScrubTrack(options.cacheRoot, absolutePath));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
log(`Serving track ${trackId} segment ${segmentIdStr || "all"} for ${absolutePath}`);
|
|
111
|
+
const trackUrl = `/@ef-track/${src}?trackId=${trackId}${segmentIdStr ? `&segmentId=${segmentIdStr}` : ""}`;
|
|
112
|
+
sendTaskResult(req, res, await generateTrack(options.cacheRoot, absolutePath, trackUrl));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
116
|
+
res.end(JSON.stringify({ error: "Unknown isobmff endpoint" }));
|
|
117
|
+
} catch (error) {
|
|
118
|
+
log(`Error handling isobmff request: ${error}`);
|
|
119
|
+
if (error.code === "ENOENT") {
|
|
120
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
121
|
+
res.end("File not found");
|
|
122
|
+
} else {
|
|
123
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
124
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
20
128
|
server.middlewares.use(async (req, res, next) => {
|
|
21
129
|
const log = debug("ef:vite-plugin");
|
|
22
130
|
if (req.url?.startsWith("/@ef")) forbidRelativePaths(req);
|
|
23
131
|
else return next();
|
|
24
|
-
log(`Handling ${req.url}`);
|
|
132
|
+
log(`Handling ${req.url} at ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
25
133
|
const assetPath = req.url.replace(/^\/@ef-[^/]+\//, "").replace(/\?.*$/, "");
|
|
26
134
|
const absolutePath = assetPath.startsWith("http") ? assetPath : path.join(options.root, assetPath).replace("dist/", "src/");
|
|
27
135
|
options.cacheRoot = options.cacheRoot.replace("dist/", "src/");
|
|
28
136
|
const efPrefix = req.url.split("/")[1];
|
|
29
137
|
switch (efPrefix) {
|
|
30
|
-
case "@ef-clear-cache":
|
|
138
|
+
case "@ef-clear-cache": {
|
|
31
139
|
if (req.method !== "DELETE") {
|
|
32
140
|
res.writeHead(405, { Allow: "DELETE" });
|
|
33
141
|
res.end();
|
|
34
142
|
break;
|
|
35
143
|
}
|
|
36
144
|
log(`Clearing cache for ${options.cacheRoot}`);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
145
|
+
const cachePath = join(options.cacheRoot, ".cache");
|
|
146
|
+
const maxRetries = 3;
|
|
147
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) try {
|
|
148
|
+
await rm(cachePath, {
|
|
149
|
+
recursive: true,
|
|
150
|
+
force: true
|
|
151
|
+
});
|
|
152
|
+
break;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error.code === "ENOENT") break;
|
|
155
|
+
if (error.code === "ENOTEMPTY" && attempt < maxRetries - 1) {
|
|
156
|
+
await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
log(`Warning: Cache clear attempt ${attempt + 1} failed: ${error.message}`);
|
|
160
|
+
if (attempt === maxRetries - 1) log(`Cache clear failed after ${maxRetries} attempts, continuing anyway`);
|
|
161
|
+
}
|
|
41
162
|
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
42
163
|
res.end("Cache cleared");
|
|
43
164
|
break;
|
|
165
|
+
}
|
|
44
166
|
case "@ef-asset":
|
|
45
167
|
if (req.method !== "HEAD") {
|
|
46
168
|
res.writeHead(405, { Allow: "HEAD" });
|
|
47
169
|
res.end();
|
|
48
170
|
}
|
|
49
171
|
md5FilePath(absolutePath).then((md5) => {
|
|
172
|
+
md5ToFilePathMap.set(md5, absolutePath);
|
|
173
|
+
log(`Stored MD5 mapping: ${md5} -> ${absolutePath}`);
|
|
50
174
|
res.writeHead(200, { etag: md5 });
|
|
51
175
|
res.end();
|
|
52
176
|
}).catch(next);
|
|
53
177
|
break;
|
|
54
|
-
case "@ef-track-fragment-index":
|
|
178
|
+
case "@ef-track-fragment-index": {
|
|
179
|
+
const indexStartTime = Date.now();
|
|
55
180
|
log(`Serving track fragment index for ${absolutePath}`);
|
|
56
|
-
generateTrackFragmentIndex(options.cacheRoot, absolutePath).then((taskResult) =>
|
|
181
|
+
generateTrackFragmentIndex(options.cacheRoot, absolutePath).then((taskResult) => {
|
|
182
|
+
log(`Fragment index generated in ${Date.now() - indexStartTime}ms: ${taskResult.cachePath}`);
|
|
183
|
+
sendTaskResult(req, res, taskResult);
|
|
184
|
+
}).catch((error) => {
|
|
185
|
+
log(`Error generating fragment index after ${Date.now() - indexStartTime}ms:`, error);
|
|
186
|
+
next(error);
|
|
187
|
+
});
|
|
57
188
|
break;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
189
|
+
}
|
|
190
|
+
case "@ef-track": {
|
|
191
|
+
const trackStartTime = Date.now();
|
|
192
|
+
log(`Serving track for ${absolutePath} (cacheRoot: ${options.cacheRoot})`);
|
|
193
|
+
generateTrack(options.cacheRoot, absolutePath, req.url).then((taskResult) => {
|
|
194
|
+
log(`Track generated in ${Date.now() - trackStartTime}ms: ${taskResult.cachePath}`);
|
|
195
|
+
sendTaskResult(req, res, taskResult);
|
|
196
|
+
}).catch((error) => {
|
|
197
|
+
log(`Error generating track after ${Date.now() - trackStartTime}ms:`, error);
|
|
198
|
+
next(error);
|
|
199
|
+
});
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "@ef-scrub-track":
|
|
203
|
+
log(`Serving scrub track for ${absolutePath}`);
|
|
204
|
+
generateScrubTrack(options.cacheRoot, absolutePath).then((taskResult) => sendTaskResult(req, res, taskResult)).catch(next);
|
|
61
205
|
break;
|
|
62
206
|
case "@ef-captions":
|
|
63
207
|
log(`Serving captions for ${absolutePath}`);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { rm } from \"node:fs/promises\";\nimport path, { join } from \"node:path\";\nimport { Client, createURLToken } from \"@editframe/api\";\nimport {\n cacheImage,\n findOrCreateCaptions,\n generateTrack,\n generateTrackFragmentIndex,\n md5FilePath,\n} from \"@editframe/assets\";\nimport debug from \"debug\";\nimport type { Plugin } from \"vite\";\n\nimport { forbidRelativePaths } from \"./forbidRelativePaths.js\";\nimport { sendTaskResult } from \"./sendTaskResult.js\";\n\ninterface VitePluginEditframeOptions {\n root: string;\n cacheRoot: string;\n}\n\n// Create editframe client instance\nconst getEditframeClient = () => {\n const token = process.env.EF_TOKEN;\n const efHost = process.env.EF_HOST;\n if (!token) {\n throw new Error(\"EF_TOKEN environment variable must be set\");\n }\n return new Client(token, efHost);\n};\n\nexport const vitePluginEditframe = (options: VitePluginEditframeOptions) => {\n return {\n name: \"vite-plugin-editframe\",\n\n configureServer(server) {\n server.middlewares.use(async (req, res, next) => {\n const log = debug(\"ef:vite-plugin\");\n // Forbid relative paths in any request\n if (req.url?.startsWith(\"/@ef\")) {\n forbidRelativePaths(req);\n } else {\n return next();\n }\n\n log(`Handling ${req.url}`);\n\n const requestPath = req.url.replace(/^\\/@ef-[^/]+\\//, \"\");\n const assetPath = requestPath.replace(/\\?.*$/, \"\");\n\n const absolutePath = assetPath.startsWith(\"http\")\n ? assetPath\n : path.join(options.root, assetPath).replace(\"dist/\", \"src/\");\n\n options.cacheRoot = options.cacheRoot.replace(\"dist/\", \"src/\");\n\n const efPrefix = req.url.split(\"/\")[1];\n\n switch (efPrefix) {\n case \"@ef-clear-cache\": {\n if (req.method !== \"DELETE\") {\n res.writeHead(405, { Allow: \"DELETE\" });\n res.end();\n break;\n }\n log(`Clearing cache for ${options.cacheRoot}`);\n await rm(join(options.cacheRoot, \".cache\"), {\n recursive: true,\n force: true,\n });\n res.writeHead(200, { \"Content-Type\": \"text/plain\" });\n res.end(\"Cache cleared\");\n break;\n }\n case \"@ef-asset\": {\n if (req.method !== \"HEAD\") {\n res.writeHead(405, { Allow: \"HEAD\" });\n res.end();\n }\n md5FilePath(absolutePath)\n .then((md5) => {\n res.writeHead(200, {\n etag: md5,\n });\n res.end();\n })\n .catch(next);\n break;\n }\n case \"@ef-track-fragment-index\": {\n log(`Serving track fragment index for ${absolutePath}`);\n generateTrackFragmentIndex(options.cacheRoot, absolutePath)\n .then((taskResult) => sendTaskResult(req, res, taskResult))\n .catch(next);\n break;\n }\n case \"@ef-track\": {\n log(`Serving track for ${absolutePath}`);\n generateTrack(options.cacheRoot, absolutePath, req.url)\n .then((taskResult) => sendTaskResult(req, res, taskResult))\n .catch(next);\n break;\n }\n case \"@ef-captions\":\n log(`Serving captions for ${absolutePath}`);\n findOrCreateCaptions(options.cacheRoot, absolutePath)\n .then((taskResult) => sendTaskResult(req, res, taskResult))\n .catch(next);\n break;\n case \"@ef-image\":\n log(`Serving image file ${absolutePath}`);\n cacheImage(options.cacheRoot, absolutePath)\n .then((taskResult) => sendTaskResult(req, res, taskResult))\n .catch(next);\n break;\n case \"@ef-sign-url\": {\n if (req.method !== \"POST\") {\n res.writeHead(405, { Allow: \"POST\" });\n res.end();\n break;\n }\n\n log(\"Signing URL token\");\n\n // Parse request body\n let body = \"\";\n req.on(\"data\", (chunk) => {\n body += chunk.toString();\n });\n\n req.on(\"end\", async () => {\n try {\n const payload = JSON.parse(body);\n log(\"Token signing request payload:\", payload);\n\n // Handle both formats: { url } and { url, params }\n const { url, params } = payload;\n if (!url) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"URL is required\" }));\n return;\n }\n\n const client = getEditframeClient();\n\n // createURLToken expects just a string URL\n // For transcode URLs with params, we need to reconstruct the full URL\n let fullUrl = url;\n if (params) {\n const urlObj = new URL(url);\n // Add params as query parameters\n Object.entries(params).forEach(([key, value]) => {\n urlObj.searchParams.set(key, String(value));\n });\n fullUrl = urlObj.toString();\n }\n\n log(\"Creating token for full URL:\", fullUrl);\n const token = await createURLToken(client, fullUrl);\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ token }));\n } catch (error) {\n log(`Error signing URL token: ${error}`);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Failed to sign URL token\" }));\n }\n });\n\n break;\n }\n default:\n log(`Unknown asset type ${efPrefix}`);\n break;\n }\n });\n },\n } satisfies Plugin;\n};\n"],"mappings":";;;;;;;;;AAsBA,MAAM,2BAA2B;CAC/B,MAAM,QAAQ,QAAQ,IAAI;CAC1B,MAAM,SAAS,QAAQ,IAAI;AAC3B,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,4CAA4C;AAE9D,QAAO,IAAI,OAAO,OAAO,OAAO;;AAGlC,MAAa,uBAAuB,YAAwC;AAC1E,QAAO;EACL,MAAM;EAEN,gBAAgB,QAAQ;AACtB,UAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;IAC/C,MAAM,MAAM,MAAM,iBAAiB;AAEnC,QAAI,IAAI,KAAK,WAAW,OAAO,CAC7B,qBAAoB,IAAI;QAExB,QAAO,MAAM;AAGf,QAAI,YAAY,IAAI,MAAM;IAG1B,MAAM,YADc,IAAI,IAAI,QAAQ,kBAAkB,GAAG,CAC3B,QAAQ,SAAS,GAAG;IAElD,MAAM,eAAe,UAAU,WAAW,OAAO,GAC7C,YACA,KAAK,KAAK,QAAQ,MAAM,UAAU,CAAC,QAAQ,SAAS,OAAO;AAE/D,YAAQ,YAAY,QAAQ,UAAU,QAAQ,SAAS,OAAO;IAE9D,MAAM,WAAW,IAAI,IAAI,MAAM,IAAI,CAAC;AAEpC,YAAQ,UAAR;KACE,KAAK;AACH,UAAI,IAAI,WAAW,UAAU;AAC3B,WAAI,UAAU,KAAK,EAAE,OAAO,UAAU,CAAC;AACvC,WAAI,KAAK;AACT;;AAEF,UAAI,sBAAsB,QAAQ,YAAY;AAC9C,YAAM,GAAG,KAAK,QAAQ,WAAW,SAAS,EAAE;OAC1C,WAAW;OACX,OAAO;OACR,CAAC;AACF,UAAI,UAAU,KAAK,EAAE,gBAAgB,cAAc,CAAC;AACpD,UAAI,IAAI,gBAAgB;AACxB;KAEF,KAAK;AACH,UAAI,IAAI,WAAW,QAAQ;AACzB,WAAI,UAAU,KAAK,EAAE,OAAO,QAAQ,CAAC;AACrC,WAAI,KAAK;;AAEX,kBAAY,aAAa,CACtB,MAAM,QAAQ;AACb,WAAI,UAAU,KAAK,EACjB,MAAM,KACP,CAAC;AACF,WAAI,KAAK;QACT,CACD,MAAM,KAAK;AACd;KAEF,KAAK;AACH,UAAI,oCAAoC,eAAe;AACvD,iCAA2B,QAAQ,WAAW,aAAa,CACxD,MAAM,eAAe,eAAe,KAAK,KAAK,WAAW,CAAC,CAC1D,MAAM,KAAK;AACd;KAEF,KAAK;AACH,UAAI,qBAAqB,eAAe;AACxC,oBAAc,QAAQ,WAAW,cAAc,IAAI,IAAI,CACpD,MAAM,eAAe,eAAe,KAAK,KAAK,WAAW,CAAC,CAC1D,MAAM,KAAK;AACd;KAEF,KAAK;AACH,UAAI,wBAAwB,eAAe;AAC3C,2BAAqB,QAAQ,WAAW,aAAa,CAClD,MAAM,eAAe,eAAe,KAAK,KAAK,WAAW,CAAC,CAC1D,MAAM,KAAK;AACd;KACF,KAAK;AACH,UAAI,sBAAsB,eAAe;AACzC,iBAAW,QAAQ,WAAW,aAAa,CACxC,MAAM,eAAe,eAAe,KAAK,KAAK,WAAW,CAAC,CAC1D,MAAM,KAAK;AACd;KACF,KAAK,gBAAgB;AACnB,UAAI,IAAI,WAAW,QAAQ;AACzB,WAAI,UAAU,KAAK,EAAE,OAAO,QAAQ,CAAC;AACrC,WAAI,KAAK;AACT;;AAGF,UAAI,oBAAoB;MAGxB,IAAI,OAAO;AACX,UAAI,GAAG,SAAS,UAAU;AACxB,eAAQ,MAAM,UAAU;QACxB;AAEF,UAAI,GAAG,OAAO,YAAY;AACxB,WAAI;QACF,MAAM,UAAU,KAAK,MAAM,KAAK;AAChC,YAAI,kCAAkC,QAAQ;QAG9C,MAAM,EAAE,KAAK,WAAW;AACxB,YAAI,CAAC,KAAK;AACR,aAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,aAAI,IAAI,KAAK,UAAU,EAAE,OAAO,mBAAmB,CAAC,CAAC;AACrD;;QAGF,MAAM,SAAS,oBAAoB;QAInC,IAAI,UAAU;AACd,YAAI,QAAQ;SACV,MAAM,SAAS,IAAI,IAAI,IAAI;AAE3B,gBAAO,QAAQ,OAAO,CAAC,SAAS,CAAC,KAAK,WAAW;AAC/C,iBAAO,aAAa,IAAI,KAAK,OAAO,MAAM,CAAC;WAC3C;AACF,mBAAU,OAAO,UAAU;;AAG7B,YAAI,gCAAgC,QAAQ;QAC5C,MAAM,QAAQ,MAAM,eAAe,QAAQ,QAAQ;AAEnD,YAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,CAAC,CAAC;gBAC3B,OAAO;AACd,YAAI,4BAA4B,QAAQ;AACxC,YAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,4BAA4B,CAAC,CAAC;;QAEhE;AAEF;;KAEF;AACE,UAAI,sBAAsB,WAAW;AACrC;;KAEJ;;EAEL"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["error: any"],"sources":["../src/index.ts"],"sourcesContent":["import { rm } from \"node:fs/promises\";\nimport path, { join } from \"node:path\";\nimport { Client, createURLToken } from \"@editframe/api\";\nimport {\n cacheImage,\n findOrCreateCaptions,\n generateTrack,\n generateScrubTrack,\n generateTrackFragmentIndex,\n md5FilePath,\n} from \"@editframe/assets\";\nimport debug from \"debug\";\nimport type { Plugin } from \"vite\";\n\nimport { forbidRelativePaths } from \"./forbidRelativePaths.js\";\nimport { sendTaskResult } from \"./sendTaskResult.js\";\n\ninterface VitePluginEditframeOptions {\n root: string;\n cacheRoot: string;\n}\n\n// In-memory mapping of MD5 -> file path for API route handling\nconst md5ToFilePathMap = new Map<string, string>();\n\n// Create editframe client instance\nconst getEditframeClient = () => {\n const token = process.env.EF_TOKEN;\n const efHost = process.env.EF_HOST;\n if (!token) {\n throw new Error(\"EF_TOKEN environment variable must be set\");\n }\n return new Client(token, efHost);\n};\n\nexport const vitePluginEditframe = (options: VitePluginEditframeOptions) => {\n return {\n name: \"vite-plugin-editframe\",\n\n configureServer(server) {\n // Handle local assets API format: /api/v1/assets/local/*\n server.middlewares.use(async (req, res, next) => {\n const log = debug(\"ef:vite-plugin\");\n const reqUrl = req.url || \"\";\n \n if (!reqUrl.startsWith(\"/api/v1/assets/local/\")) {\n return next();\n }\n \n const url = new URL(reqUrl, `http://${req.headers.host}`);\n const urlPath = url.pathname;\n const src = url.searchParams.get(\"src\");\n \n if (!src) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"src parameter is required\" }));\n return;\n }\n \n // Resolve src to absolute file path\n const absolutePath = src.startsWith(\"http\")\n ? src\n : path.join(options.root, src).replace(\"dist/\", \"src/\");\n \n log(`Handling local assets API: ${urlPath} for ${absolutePath}`);\n \n try {\n // Handle /api/v1/assets/local/captions - captions/transcriptions\n if (urlPath === \"/api/v1/assets/local/captions\") {\n log(`Serving captions for ${absolutePath}`);\n const taskResult = await findOrCreateCaptions(options.cacheRoot, absolutePath);\n sendTaskResult(req, res, taskResult);\n return;\n }\n \n // Handle /api/v1/assets/local/image - cached images\n if (urlPath === \"/api/v1/assets/local/image\") {\n log(`Serving image for ${absolutePath}`);\n const taskResult = await cacheImage(options.cacheRoot, absolutePath);\n sendTaskResult(req, res, taskResult);\n return;\n }\n \n // Unknown endpoint\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Unknown assets endpoint\" }));\n } catch (error) {\n log(`Error handling assets request: ${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(JSON.stringify({ error: (error as Error).message }));\n }\n }\n });\n\n // Handle production API format for local files: /api/v1/isobmff_files/local/*\n server.middlewares.use(async (req, res, next) => {\n const log = debug(\"ef:vite-plugin\");\n const reqUrl = req.url || \"\";\n \n if (!reqUrl.startsWith(\"/api/v1/isobmff_files/local/\")) {\n return next();\n }\n \n const url = new URL(reqUrl, `http://${req.headers.host}`);\n const urlPath = url.pathname;\n const src = url.searchParams.get(\"src\");\n \n if (!src) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"src parameter is required\" }));\n return;\n }\n \n // Resolve src to absolute file path\n const absolutePath = src.startsWith(\"http\")\n ? src\n : path.join(options.root, src).replace(\"dist/\", \"src/\");\n \n log(`Handling local isobmff API: ${urlPath} for ${absolutePath}`);\n \n try {\n // Handle /api/v1/isobmff_files/local/index - fragment index\n if (urlPath === \"/api/v1/isobmff_files/local/index\") {\n log(`Serving track fragment index for ${absolutePath}`);\n const taskResult = await generateTrackFragmentIndex(options.cacheRoot, absolutePath);\n sendTaskResult(req, res, taskResult);\n return;\n }\n \n // Handle /api/v1/isobmff_files/local/md5 - get MD5 hash for a file\n if (urlPath === \"/api/v1/isobmff_files/local/md5\") {\n log(`Getting MD5 for ${absolutePath}`);\n try {\n const md5 = await md5FilePath(absolutePath);\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ md5 }));\n } catch (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(JSON.stringify({ error: (error as Error).message }));\n }\n }\n return;\n }\n \n // Handle /api/v1/isobmff_files/local/track - track segments\n if (urlPath === \"/api/v1/isobmff_files/local/track\") {\n const trackIdStr = url.searchParams.get(\"trackId\");\n const segmentIdStr = url.searchParams.get(\"segmentId\");\n \n if (!trackIdStr) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"trackId parameter is required\" }));\n return;\n }\n \n const trackId = parseInt(trackIdStr, 10);\n \n // For scrub track (trackId -1), use generateScrubTrack\n if (trackId === -1) {\n log(`Serving scrub track for ${absolutePath}`);\n const taskResult = await generateScrubTrack(options.cacheRoot, absolutePath);\n sendTaskResult(req, res, taskResult);\n return;\n }\n \n // For regular tracks, use generateTrack\n log(`Serving track ${trackId} segment ${segmentIdStr || \"all\"} for ${absolutePath}`);\n const trackUrl = `/@ef-track/${src}?trackId=${trackId}${segmentIdStr ? `&segmentId=${segmentIdStr}` : \"\"}`;\n const taskResult = await generateTrack(options.cacheRoot, absolutePath, trackUrl);\n sendTaskResult(req, res, taskResult);\n return;\n }\n \n // Unknown endpoint\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Unknown isobmff endpoint\" }));\n } catch (error) {\n log(`Error handling isobmff request: ${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(JSON.stringify({ error: (error as Error).message }));\n }\n }\n });\n \n // Handle @ef-* format (legacy)\n server.middlewares.use(async (req, res, next) => {\n const log = debug(\"ef:vite-plugin\");\n // Forbid relative paths in any request\n if (req.url?.startsWith(\"/@ef\")) {\n forbidRelativePaths(req);\n } else {\n return next();\n }\n\n log(`Handling ${req.url} at ${new Date().toISOString()}`);\n\n const requestPath = req.url.replace(/^\\/@ef-[^/]+\\//, \"\");\n const assetPath = requestPath.replace(/\\?.*$/, \"\");\n\n const absolutePath = assetPath.startsWith(\"http\")\n ? assetPath\n : path.join(options.root, assetPath).replace(\"dist/\", \"src/\");\n\n options.cacheRoot = options.cacheRoot.replace(\"dist/\", \"src/\");\n\n const efPrefix = req.url.split(\"/\")[1];\n\n switch (efPrefix) {\n case \"@ef-clear-cache\": {\n if (req.method !== \"DELETE\") {\n res.writeHead(405, { Allow: \"DELETE\" });\n res.end();\n break;\n }\n log(`Clearing cache for ${options.cacheRoot}`);\n // Retry cache clearing to handle race conditions with concurrent tests\n const cachePath = join(options.cacheRoot, \".cache\");\n const maxRetries = 3;\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n await rm(cachePath, {\n recursive: true,\n force: true,\n });\n break; // Success, exit retry loop\n } catch (error: any) {\n // ENOTEMPTY can happen if files are being written during deletion\n // ENOENT is fine - directory doesn't exist\n if (error.code === \"ENOENT\") {\n break; // Already cleared, done\n }\n if (error.code === \"ENOTEMPTY\" && attempt < maxRetries - 1) {\n // Wait a bit and retry\n await new Promise(resolve => setTimeout(resolve, 100 * (attempt + 1)));\n continue;\n }\n // Log but don't fail - cache clearing is best-effort\n log(`Warning: Cache clear attempt ${attempt + 1} failed: ${error.message}`);\n if (attempt === maxRetries - 1) {\n log(`Cache clear failed after ${maxRetries} attempts, continuing anyway`);\n }\n }\n }\n res.writeHead(200, { \"Content-Type\": \"text/plain\" });\n res.end(\"Cache cleared\");\n break;\n }\n case \"@ef-asset\": {\n if (req.method !== \"HEAD\") {\n res.writeHead(405, { Allow: \"HEAD\" });\n res.end();\n }\n md5FilePath(absolutePath)\n .then((md5) => {\n // Store mapping for API route handling\n md5ToFilePathMap.set(md5, absolutePath);\n log(`Stored MD5 mapping: ${md5} -> ${absolutePath}`);\n res.writeHead(200, {\n etag: md5,\n });\n res.end();\n })\n .catch(next);\n break;\n }\n case \"@ef-track-fragment-index\": {\n const indexStartTime = Date.now();\n log(`Serving track fragment index for ${absolutePath}`);\n generateTrackFragmentIndex(options.cacheRoot, absolutePath)\n .then((taskResult) => {\n const elapsed = Date.now() - indexStartTime;\n log(`Fragment index generated in ${elapsed}ms: ${taskResult.cachePath}`);\n sendTaskResult(req, res, taskResult);\n })\n .catch((error) => {\n const elapsed = Date.now() - indexStartTime;\n log(`Error generating fragment index after ${elapsed}ms:`, error);\n next(error);\n });\n break;\n }\n case \"@ef-track\": {\n const trackStartTime = Date.now();\n log(`Serving track for ${absolutePath} (cacheRoot: ${options.cacheRoot})`);\n generateTrack(options.cacheRoot, absolutePath, req.url)\n .then((taskResult) => {\n const elapsed = Date.now() - trackStartTime;\n log(`Track generated in ${elapsed}ms: ${taskResult.cachePath}`);\n sendTaskResult(req, res, taskResult);\n })\n .catch((error) => {\n const elapsed = Date.now() - trackStartTime;\n log(`Error generating track after ${elapsed}ms:`, error);\n next(error);\n });\n break;\n }\n case \"@ef-scrub-track\": {\n log(`Serving scrub track for ${absolutePath}`);\n generateScrubTrack(options.cacheRoot, absolutePath)\n .then((taskResult) => sendTaskResult(req, res, taskResult))\n .catch(next);\n break;\n }\n case \"@ef-captions\":\n log(`Serving captions for ${absolutePath}`);\n findOrCreateCaptions(options.cacheRoot, absolutePath)\n .then((taskResult) => sendTaskResult(req, res, taskResult))\n .catch(next);\n break;\n case \"@ef-image\":\n log(`Serving image file ${absolutePath}`);\n cacheImage(options.cacheRoot, absolutePath)\n .then((taskResult) => sendTaskResult(req, res, taskResult))\n .catch(next);\n break;\n case \"@ef-sign-url\": {\n if (req.method !== \"POST\") {\n res.writeHead(405, { Allow: \"POST\" });\n res.end();\n break;\n }\n\n log(\"Signing URL token\");\n\n // Parse request body\n let body = \"\";\n req.on(\"data\", (chunk) => {\n body += chunk.toString();\n });\n\n req.on(\"end\", async () => {\n try {\n const payload = JSON.parse(body);\n log(\"Token signing request payload:\", payload);\n\n // Handle both formats: { url } and { url, params }\n const { url, params } = payload;\n if (!url) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"URL is required\" }));\n return;\n }\n\n const client = getEditframeClient();\n\n // createURLToken expects just a string URL\n // For transcode URLs with params, we need to reconstruct the full URL\n let fullUrl = url;\n if (params) {\n const urlObj = new URL(url);\n // Add params as query parameters\n Object.entries(params).forEach(([key, value]) => {\n urlObj.searchParams.set(key, String(value));\n });\n fullUrl = urlObj.toString();\n }\n\n log(\"Creating token for full URL:\", fullUrl);\n const token = await createURLToken(client, fullUrl);\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ token }));\n } catch (error) {\n log(`Error signing URL token: ${error}`);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Failed to sign URL token\" }));\n }\n });\n\n break;\n }\n default:\n log(`Unknown asset type ${efPrefix}`);\n break;\n }\n });\n },\n } satisfies Plugin;\n};\n"],"mappings":"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"}
|
package/dist/sendTaskResult.js
CHANGED
|
@@ -8,9 +8,10 @@ const sendTaskResult = (req, res, taskResult) => {
|
|
|
8
8
|
const filePath = cachePath;
|
|
9
9
|
const headers = { etag: md5Sum };
|
|
10
10
|
const log = debug("ef:sendfile");
|
|
11
|
+
const sendStartTime = Date.now();
|
|
11
12
|
try {
|
|
12
|
-
log(`Sending file ${filePath}`);
|
|
13
13
|
const stats = statSync(filePath);
|
|
14
|
+
log(`Sending file ${filePath} (size: ${stats.size} bytes)`);
|
|
14
15
|
if (req.headers.range) {
|
|
15
16
|
const [x, y] = req.headers.range.replace("bytes=", "").split("-");
|
|
16
17
|
let end = Number.parseInt(y ?? "0", 10) || stats.size - 1;
|
|
@@ -31,10 +32,14 @@ const sendTaskResult = (req, res, taskResult) => {
|
|
|
31
32
|
"Accept-Ranges": "bytes"
|
|
32
33
|
});
|
|
33
34
|
log(`Sending ${filePath} range ${start}-${end}/${stats.size}`);
|
|
34
|
-
createReadStream(filePath, {
|
|
35
|
+
const readStream = createReadStream(filePath, {
|
|
35
36
|
start,
|
|
36
37
|
end
|
|
37
|
-
})
|
|
38
|
+
});
|
|
39
|
+
readStream.on("end", () => {
|
|
40
|
+
log(`Range request completed in ${Date.now() - sendStartTime}ms`);
|
|
41
|
+
});
|
|
42
|
+
readStream.pipe(res);
|
|
38
43
|
} else {
|
|
39
44
|
res.writeHead(200, {
|
|
40
45
|
...headers,
|
|
@@ -42,12 +47,15 @@ const sendTaskResult = (req, res, taskResult) => {
|
|
|
42
47
|
"Cache-Control": "max-age=3600",
|
|
43
48
|
"Content-Length": stats.size
|
|
44
49
|
});
|
|
45
|
-
log(`Sending ${filePath}`);
|
|
46
|
-
createReadStream(filePath)
|
|
50
|
+
log(`Sending full file ${filePath} (${stats.size} bytes)`);
|
|
51
|
+
const readStream = createReadStream(filePath);
|
|
52
|
+
readStream.on("end", () => {
|
|
53
|
+
log(`File send completed in ${Date.now() - sendStartTime}ms`);
|
|
54
|
+
});
|
|
55
|
+
readStream.pipe(res);
|
|
47
56
|
}
|
|
48
57
|
} catch (error) {
|
|
49
|
-
log(
|
|
50
|
-
console.error(error);
|
|
58
|
+
log(`Error sending file after ${Date.now() - sendStartTime}ms:`, error);
|
|
51
59
|
}
|
|
52
60
|
};
|
|
53
61
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sendTaskResult.js","names":[],"sources":["../src/sendTaskResult.ts"],"sourcesContent":["import { createReadStream, statSync } from \"node:fs\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { TaskResult } from \"@editframe/assets\";\nimport debug from \"debug\";\nimport mime from \"mime\";\n\nexport const sendTaskResult = (\n req: IncomingMessage,\n res: ServerResponse<IncomingMessage>,\n taskResult: TaskResult,\n) => {\n const { cachePath, md5Sum } = taskResult;\n const filePath = cachePath;\n const headers = {\n etag: md5Sum,\n };\n const log = debug(\"ef:sendfile\");\n try {\n log(`Sending file ${filePath}
|
|
1
|
+
{"version":3,"file":"sendTaskResult.js","names":[],"sources":["../src/sendTaskResult.ts"],"sourcesContent":["import { createReadStream, statSync } from \"node:fs\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { TaskResult } from \"@editframe/assets\";\nimport debug from \"debug\";\nimport mime from \"mime\";\n\nexport const sendTaskResult = (\n req: IncomingMessage,\n res: ServerResponse<IncomingMessage>,\n taskResult: TaskResult,\n) => {\n const { cachePath, md5Sum } = taskResult;\n const filePath = cachePath;\n const headers = {\n etag: md5Sum,\n };\n const log = debug(\"ef:sendfile\");\n const sendStartTime = Date.now();\n try {\n const stats = statSync(filePath);\n log(`Sending file ${filePath} (size: ${stats.size} bytes)`);\n\n if (req.headers.range) {\n const [x, y] = req.headers.range.replace(\"bytes=\", \"\").split(\"-\");\n let end = Number.parseInt(y ?? \"0\", 10) || stats.size - 1;\n const start = Number.parseInt(x ?? \"0\", 10) || 0;\n\n if (end >= stats.size) {\n end = stats.size - 1;\n }\n\n if (start >= stats.size) {\n log(\"Range start is greater than file size\");\n res.setHeader(\"Content-Range\", `bytes */${stats.size}`);\n res.statusCode = 416;\n return res.end();\n }\n\n res.writeHead(206, {\n ...headers,\n \"Content-Type\": mime.getType(filePath) || \"text/plain\",\n \"Cache-Control\": \"max-age=3600\",\n \"Content-Range\": `bytes ${start}-${end}/${stats.size}`,\n \"Content-Length\": end - start + 1,\n \"Accept-Ranges\": \"bytes\",\n });\n log(`Sending ${filePath} range ${start}-${end}/${stats.size}`);\n const readStream = createReadStream(filePath, { start, end });\n readStream.on(\"end\", () => {\n const elapsed = Date.now() - sendStartTime;\n log(`Range request completed in ${elapsed}ms`);\n });\n readStream.pipe(res);\n } else {\n res.writeHead(200, {\n ...headers,\n \"Content-Type\": mime.getType(filePath) || \"text/plain\",\n \"Cache-Control\": \"max-age=3600\",\n \"Content-Length\": stats.size,\n });\n log(`Sending full file ${filePath} (${stats.size} bytes)`);\n const readStream = createReadStream(filePath);\n readStream.on(\"end\", () => {\n const elapsed = Date.now() - sendStartTime;\n log(`File send completed in ${elapsed}ms`);\n });\n readStream.pipe(res);\n }\n } catch (error) {\n const elapsed = Date.now() - sendStartTime;\n log(`Error sending file after ${elapsed}ms:`, error);\n }\n};\n"],"mappings":";;;;;AAMA,MAAa,kBACX,KACA,KACA,eACG;CACH,MAAM,EAAE,WAAW,WAAW;CAC9B,MAAM,WAAW;CACjB,MAAM,UAAU,EACd,MAAM,QACP;CACD,MAAM,MAAM,MAAM,cAAc;CAChC,MAAM,gBAAgB,KAAK,KAAK;AAChC,KAAI;EACF,MAAM,QAAQ,SAAS,SAAS;AAChC,MAAI,gBAAgB,SAAS,UAAU,MAAM,KAAK,SAAS;AAE3D,MAAI,IAAI,QAAQ,OAAO;GACrB,MAAM,CAAC,GAAG,KAAK,IAAI,QAAQ,MAAM,QAAQ,UAAU,GAAG,CAAC,MAAM,IAAI;GACjE,IAAI,MAAM,OAAO,SAAS,KAAK,KAAK,GAAG,IAAI,MAAM,OAAO;GACxD,MAAM,QAAQ,OAAO,SAAS,KAAK,KAAK,GAAG,IAAI;AAE/C,OAAI,OAAO,MAAM,KACf,OAAM,MAAM,OAAO;AAGrB,OAAI,SAAS,MAAM,MAAM;AACvB,QAAI,wCAAwC;AAC5C,QAAI,UAAU,iBAAiB,WAAW,MAAM,OAAO;AACvD,QAAI,aAAa;AACjB,WAAO,IAAI,KAAK;;AAGlB,OAAI,UAAU,KAAK;IACjB,GAAG;IACH,gBAAgB,KAAK,QAAQ,SAAS,IAAI;IAC1C,iBAAiB;IACjB,iBAAiB,SAAS,MAAM,GAAG,IAAI,GAAG,MAAM;IAChD,kBAAkB,MAAM,QAAQ;IAChC,iBAAiB;IAClB,CAAC;AACF,OAAI,WAAW,SAAS,SAAS,MAAM,GAAG,IAAI,GAAG,MAAM,OAAO;GAC9D,MAAM,aAAa,iBAAiB,UAAU;IAAE;IAAO;IAAK,CAAC;AAC7D,cAAW,GAAG,aAAa;AAEzB,QAAI,8BADY,KAAK,KAAK,GAAG,cACa,IAAI;KAC9C;AACF,cAAW,KAAK,IAAI;SACf;AACL,OAAI,UAAU,KAAK;IACjB,GAAG;IACH,gBAAgB,KAAK,QAAQ,SAAS,IAAI;IAC1C,iBAAiB;IACjB,kBAAkB,MAAM;IACzB,CAAC;AACF,OAAI,qBAAqB,SAAS,IAAI,MAAM,KAAK,SAAS;GAC1D,MAAM,aAAa,iBAAiB,SAAS;AAC7C,cAAW,GAAG,aAAa;AAEzB,QAAI,0BADY,KAAK,KAAK,GAAG,cACS,IAAI;KAC1C;AACF,cAAW,KAAK,IAAI;;UAEf,OAAO;AAEd,MAAI,4BADY,KAAK,KAAK,GAAG,cACW,MAAM,MAAM"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0-beta.0",
|
|
4
4
|
"description": "Editframe vite plugin",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -20,11 +20,12 @@
|
|
|
20
20
|
"author": "",
|
|
21
21
|
"license": "UNLICENSED",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@editframe/api": "0.
|
|
24
|
-
"@editframe/assets": "0.
|
|
23
|
+
"@editframe/api": "0.31.0-beta.0",
|
|
24
|
+
"@editframe/assets": "0.31.0-beta.0",
|
|
25
25
|
"connect": "^3.7.0",
|
|
26
26
|
"debug": "^4.3.5",
|
|
27
27
|
"mime": "^4.0.3",
|
|
28
|
+
"odiff-bin": "^4.3.2",
|
|
28
29
|
"vite": "npm:rolldown-vite@^7.1.15"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|