@twick/vite-plugin 0.14.22 → 0.15.1
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.cjs +866 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +172 -0
- package/dist/index.d.ts +172 -0
- package/dist/index.js +842 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -18
- package/lib/index.d.ts +0 -3
- package/lib/index.js +0 -23
- package/lib/index.js.map +0 -1
- package/lib/main.d.ts +0 -71
- package/lib/main.js +0 -40
- package/lib/main.js.map +0 -1
- package/lib/openInExplorer.d.ts +0 -1
- package/lib/openInExplorer.js +0 -44
- package/lib/openInExplorer.js.map +0 -1
- package/lib/partials/assets.d.ts +0 -6
- package/lib/partials/assets.js +0 -50
- package/lib/partials/assets.js.map +0 -1
- package/lib/partials/editor.d.ts +0 -8
- package/lib/partials/editor.js +0 -73
- package/lib/partials/editor.js.map +0 -1
- package/lib/partials/ffmpegBridge.d.ts +0 -40
- package/lib/partials/ffmpegBridge.js +0 -195
- package/lib/partials/ffmpegBridge.js.map +0 -1
- package/lib/partials/imageExporter.d.ts +0 -6
- package/lib/partials/imageExporter.js +0 -45
- package/lib/partials/imageExporter.js.map +0 -1
- package/lib/partials/index.d.ts +0 -11
- package/lib/partials/index.js +0 -28
- package/lib/partials/index.js.map +0 -1
- package/lib/partials/meta.d.ts +0 -2
- package/lib/partials/meta.js +0 -68
- package/lib/partials/meta.js.map +0 -1
- package/lib/partials/metrics.d.ts +0 -2
- package/lib/partials/metrics.js +0 -13
- package/lib/partials/metrics.js.map +0 -1
- package/lib/partials/projects.d.ts +0 -10
- package/lib/partials/projects.js +0 -34
- package/lib/partials/projects.js.map +0 -1
- package/lib/partials/rive.d.ts +0 -2
- package/lib/partials/rive.js +0 -70
- package/lib/partials/rive.js.map +0 -1
- package/lib/partials/settings.d.ts +0 -2
- package/lib/partials/settings.js +0 -65
- package/lib/partials/settings.js.map +0 -1
- package/lib/partials/wasmExporter.d.ts +0 -2
- package/lib/partials/wasmExporter.js +0 -87
- package/lib/partials/wasmExporter.js.map +0 -1
- package/lib/partials/webgl.d.ts +0 -10
- package/lib/partials/webgl.js +0 -88
- package/lib/partials/webgl.js.map +0 -1
- package/lib/plugins.d.ts +0 -99
- package/lib/plugins.js +0 -9
- package/lib/plugins.js.map +0 -1
- package/lib/tsconfig.tsbuildinfo +0 -1
- package/lib/utils.d.ts +0 -6
- package/lib/utils.js +0 -44
- package/lib/utils.js.map +0 -1
- package/lib/versions.d.ts +0 -6
- package/lib/versions.js +0 -28
- package/lib/versions.js.map +0 -1
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
PLUGIN_OPTIONS: () => PLUGIN_OPTIONS,
|
|
34
|
+
default: () => index_default,
|
|
35
|
+
isPlugin: () => isPlugin
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
|
|
39
|
+
// src/main.ts
|
|
40
|
+
var import_path10 = __toESM(require("path"), 1);
|
|
41
|
+
|
|
42
|
+
// src/partials/assets.ts
|
|
43
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
44
|
+
var import_path = __toESM(require("path"), 1);
|
|
45
|
+
var import_stream = require("stream");
|
|
46
|
+
var AUDIO_EXTENSION_REGEX = /\.(mp3|wav|ogg|aac|flac)(?:$|\?)/;
|
|
47
|
+
var AUDIO_HMR_DELAY = 1e3;
|
|
48
|
+
function assetsPlugin({ bufferedAssets }) {
|
|
49
|
+
let config;
|
|
50
|
+
return {
|
|
51
|
+
name: "twick:assets",
|
|
52
|
+
configResolved(resolvedConfig) {
|
|
53
|
+
config = resolvedConfig;
|
|
54
|
+
},
|
|
55
|
+
configureServer(server) {
|
|
56
|
+
server.middlewares.use((req, res, next) => {
|
|
57
|
+
if (req.url && bufferedAssets && bufferedAssets.test(req.url)) {
|
|
58
|
+
const file = import_fs.default.readFileSync(
|
|
59
|
+
import_path.default.resolve(config.root, req.url.slice(1))
|
|
60
|
+
);
|
|
61
|
+
import_stream.Readable.from(file).pipe(res);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
next();
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
async handleHotUpdate(ctx) {
|
|
68
|
+
const urls = [];
|
|
69
|
+
const modules = [];
|
|
70
|
+
for (const module2 of ctx.modules) {
|
|
71
|
+
urls.push(module2.url);
|
|
72
|
+
if (!AUDIO_EXTENSION_REGEX.test(module2.url)) {
|
|
73
|
+
modules.push(module2);
|
|
74
|
+
} else {
|
|
75
|
+
await new Promise((resolve) => {
|
|
76
|
+
setTimeout(resolve, AUDIO_HMR_DELAY);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (urls.length > 0) {
|
|
81
|
+
ctx.server.ws.send("twick:assets", { urls });
|
|
82
|
+
}
|
|
83
|
+
return modules;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/partials/editor.ts
|
|
89
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
90
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
91
|
+
function editorPlugin({ editor, projects }) {
|
|
92
|
+
const editorPath = import_path2.default.dirname(require.resolve(editor));
|
|
93
|
+
const editorFile = import_fs2.default.readFileSync(import_path2.default.resolve(editorPath, "editor.html"));
|
|
94
|
+
const htmlParts = editorFile.toString().replace("{{style}}", `/@fs/${import_path2.default.resolve(editorPath, "style.css")}`).split("{{source}}");
|
|
95
|
+
const createHtml = (src) => htmlParts[0] + src + htmlParts[1];
|
|
96
|
+
const resolvedEditorId = "\0virtual:editor";
|
|
97
|
+
return {
|
|
98
|
+
name: "twick:editor",
|
|
99
|
+
async load(id) {
|
|
100
|
+
const [, query] = id.split("?");
|
|
101
|
+
if (id.startsWith(resolvedEditorId)) {
|
|
102
|
+
if (projects.list.length === 1) {
|
|
103
|
+
return `import {editor} from '${editor}';
|
|
104
|
+
import project from '${projects.list[0].url}';
|
|
105
|
+
import {addEditorToProject} from '@twick/core';
|
|
106
|
+
editor(await addEditorToProject(project));
|
|
107
|
+
`;
|
|
108
|
+
}
|
|
109
|
+
if (query) {
|
|
110
|
+
const params = new URLSearchParams(query);
|
|
111
|
+
const name = params.get("project");
|
|
112
|
+
if (name && projects.lookup.has(name)) {
|
|
113
|
+
return `import {editor} from '${editor}';
|
|
114
|
+
import project from '${projects.lookup.get(name).url}';
|
|
115
|
+
import {addEditorToProject} from '@twick/core';
|
|
116
|
+
editor(await addEditorToProject(project));
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return `import {index} from '${editor}';
|
|
121
|
+
index(${JSON.stringify(projects.list)});
|
|
122
|
+
`;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
configureServer(server) {
|
|
126
|
+
server.middlewares.use((req, res, next) => {
|
|
127
|
+
if (req.url) {
|
|
128
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
129
|
+
if (url.pathname === "/") {
|
|
130
|
+
res.setHeader("Content-Type", "text/html");
|
|
131
|
+
res.end(createHtml("/@id/__x00__virtual:editor"));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const name = url.pathname.slice(1);
|
|
135
|
+
if (name && projects.lookup.has(name)) {
|
|
136
|
+
res.setHeader("Content-Type", "text/html");
|
|
137
|
+
res.end(createHtml(`/@id/__x00__virtual:editor?project=${name}`));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
next();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/partials/ffmpegBridge.ts
|
|
148
|
+
var import_ffmpeg = require("@twick/ffmpeg");
|
|
149
|
+
var import_fs3 = require("fs");
|
|
150
|
+
function ffmpegBridgePlugin({ output }) {
|
|
151
|
+
return {
|
|
152
|
+
name: "twick/ffmpeg",
|
|
153
|
+
configureServer(server) {
|
|
154
|
+
const ffmpegBridge = new FFmpegBridge(server.ws, { output });
|
|
155
|
+
const handlePostRequest = async (req, res, handler) => {
|
|
156
|
+
if (req.method !== "POST") {
|
|
157
|
+
res.statusCode = 405;
|
|
158
|
+
res.end("Method Not Allowed");
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const body = await new Promise((resolve, reject) => {
|
|
163
|
+
let data = "";
|
|
164
|
+
req.on("data", (chunk) => data += chunk);
|
|
165
|
+
req.on("end", () => resolve(data));
|
|
166
|
+
req.on("error", reject);
|
|
167
|
+
});
|
|
168
|
+
const parsedBody = JSON.parse(body);
|
|
169
|
+
const result = await handler(parsedBody);
|
|
170
|
+
if (res.writableEnded) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
res.statusCode = 200;
|
|
174
|
+
if (result) {
|
|
175
|
+
res.setHeader("Content-Type", "application/json");
|
|
176
|
+
res.end(JSON.stringify(result));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
res.end("OK");
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error("error in request handler", error);
|
|
182
|
+
res.statusCode = 500;
|
|
183
|
+
res.end("Internal Server Error");
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
server.middlewares.use(
|
|
187
|
+
"/audio-processing/generate-audio",
|
|
188
|
+
(req, res) => handlePostRequest(
|
|
189
|
+
req,
|
|
190
|
+
res,
|
|
191
|
+
async ({ tempDir, assets, startFrame, endFrame, fps }) => (0, import_ffmpeg.generateAudio)({
|
|
192
|
+
outputDir: output,
|
|
193
|
+
tempDir,
|
|
194
|
+
assets,
|
|
195
|
+
startFrame,
|
|
196
|
+
endFrame,
|
|
197
|
+
fps
|
|
198
|
+
})
|
|
199
|
+
)
|
|
200
|
+
);
|
|
201
|
+
server.middlewares.use(
|
|
202
|
+
"/audio-processing/merge-media",
|
|
203
|
+
(req, res) => handlePostRequest(
|
|
204
|
+
req,
|
|
205
|
+
res,
|
|
206
|
+
async ({ outputFilename, tempDir, format }) => (0, import_ffmpeg.mergeMedia)(outputFilename, output, tempDir, format)
|
|
207
|
+
)
|
|
208
|
+
);
|
|
209
|
+
server.middlewares.use(
|
|
210
|
+
"/twick-ffmpeg-decoder/video-frame",
|
|
211
|
+
(req, res) => handlePostRequest(req, res, async (data) => {
|
|
212
|
+
const { frame, width, height } = await ffmpegBridge.handleDecodeVideoFrame(data);
|
|
213
|
+
res.setHeader("X-Frame-Width", width.toString());
|
|
214
|
+
res.setHeader("X-Frame-Height", height.toString());
|
|
215
|
+
res.setHeader("Content-Type", "application/octet-stream");
|
|
216
|
+
res.end(Buffer.from(frame));
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
server.middlewares.use("/twick-ffmpeg-decoder/finished", (req, res) => {
|
|
220
|
+
handlePostRequest(req, res, async () => {
|
|
221
|
+
await ffmpegBridge.handleRenderFinished();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
server.middlewares.use(
|
|
225
|
+
"/twick-ffmpeg-decoder/download-video-chunks",
|
|
226
|
+
(req, res) => handlePostRequest(req, res, async (videoDurations) => {
|
|
227
|
+
const downloadedPaths = await ffmpegBridge.handleDownloadVideoChunks(videoDurations);
|
|
228
|
+
return { success: true, paths: downloadedPaths };
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
var FFmpegBridge = class {
|
|
235
|
+
constructor(ws, config) {
|
|
236
|
+
this.ws = ws;
|
|
237
|
+
this.config = config;
|
|
238
|
+
this.process = null;
|
|
239
|
+
this.handleMessage = async ({ method, data }) => {
|
|
240
|
+
if (method === "start") {
|
|
241
|
+
try {
|
|
242
|
+
this.process = new import_ffmpeg.FFmpegExporterServer({
|
|
243
|
+
...data,
|
|
244
|
+
...this.config
|
|
245
|
+
});
|
|
246
|
+
this.respondSuccess(method, await this.process.start());
|
|
247
|
+
} catch (e) {
|
|
248
|
+
this.respondError(method, e?.message);
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!this.process) {
|
|
253
|
+
this.respondError(method, "The exporting process has not been started.");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (!(method in this.process)) {
|
|
257
|
+
this.respondError(method, `Unknown method: "${method}".`);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
this.respondSuccess(method, await this.process[method](data));
|
|
262
|
+
} catch (e) {
|
|
263
|
+
this.respondError(method, e?.message);
|
|
264
|
+
}
|
|
265
|
+
if (method === "kill") {
|
|
266
|
+
this.process = null;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
// List of VideoFrameExtractors
|
|
271
|
+
this.videoFrameExtractors = /* @__PURE__ */ new Map();
|
|
272
|
+
ws.on("twick:ffmpeg-exporter", this.handleMessage);
|
|
273
|
+
}
|
|
274
|
+
respondSuccess(method, data = {}) {
|
|
275
|
+
this.ws.send("twick:ffmpeg-exporter-ack", {
|
|
276
|
+
status: "success",
|
|
277
|
+
method,
|
|
278
|
+
data
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
respondError(method, message = "Unknown error.") {
|
|
282
|
+
this.ws.send("twick:ffmpeg-exporter-ack", {
|
|
283
|
+
status: "error",
|
|
284
|
+
method,
|
|
285
|
+
message
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async handleDownloadVideoChunks(videoDurations) {
|
|
289
|
+
const downloadPromises = videoDurations.map(
|
|
290
|
+
({ src, startTime, endTime }) => import_ffmpeg.VideoFrameExtractor.downloadVideoChunk(src, startTime, endTime)
|
|
291
|
+
);
|
|
292
|
+
await Promise.all(downloadPromises);
|
|
293
|
+
}
|
|
294
|
+
async handleDecodeVideoFrame(data) {
|
|
295
|
+
const typedData = data;
|
|
296
|
+
const id = typedData.filePath + "-" + typedData.id;
|
|
297
|
+
let extractor = this.videoFrameExtractors.get(id);
|
|
298
|
+
const frameDuration = 1 / typedData.fps;
|
|
299
|
+
const isOldFrame = extractor && Math.abs(typedData.startTime - extractor.getLastTime()) < frameDuration / 2;
|
|
300
|
+
if (isOldFrame) {
|
|
301
|
+
const frame2 = extractor.getLastFrame();
|
|
302
|
+
return {
|
|
303
|
+
frame: frame2,
|
|
304
|
+
width: extractor.getWidth(),
|
|
305
|
+
height: extractor.getHeight()
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
if (extractor && typedData.startTime + frameDuration < extractor.getTime()) {
|
|
309
|
+
extractor.destroy();
|
|
310
|
+
this.videoFrameExtractors.delete(id);
|
|
311
|
+
extractor = void 0;
|
|
312
|
+
}
|
|
313
|
+
if (extractor && typedData.startTime > extractor.getTime() + frameDuration) {
|
|
314
|
+
extractor.destroy();
|
|
315
|
+
this.videoFrameExtractors.delete(id);
|
|
316
|
+
extractor = void 0;
|
|
317
|
+
}
|
|
318
|
+
if (!extractor) {
|
|
319
|
+
extractor = new import_ffmpeg.VideoFrameExtractor(
|
|
320
|
+
data.filePath,
|
|
321
|
+
data.startTime,
|
|
322
|
+
data.fps,
|
|
323
|
+
data.duration
|
|
324
|
+
);
|
|
325
|
+
this.videoFrameExtractors.set(id, extractor);
|
|
326
|
+
}
|
|
327
|
+
const frame = await extractor.popImage();
|
|
328
|
+
return {
|
|
329
|
+
frame,
|
|
330
|
+
width: extractor.getWidth(),
|
|
331
|
+
height: extractor.getHeight()
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async handleRenderFinished() {
|
|
335
|
+
this.videoFrameExtractors.forEach((extractor) => {
|
|
336
|
+
extractor.destroy();
|
|
337
|
+
const localFile = import_ffmpeg.VideoFrameExtractor.downloadedVideoMap.get(
|
|
338
|
+
extractor.filePath
|
|
339
|
+
)?.localPath;
|
|
340
|
+
if (localFile && (0, import_fs3.existsSync)(localFile)) {
|
|
341
|
+
(0, import_fs3.unlinkSync)(localFile);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
this.videoFrameExtractors.clear();
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// src/partials/imageExporter.ts
|
|
349
|
+
var import_fs4 = __toESM(require("fs"), 1);
|
|
350
|
+
var import_mime_types = __toESM(require("mime-types"), 1);
|
|
351
|
+
var import_path3 = __toESM(require("path"), 1);
|
|
352
|
+
|
|
353
|
+
// src/openInExplorer.ts
|
|
354
|
+
var import_child_process = require("child_process");
|
|
355
|
+
var import_os = require("os");
|
|
356
|
+
function openInExplorer(file) {
|
|
357
|
+
let command = null;
|
|
358
|
+
let args = [file];
|
|
359
|
+
const os3 = (0, import_os.platform)();
|
|
360
|
+
switch (os3) {
|
|
361
|
+
case "win32":
|
|
362
|
+
command = "explorer";
|
|
363
|
+
break;
|
|
364
|
+
case "linux":
|
|
365
|
+
if (isRunningOnWSL()) {
|
|
366
|
+
command = "bash";
|
|
367
|
+
args = ["-c", `cd ${file} && explorer.exe .`];
|
|
368
|
+
} else {
|
|
369
|
+
command = "xdg-open";
|
|
370
|
+
}
|
|
371
|
+
break;
|
|
372
|
+
case "darwin":
|
|
373
|
+
command = "open";
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
if (command) {
|
|
377
|
+
(0, import_child_process.spawn)(command, args, { detached: true }).unref();
|
|
378
|
+
} else {
|
|
379
|
+
console.warn(`Unsupported OS: ${os3}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function isRunningOnWSL() {
|
|
383
|
+
try {
|
|
384
|
+
const uname = (0, import_child_process.execSync)("uname -a").toString().toLowerCase();
|
|
385
|
+
return uname.includes("microsoft");
|
|
386
|
+
} catch (error) {
|
|
387
|
+
console.error(`exec error: ${error}`);
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/partials/imageExporter.ts
|
|
393
|
+
function exporterPlugin({ outputPath }) {
|
|
394
|
+
return {
|
|
395
|
+
name: "twick:exporter",
|
|
396
|
+
configureServer(server) {
|
|
397
|
+
server.middlewares.use((req, res, next) => {
|
|
398
|
+
if (req.url === "/__open-output-path") {
|
|
399
|
+
if (!import_fs4.default.existsSync(outputPath)) {
|
|
400
|
+
import_fs4.default.mkdirSync(outputPath, { recursive: true });
|
|
401
|
+
}
|
|
402
|
+
openInExplorer(outputPath);
|
|
403
|
+
res.end();
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
next();
|
|
407
|
+
});
|
|
408
|
+
server.ws.on(
|
|
409
|
+
"twick:export",
|
|
410
|
+
async ({ data, frame, sceneFrame, subDirectories, mimeType, groupByScene }, client) => {
|
|
411
|
+
const name = (groupByScene ? sceneFrame : frame).toString().padStart(6, "0");
|
|
412
|
+
const extension = import_mime_types.default.extension(mimeType);
|
|
413
|
+
const outputFilePath = import_path3.default.join(
|
|
414
|
+
outputPath,
|
|
415
|
+
...subDirectories,
|
|
416
|
+
`${name}.${extension}`
|
|
417
|
+
);
|
|
418
|
+
const outputDirectory = import_path3.default.dirname(outputFilePath);
|
|
419
|
+
if (!import_fs4.default.existsSync(outputDirectory)) {
|
|
420
|
+
import_fs4.default.mkdirSync(outputDirectory, { recursive: true });
|
|
421
|
+
}
|
|
422
|
+
const base64Data = data.slice(data.indexOf(",") + 1);
|
|
423
|
+
await import_fs4.default.promises.writeFile(outputFilePath, base64Data, {
|
|
424
|
+
encoding: "base64"
|
|
425
|
+
});
|
|
426
|
+
client.send("twick:export-ack", { frame });
|
|
427
|
+
}
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// src/partials/meta.ts
|
|
434
|
+
var import_fs5 = __toESM(require("fs"), 1);
|
|
435
|
+
var import_path4 = __toESM(require("path"), 1);
|
|
436
|
+
function metaPlugin() {
|
|
437
|
+
const timeStamps = {};
|
|
438
|
+
let config;
|
|
439
|
+
return {
|
|
440
|
+
name: "twick:meta",
|
|
441
|
+
configResolved(resolvedConfig) {
|
|
442
|
+
config = resolvedConfig;
|
|
443
|
+
},
|
|
444
|
+
async transform(code, id) {
|
|
445
|
+
const [base] = id.split("?");
|
|
446
|
+
const { name, ext } = import_path4.default.posix.parse(base);
|
|
447
|
+
if (ext !== ".meta") {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const sourceFile = config.command === "build" ? false : JSON.stringify(id);
|
|
451
|
+
return `import {MetaFile} from '@twick/core';
|
|
452
|
+
let meta;
|
|
453
|
+
if (import.meta.hot) {
|
|
454
|
+
meta = import.meta.hot.data.meta;
|
|
455
|
+
}
|
|
456
|
+
meta ??= new MetaFile('${name}', ${sourceFile});
|
|
457
|
+
if (import.meta.hot) {
|
|
458
|
+
import.meta.hot.accept();
|
|
459
|
+
import.meta.hot.data.meta = meta;
|
|
460
|
+
}
|
|
461
|
+
meta.loadData(${code});
|
|
462
|
+
export default meta;
|
|
463
|
+
`;
|
|
464
|
+
},
|
|
465
|
+
configureServer(server) {
|
|
466
|
+
server.ws.on("twick:meta", async ({ source, data }, client) => {
|
|
467
|
+
if (source.startsWith("\0")) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
timeStamps[source] = Date.now();
|
|
471
|
+
if (!process.env.DONT_WRITE_TO_META_FILES) {
|
|
472
|
+
await import_fs5.default.promises.writeFile(
|
|
473
|
+
source,
|
|
474
|
+
JSON.stringify(data, void 0, 2),
|
|
475
|
+
"utf8"
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
client.send("twick:meta-ack", { source });
|
|
479
|
+
});
|
|
480
|
+
},
|
|
481
|
+
handleHotUpdate(ctx) {
|
|
482
|
+
const now = Date.now();
|
|
483
|
+
const modules = [];
|
|
484
|
+
for (const module2 of ctx.modules) {
|
|
485
|
+
if (module2.file !== null && timeStamps[module2.file] && timeStamps[module2.file] + 1e3 > now) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
modules.push(module2);
|
|
489
|
+
}
|
|
490
|
+
return modules;
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/partials/metrics.ts
|
|
496
|
+
var import_telemetry = require("@twick/telemetry");
|
|
497
|
+
function metricsPlugin() {
|
|
498
|
+
return {
|
|
499
|
+
name: "twick:metrics",
|
|
500
|
+
async configResolved() {
|
|
501
|
+
(0, import_telemetry.sendEvent)(import_telemetry.EventName.ServerStarted);
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// src/partials/projects.ts
|
|
507
|
+
function projectsPlugin({
|
|
508
|
+
buildForEditor,
|
|
509
|
+
projects
|
|
510
|
+
}) {
|
|
511
|
+
return {
|
|
512
|
+
name: "twick:project",
|
|
513
|
+
config(config) {
|
|
514
|
+
return {
|
|
515
|
+
build: {
|
|
516
|
+
target: buildForEditor ? "esnext" : "modules",
|
|
517
|
+
assetsDir: "./",
|
|
518
|
+
rollupOptions: {
|
|
519
|
+
preserveEntrySignatures: "strict",
|
|
520
|
+
input: Object.fromEntries(
|
|
521
|
+
projects.list.map((project) => [project.name, project.url])
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
server: {
|
|
526
|
+
port: config?.server?.port ?? 9e3
|
|
527
|
+
},
|
|
528
|
+
esbuild: {
|
|
529
|
+
jsx: "automatic",
|
|
530
|
+
jsxImportSource: "@twick/2d/lib"
|
|
531
|
+
},
|
|
532
|
+
optimizeDeps: {
|
|
533
|
+
entries: projects.list.map((project) => project.url),
|
|
534
|
+
exclude: ["preact", "preact/*", "@preact/signals"]
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// src/partials/rive.ts
|
|
542
|
+
var fs5 = __toESM(require("fs"), 1);
|
|
543
|
+
var import_path5 = __toESM(require("path"), 1);
|
|
544
|
+
var import_stream2 = require("stream");
|
|
545
|
+
async function getRiveWasmPath() {
|
|
546
|
+
try {
|
|
547
|
+
const packageJsonPath = require.resolve("@rive-app/canvas-advanced/package.json");
|
|
548
|
+
const packageDir = import_path5.default.dirname(packageJsonPath);
|
|
549
|
+
const wasmPath = import_path5.default.join(packageDir, "rive.wasm");
|
|
550
|
+
await fs5.promises.access(wasmPath, fs5.constants.F_OK);
|
|
551
|
+
return wasmPath;
|
|
552
|
+
} catch (error) {
|
|
553
|
+
console.error("Error finding Rive WASM file:", error);
|
|
554
|
+
throw new Error("Could not find Rive WASM file");
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function rivePlugin() {
|
|
558
|
+
return {
|
|
559
|
+
name: "twick:rive-wasm",
|
|
560
|
+
configureServer(server) {
|
|
561
|
+
server.middlewares.use(async (req, res, next) => {
|
|
562
|
+
if (req.url && req.url === "/@rive-wasm") {
|
|
563
|
+
try {
|
|
564
|
+
const wasmPath = await getRiveWasmPath();
|
|
565
|
+
const file = await fs5.promises.readFile(wasmPath);
|
|
566
|
+
res.setHeader("Content-Type", "application/wasm");
|
|
567
|
+
import_stream2.Readable.from(file).pipe(res);
|
|
568
|
+
} catch (error) {
|
|
569
|
+
console.error("Error serving Rive WASM file:", error);
|
|
570
|
+
res.statusCode = 500;
|
|
571
|
+
res.end("Error serving Rive WASM file");
|
|
572
|
+
}
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
next();
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/partials/settings.ts
|
|
582
|
+
var import_fs6 = __toESM(require("fs"), 1);
|
|
583
|
+
var import_os2 = __toESM(require("os"), 1);
|
|
584
|
+
var import_path6 = __toESM(require("path"), 1);
|
|
585
|
+
function settingsPlugin() {
|
|
586
|
+
const settingsId = "virtual:settings.meta";
|
|
587
|
+
const resolvedSettingsId = "\0" + settingsId;
|
|
588
|
+
const settingsPath = import_path6.default.resolve(import_os2.default.homedir(), ".twick/settings.json");
|
|
589
|
+
const outputDirectory = import_path6.default.dirname(settingsPath);
|
|
590
|
+
return {
|
|
591
|
+
name: "twick:settings",
|
|
592
|
+
resolveId(id) {
|
|
593
|
+
if (id === settingsId) {
|
|
594
|
+
return resolvedSettingsId;
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
async load(id) {
|
|
598
|
+
if (id === resolvedSettingsId) {
|
|
599
|
+
let parsed = {};
|
|
600
|
+
try {
|
|
601
|
+
parsed = JSON.parse(await import_fs6.default.promises.readFile(settingsPath, "utf8"));
|
|
602
|
+
} catch (_) {
|
|
603
|
+
}
|
|
604
|
+
return JSON.stringify(parsed);
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
configureServer(server) {
|
|
608
|
+
server.ws.on("twick:meta", async ({ source, data }, client) => {
|
|
609
|
+
if (source !== resolvedSettingsId) {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
await import_fs6.default.promises.mkdir(outputDirectory, { recursive: true });
|
|
613
|
+
const newData = JSON.stringify(data, void 0, 2);
|
|
614
|
+
let oldData = "";
|
|
615
|
+
try {
|
|
616
|
+
oldData = await import_fs6.default.promises.readFile(settingsPath, "utf8");
|
|
617
|
+
} catch (_) {
|
|
618
|
+
}
|
|
619
|
+
if (oldData !== newData) {
|
|
620
|
+
await Promise.all([
|
|
621
|
+
import_fs6.default.promises.writeFile(settingsPath, newData, "utf8"),
|
|
622
|
+
// Invalidate the module so that the settings are up-to-date next
|
|
623
|
+
// time the browser is refreshed.
|
|
624
|
+
server.moduleGraph.getModuleByUrl(source).then((module2) => {
|
|
625
|
+
if (module2) {
|
|
626
|
+
server.moduleGraph.invalidateModule(module2);
|
|
627
|
+
}
|
|
628
|
+
})
|
|
629
|
+
]);
|
|
630
|
+
}
|
|
631
|
+
client.send("twick:meta-ack", { source });
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// src/partials/wasmExporter.ts
|
|
638
|
+
var import_formidable = require("formidable");
|
|
639
|
+
var fs7 = __toESM(require("fs"), 1);
|
|
640
|
+
var os2 = __toESM(require("os"), 1);
|
|
641
|
+
var import_path7 = __toESM(require("path"), 1);
|
|
642
|
+
var import_stream3 = require("stream");
|
|
643
|
+
function wasmExporterPlugin() {
|
|
644
|
+
return {
|
|
645
|
+
name: "twick:mp4-wasm",
|
|
646
|
+
configureServer(server) {
|
|
647
|
+
server.middlewares.use(async (req, res, next) => {
|
|
648
|
+
if (req.url && req.url === "/@mp4-wasm") {
|
|
649
|
+
const pathNew = import_path7.default.dirname(require.resolve("mp4-wasm"));
|
|
650
|
+
const filePath = import_path7.default.resolve(pathNew, "mp4.wasm");
|
|
651
|
+
const file = await fs7.promises.readFile(filePath);
|
|
652
|
+
res.setHeader("Content-Type", "application/wasm");
|
|
653
|
+
import_stream3.Readable.from(file).pipe(res);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
next();
|
|
657
|
+
});
|
|
658
|
+
server.middlewares.use("/uploadVideoFile", async (req, res) => {
|
|
659
|
+
if (req.method === "POST") {
|
|
660
|
+
const form = new import_formidable.IncomingForm({ maxFileSize: 1024 * 1024 * 1024 * 10 });
|
|
661
|
+
form.parse(req, async (err, fields, files) => {
|
|
662
|
+
if (err) {
|
|
663
|
+
console.error("Error parsing form:", err);
|
|
664
|
+
res.statusCode = 500;
|
|
665
|
+
res.end();
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
try {
|
|
669
|
+
const tempDir = fields.tempDir[0];
|
|
670
|
+
const file = files.file[0];
|
|
671
|
+
const outputPath = import_path7.default.join(os2.tmpdir(), tempDir, "visuals.mp4");
|
|
672
|
+
const writeStream = fs7.createWriteStream(outputPath);
|
|
673
|
+
await new Promise((resolve, reject) => {
|
|
674
|
+
fs7.createReadStream(file.filepath).pipe(writeStream).on("finish", resolve).on("error", reject);
|
|
675
|
+
});
|
|
676
|
+
res.statusCode = 200;
|
|
677
|
+
res.end();
|
|
678
|
+
} catch (err2) {
|
|
679
|
+
console.error("Error uploading video:", err2);
|
|
680
|
+
res.statusCode = 500;
|
|
681
|
+
res.end();
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/partials/webgl.ts
|
|
691
|
+
var import_fs7 = __toESM(require("fs"), 1);
|
|
692
|
+
var import_path8 = __toESM(require("path"), 1);
|
|
693
|
+
var import_source_map = require("source-map");
|
|
694
|
+
var import_vite = require("vite");
|
|
695
|
+
var GLSL_EXTENSION_REGEX = /\.glsl(?:$|\?)/;
|
|
696
|
+
var INCLUDE_REGEX = /^#include "([^"]+)"/;
|
|
697
|
+
function webglPlugin() {
|
|
698
|
+
let config;
|
|
699
|
+
return {
|
|
700
|
+
name: "twick:webgl",
|
|
701
|
+
configResolved(resolvedConfig) {
|
|
702
|
+
config = resolvedConfig;
|
|
703
|
+
},
|
|
704
|
+
async transform(code, id) {
|
|
705
|
+
if (!GLSL_EXTENSION_REGEX.test(id)) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const [base, query] = id.split("?");
|
|
709
|
+
const { dir } = import_path8.default.posix.parse(base);
|
|
710
|
+
const params = new URLSearchParams(query);
|
|
711
|
+
if (params.has("raw")) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const context = {
|
|
715
|
+
rootDir: dir,
|
|
716
|
+
fileStack: [],
|
|
717
|
+
includeMap: /* @__PURE__ */ new Map(),
|
|
718
|
+
watchFile: (file) => this.addWatchFile(file),
|
|
719
|
+
resolve: async (source, importer) => {
|
|
720
|
+
const resolved = await this.resolve(source, importer);
|
|
721
|
+
return resolved?.id;
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
const glslSource = await resolveGlsl(context, id, code);
|
|
725
|
+
const sourceUrl = (0, import_vite.normalizePath)(import_path8.default.relative(config.root, base));
|
|
726
|
+
const result = glslSource.toStringWithSourceMap();
|
|
727
|
+
const map = result.map.toJSON();
|
|
728
|
+
map.includeMap = Object.fromEntries(context.includeMap);
|
|
729
|
+
return {
|
|
730
|
+
map,
|
|
731
|
+
code: `export default \`${result.code}
|
|
732
|
+
//# sourceURL=${sourceUrl}\`;`
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
async function resolveGlsl(context, id, code) {
|
|
738
|
+
const lines = code.split(/\r?\n/);
|
|
739
|
+
const source = new import_source_map.SourceNode(1, 0, "", "");
|
|
740
|
+
if (context.fileStack.includes(id)) {
|
|
741
|
+
throw new Error(
|
|
742
|
+
`Circular dependency detected: ${context.fileStack.join(" -> ")}`
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
context.fileStack.push(id);
|
|
746
|
+
const sourceMapId = import_path8.default.posix.relative(context.rootDir, id);
|
|
747
|
+
for (let i = 0; i < lines.length; i++) {
|
|
748
|
+
const line = lines[i];
|
|
749
|
+
const match = line.match(INCLUDE_REGEX);
|
|
750
|
+
if (match) {
|
|
751
|
+
const childId = await context.resolve(match[1], id);
|
|
752
|
+
if (!childId) {
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
const childSourceMapId = import_path8.default.posix.relative(context.rootDir, childId);
|
|
756
|
+
if (context.includeMap.has(childSourceMapId)) {
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
context.includeMap.set(childSourceMapId, [sourceMapId, i + 1]);
|
|
760
|
+
context.watchFile(childId);
|
|
761
|
+
const childCode = await import_fs7.default.promises.readFile(childId, "utf-8");
|
|
762
|
+
source.add(await resolveGlsl(context, childId, childCode));
|
|
763
|
+
} else {
|
|
764
|
+
let j = 0;
|
|
765
|
+
for (; j < line.length; j++) {
|
|
766
|
+
source.add(new import_source_map.SourceNode(i + 1, j, sourceMapId, line[j]));
|
|
767
|
+
}
|
|
768
|
+
source.add(new import_source_map.SourceNode(i + 1, j, sourceMapId, "\n"));
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
context.fileStack.pop();
|
|
772
|
+
return source;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// src/plugins.ts
|
|
776
|
+
var PLUGIN_OPTIONS = /* @__PURE__ */ Symbol.for("@twick/vite-plugin/PLUGIN_OPTIONS");
|
|
777
|
+
function isPlugin(value) {
|
|
778
|
+
return value && typeof value === "object" && PLUGIN_OPTIONS in value;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/utils.ts
|
|
782
|
+
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
783
|
+
var import_fs8 = __toESM(require("fs"), 1);
|
|
784
|
+
var import_path9 = __toESM(require("path"), 1);
|
|
785
|
+
function getProjects(project) {
|
|
786
|
+
const list = [];
|
|
787
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
788
|
+
const projectList = expandFilePaths(project);
|
|
789
|
+
for (const url of projectList) {
|
|
790
|
+
const { name, dir } = import_path9.default.posix.parse(url);
|
|
791
|
+
const metaFile = `${name}.meta`;
|
|
792
|
+
const metaData = getMeta(import_path9.default.join(dir, metaFile));
|
|
793
|
+
const data = { name: metaData?.name ?? name, fileName: name, url };
|
|
794
|
+
list.push(data);
|
|
795
|
+
lookup.set(data.name, data);
|
|
796
|
+
}
|
|
797
|
+
return { list, lookup };
|
|
798
|
+
}
|
|
799
|
+
function expandFilePaths(filePaths) {
|
|
800
|
+
const expandedFilePaths = [];
|
|
801
|
+
for (const filePath of typeof filePaths === "string" ? [filePaths] : filePaths) {
|
|
802
|
+
if (import_fast_glob.default.isDynamicPattern(filePath)) {
|
|
803
|
+
const matchingFilePaths = import_fast_glob.default.sync(filePath, { onlyFiles: true });
|
|
804
|
+
expandedFilePaths.push(...matchingFilePaths);
|
|
805
|
+
} else {
|
|
806
|
+
expandedFilePaths.push(filePath);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return expandedFilePaths;
|
|
810
|
+
}
|
|
811
|
+
function getMeta(metaPath) {
|
|
812
|
+
if (import_fs8.default.existsSync(metaPath)) {
|
|
813
|
+
return JSON.parse(import_fs8.default.readFileSync(metaPath, "utf8"));
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/main.ts
|
|
818
|
+
var main_default = ({
|
|
819
|
+
project = "./src/project.ts",
|
|
820
|
+
output = "./output",
|
|
821
|
+
bufferedAssets = /^$/,
|
|
822
|
+
editor = "@twick/ui",
|
|
823
|
+
buildForEditor
|
|
824
|
+
} = {}) => {
|
|
825
|
+
const plugins = [];
|
|
826
|
+
const outputPath = import_path10.default.resolve(output);
|
|
827
|
+
const projects = getProjects(project);
|
|
828
|
+
return [
|
|
829
|
+
{
|
|
830
|
+
name: "twick",
|
|
831
|
+
async configResolved(resolvedConfig) {
|
|
832
|
+
plugins.push(
|
|
833
|
+
...resolvedConfig.plugins.filter(isPlugin).map((plugin) => plugin[PLUGIN_OPTIONS])
|
|
834
|
+
);
|
|
835
|
+
await Promise.all(
|
|
836
|
+
plugins.map(
|
|
837
|
+
(plugin) => plugin.config?.({
|
|
838
|
+
output: outputPath,
|
|
839
|
+
projects: projects.list
|
|
840
|
+
})
|
|
841
|
+
)
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
metaPlugin(),
|
|
846
|
+
settingsPlugin(),
|
|
847
|
+
exporterPlugin({ outputPath }),
|
|
848
|
+
ffmpegBridgePlugin({ output: outputPath }),
|
|
849
|
+
editorPlugin({ editor, projects }),
|
|
850
|
+
projectsPlugin({ projects, plugins, buildForEditor }),
|
|
851
|
+
assetsPlugin({ bufferedAssets }),
|
|
852
|
+
wasmExporterPlugin(),
|
|
853
|
+
rivePlugin(),
|
|
854
|
+
webglPlugin(),
|
|
855
|
+
metricsPlugin()
|
|
856
|
+
];
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
// src/index.ts
|
|
860
|
+
var index_default = main_default;
|
|
861
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
862
|
+
0 && (module.exports = {
|
|
863
|
+
PLUGIN_OPTIONS,
|
|
864
|
+
isPlugin
|
|
865
|
+
});
|
|
866
|
+
//# sourceMappingURL=index.cjs.map
|