@hyperframes/studio-server 0.8.63 → 0.8.65
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.d.ts +9 -2
- package/dist/index.js +89 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -41,8 +41,15 @@ declare function createStudioApi(adapter: StudioApiAdapter): Hono;
|
|
|
41
41
|
declare function affectsProjectSignature(projectDir: string, changedPath: string): boolean;
|
|
42
42
|
/**
|
|
43
43
|
* Creates a stable preview cache-busting signature for project source plus Studio manifests.
|
|
44
|
+
* `excluding` (project-relative paths) leaves those files out.
|
|
44
45
|
*/
|
|
45
|
-
declare function createProjectSignature(projectDir: string): string;
|
|
46
|
+
declare function createProjectSignature(projectDir: string, excluding?: ReadonlySet<string>): string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Compositions whose rendered frames a write at `changedPath` can change, or `null` for all
|
|
50
|
+
* of them: the root, assets and any file the root does not mount reach every composition.
|
|
51
|
+
*/
|
|
52
|
+
declare function compositionsAffectedBy(projectDir: string, changedPath: string): string[] | null;
|
|
46
53
|
|
|
47
54
|
/** Recursively walk a directory and return relative file paths. */
|
|
48
55
|
declare function walkDir(dir: string, prefix?: string): string[];
|
|
@@ -160,4 +167,4 @@ type BackgroundRemovalRender = (options: {
|
|
|
160
167
|
}>;
|
|
161
168
|
declare function createBackgroundRemovalJob(opts: BackgroundRemovalJobOptions, render: BackgroundRemovalRender): MediaProcessingJobState;
|
|
162
169
|
|
|
163
|
-
export { type AppliedFileMutation, type BackgroundRemovalRender, type DuplicateElementResult, type FileMutationInput, type FileWriteReceipt, MIME_TYPES, MediaProcessingJobState, SourceMutationTarget, StudioApiAdapter, type ThumbnailOutputDimensions, affectsProjectSignature, applyFileMutations, buildSubCompositionHtml, consumeFileWriteReceipt, createBackgroundRemovalJob, createProjectSignature, createStudioApi, duplicateElementInHtml, fileContentVersion, getMimeType, identifyFileWrite, thumbnailDeviceScaleFactor, walkDir };
|
|
170
|
+
export { type AppliedFileMutation, type BackgroundRemovalRender, type DuplicateElementResult, type FileMutationInput, type FileWriteReceipt, MIME_TYPES, MediaProcessingJobState, SourceMutationTarget, StudioApiAdapter, type ThumbnailOutputDimensions, affectsProjectSignature, applyFileMutations, buildSubCompositionHtml, compositionsAffectedBy, consumeFileWriteReceipt, createBackgroundRemovalJob, createProjectSignature, createStudioApi, duplicateElementInHtml, fileContentVersion, getMimeType, identifyFileWrite, thumbnailDeviceScaleFactor, walkDir };
|
package/dist/index.js
CHANGED
|
@@ -217,14 +217,18 @@ async function resolveProjectAndSignature(adapter, projectId) {
|
|
|
217
217
|
if (!project) return null;
|
|
218
218
|
return { project, signature: resolveProjectSignature(adapter, project.dir) };
|
|
219
219
|
}
|
|
220
|
-
function createProjectSignature(projectDir) {
|
|
220
|
+
function createProjectSignature(projectDir, excluding = /* @__PURE__ */ new Set()) {
|
|
221
221
|
const normalizedProjectDir = resolve(projectDir);
|
|
222
|
-
const
|
|
223
|
-
collectProjectSignatureFiles(normalizedProjectDir, normalizedProjectDir,
|
|
224
|
-
collectProjectSignatureManifestFiles(normalizedProjectDir,
|
|
222
|
+
const collected = [];
|
|
223
|
+
collectProjectSignatureFiles(normalizedProjectDir, normalizedProjectDir, collected);
|
|
224
|
+
collectProjectSignatureManifestFiles(normalizedProjectDir, collected);
|
|
225
|
+
const files = collected.filter(
|
|
226
|
+
(entry) => !excluding.has(relative(normalizedProjectDir, entry.file).split(sep).join("/"))
|
|
227
|
+
);
|
|
225
228
|
files.sort((a, b) => a.file.localeCompare(b.file));
|
|
226
229
|
const fingerprint = createProjectFingerprint(normalizedProjectDir, files);
|
|
227
|
-
const
|
|
230
|
+
const cacheKey = excluding.size ? `${normalizedProjectDir}\0${createHash("sha256").update([...excluding].sort().join("\0")).digest("hex")}` : normalizedProjectDir;
|
|
231
|
+
const cached = projectSignatureCache.get(cacheKey);
|
|
228
232
|
if (cached?.fingerprint === fingerprint) return cached.signature;
|
|
229
233
|
const hash = createHash("sha256");
|
|
230
234
|
for (const entry of files) {
|
|
@@ -245,7 +249,7 @@ function createProjectSignature(projectDir) {
|
|
|
245
249
|
hash.update("\0");
|
|
246
250
|
}
|
|
247
251
|
const signature = hash.digest("hex").slice(0, 24);
|
|
248
|
-
projectSignatureCache.set(
|
|
252
|
+
projectSignatureCache.set(cacheKey, { fingerprint, signature });
|
|
249
253
|
return signature;
|
|
250
254
|
}
|
|
251
255
|
|
|
@@ -2404,14 +2408,19 @@ function registerFileRoutes(api, adapter) {
|
|
|
2404
2408
|
if ("error" in res) return res.error;
|
|
2405
2409
|
if (!existsSync3(res.absPath)) {
|
|
2406
2410
|
if (c.req.query("optional") === "1") {
|
|
2407
|
-
return c.json({ filename: res.filePath, content: "" });
|
|
2411
|
+
return c.json({ filename: res.filePath, content: "", missing: true });
|
|
2408
2412
|
}
|
|
2409
2413
|
return c.json({ error: "not found" }, 404);
|
|
2410
2414
|
}
|
|
2411
2415
|
const content = readFileSync5(res.absPath);
|
|
2412
2416
|
const version = fileContentVersion(content);
|
|
2413
2417
|
c.header("ETag", version);
|
|
2414
|
-
return c.json({
|
|
2418
|
+
return c.json({
|
|
2419
|
+
filename: res.filePath,
|
|
2420
|
+
content: content.toString("utf-8"),
|
|
2421
|
+
version,
|
|
2422
|
+
missing: false
|
|
2423
|
+
});
|
|
2415
2424
|
});
|
|
2416
2425
|
api.put("/projects/:id/files/*", async (c) => {
|
|
2417
2426
|
const res = await resolveProjectFile(c, adapter);
|
|
@@ -4149,7 +4158,7 @@ function registerRenderRoutes(api, adapter) {
|
|
|
4149
4158
|
import {
|
|
4150
4159
|
existsSync as existsSync7,
|
|
4151
4160
|
mkdirSync as mkdirSync5,
|
|
4152
|
-
readFileSync as
|
|
4161
|
+
readFileSync as readFileSync12,
|
|
4153
4162
|
readdirSync as readdirSync6,
|
|
4154
4163
|
renameSync as renameSync3,
|
|
4155
4164
|
rmSync as rmSync4,
|
|
@@ -4160,6 +4169,63 @@ import {
|
|
|
4160
4169
|
import { join as join11 } from "path";
|
|
4161
4170
|
import { createHash as createHash4, randomUUID as randomUUID4 } from "crypto";
|
|
4162
4171
|
|
|
4172
|
+
// src/helpers/compositionInputs.ts
|
|
4173
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
4174
|
+
var ROOT_COMPOSITION = "index.html";
|
|
4175
|
+
var COMPOSITION_SRC = /\bdata-composition-src\s*=\s*(["'])(.*?)\1/g;
|
|
4176
|
+
function projectReader(projectDir) {
|
|
4177
|
+
const sources = /* @__PURE__ */ new Map();
|
|
4178
|
+
return (compPath) => {
|
|
4179
|
+
if (!sources.has(compPath)) {
|
|
4180
|
+
const file = resolveWithinProject(projectDir, compPath);
|
|
4181
|
+
let source = null;
|
|
4182
|
+
try {
|
|
4183
|
+
source = file ? readFileSync11(file, "utf-8") : null;
|
|
4184
|
+
} catch {
|
|
4185
|
+
}
|
|
4186
|
+
sources.set(compPath, source);
|
|
4187
|
+
}
|
|
4188
|
+
return sources.get(compPath) ?? null;
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
function normalizeSource(src) {
|
|
4192
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(src) || src.startsWith("//")) return null;
|
|
4193
|
+
const path = src.split(/[?#]/)[0]?.replace(/^\.?\//, "") ?? "";
|
|
4194
|
+
return path || null;
|
|
4195
|
+
}
|
|
4196
|
+
function closureOf(read, compPath) {
|
|
4197
|
+
const closure = /* @__PURE__ */ new Set();
|
|
4198
|
+
const visit = (path) => {
|
|
4199
|
+
if (closure.has(path)) return;
|
|
4200
|
+
closure.add(path);
|
|
4201
|
+
for (const match of (read(path) ?? "").matchAll(COMPOSITION_SRC)) {
|
|
4202
|
+
const mounted = normalizeSource(match[2] ?? "");
|
|
4203
|
+
if (mounted) visit(mounted);
|
|
4204
|
+
}
|
|
4205
|
+
};
|
|
4206
|
+
visit(compPath);
|
|
4207
|
+
return closure;
|
|
4208
|
+
}
|
|
4209
|
+
var inputSignatures = /* @__PURE__ */ new Map();
|
|
4210
|
+
function compositionInputSignature(projectDir, compPath, projectSignature) {
|
|
4211
|
+
const key = `${projectDir}\0${compPath}`;
|
|
4212
|
+
const known = inputSignatures.get(key);
|
|
4213
|
+
if (known?.projectSignature === projectSignature) return known.inputSignature;
|
|
4214
|
+
const read = projectReader(projectDir);
|
|
4215
|
+
const inputs = closureOf(read, normalizeSource(compPath) ?? compPath).add(ROOT_COMPOSITION);
|
|
4216
|
+
const siblings = [...closureOf(read, ROOT_COMPOSITION)].filter((path) => !inputs.has(path));
|
|
4217
|
+
const inputSignature = createProjectSignature(projectDir, new Set(siblings));
|
|
4218
|
+
inputSignatures.set(key, { projectSignature, inputSignature });
|
|
4219
|
+
return inputSignature;
|
|
4220
|
+
}
|
|
4221
|
+
function compositionsAffectedBy(projectDir, changedPath) {
|
|
4222
|
+
const changed = changedPath.replace(/\\/g, "/");
|
|
4223
|
+
const read = projectReader(projectDir);
|
|
4224
|
+
const mounted = closureOf(read, ROOT_COMPOSITION);
|
|
4225
|
+
if (changed === ROOT_COMPOSITION || !mounted.has(changed)) return null;
|
|
4226
|
+
return [...mounted].filter((path) => closureOf(read, path).has(changed));
|
|
4227
|
+
}
|
|
4228
|
+
|
|
4163
4229
|
// src/routes/thumbnailGenerationCoordinator.ts
|
|
4164
4230
|
var ThumbnailGenerationCoordinator = class {
|
|
4165
4231
|
constructor(concurrency = 1) {
|
|
@@ -4322,6 +4388,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4322
4388
|
c.req.path.replace(`/projects/${project.id}/thumbnail/`, "").split("?")[0] ?? ""
|
|
4323
4389
|
);
|
|
4324
4390
|
if (compPath && !compPath.includes(".")) compPath += ".html";
|
|
4391
|
+
const inputSignature = compositionInputSignature(project.dir, compPath, projectSignature);
|
|
4325
4392
|
const url = new URL(c.req.url, `http://${c.req.header("host") || "localhost"}`);
|
|
4326
4393
|
const rawSeekTime = url.searchParams.get("t");
|
|
4327
4394
|
const parsedSeekTime = rawSeekTime == null ? Number.NaN : parseFloat(rawSeekTime);
|
|
@@ -4342,7 +4409,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4342
4409
|
let sourceKey = "";
|
|
4343
4410
|
const htmlFile = join11(project.dir, compPath);
|
|
4344
4411
|
if (existsSync7(htmlFile)) {
|
|
4345
|
-
const html =
|
|
4412
|
+
const html = readFileSync12(htmlFile, "utf-8");
|
|
4346
4413
|
sourceKey = `_${createHash4("sha1").update(html).digest("hex").slice(0, 16)}`;
|
|
4347
4414
|
sourceMtime = Math.round(statSync7(htmlFile).mtimeMs);
|
|
4348
4415
|
if (!vpWidth) {
|
|
@@ -4355,14 +4422,14 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4355
4422
|
const manualEditsFile = join11(project.dir, STUDIO_MANUAL_EDITS_PATH);
|
|
4356
4423
|
let manualEditsKey = "";
|
|
4357
4424
|
if (existsSync7(manualEditsFile)) {
|
|
4358
|
-
const manualEditsContent =
|
|
4425
|
+
const manualEditsContent = readFileSync12(manualEditsFile, "utf-8");
|
|
4359
4426
|
manualEditsKey = `_${createHash4("sha1").update(manualEditsContent).digest("hex").slice(0, 16)}`;
|
|
4360
4427
|
sourceMtime = Math.max(sourceMtime, Math.round(statSync7(manualEditsFile).mtimeMs));
|
|
4361
4428
|
}
|
|
4362
4429
|
const motionFile = join11(project.dir, STUDIO_MOTION_PATH);
|
|
4363
4430
|
let motionKey = "";
|
|
4364
4431
|
if (existsSync7(motionFile)) {
|
|
4365
|
-
const motionContent =
|
|
4432
|
+
const motionContent = readFileSync12(motionFile, "utf-8");
|
|
4366
4433
|
motionKey = `_${createHash4("sha1").update(motionContent).digest("hex").slice(0, 16)}`;
|
|
4367
4434
|
sourceMtime = Math.max(sourceMtime, Math.round(statSync7(motionFile).mtimeMs));
|
|
4368
4435
|
}
|
|
@@ -4370,11 +4437,11 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4370
4437
|
const cacheDir = join11(project.dir, ".thumbnails");
|
|
4371
4438
|
const selectorKey = selector ? `_${selector.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 80)}_${selectorIndex ?? 0}` : "";
|
|
4372
4439
|
const urlVersionKey = urlVersion ? `_${urlVersion.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 32)}` : "";
|
|
4373
|
-
const
|
|
4440
|
+
const inputSignatureKey = `_${createHash4("sha1").update(inputSignature).digest("hex").slice(0, 16)}`;
|
|
4374
4441
|
const outputScale = outputMode === "source" ? 1 : Math.min(1, THUMBNAIL_MAX_OUTPUT_WIDTH / compW, THUMBNAIL_MAX_OUTPUT_HEIGHT / compH);
|
|
4375
4442
|
const outputWidth = Math.max(1, Math.round(compW * outputScale));
|
|
4376
4443
|
const outputHeight = Math.max(1, Math.round(compH * outputScale));
|
|
4377
|
-
const cacheKey = `${THUMBNAIL_CACHE_VERSION}${urlVersionKey}${
|
|
4444
|
+
const cacheKey = `${THUMBNAIL_CACHE_VERSION}${urlVersionKey}${inputSignatureKey}${manualEditsKey}${motionKey}${sourceKey}_${format}_${outputMode}_${compPath.replace(/\//g, "_")}_${compW}x${compH}_${outputWidth}x${outputHeight}_${sourceMtime}_${seekTime.toFixed(2)}${selectorKey}.${format === "png" ? "png" : "jpg"}`;
|
|
4378
4445
|
const cachePath = join11(cacheDir, cacheKey);
|
|
4379
4446
|
if (!prunedCacheDirs.has(cacheDir)) {
|
|
4380
4447
|
prunedCacheDirs.add(cacheDir);
|
|
@@ -4384,7 +4451,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4384
4451
|
);
|
|
4385
4452
|
}
|
|
4386
4453
|
if (existsSync7(cachePath)) {
|
|
4387
|
-
return new Response(new Uint8Array(
|
|
4454
|
+
return new Response(new Uint8Array(readFileSync12(cachePath)), {
|
|
4388
4455
|
headers: { "Content-Type": contentType, "Cache-Control": "no-cache" }
|
|
4389
4456
|
});
|
|
4390
4457
|
}
|
|
@@ -4409,8 +4476,8 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4409
4476
|
});
|
|
4410
4477
|
if (!generated) return null;
|
|
4411
4478
|
const afterGeneration = await resolveProjectAndSignature(adapter, project.id);
|
|
4412
|
-
const
|
|
4413
|
-
if (
|
|
4479
|
+
const inputsUnchanged = (signature) => compositionInputSignature(project.dir, compPath, signature) === inputSignature;
|
|
4480
|
+
if (afterGeneration?.project.dir !== project.dir || !inputsUnchanged(afterGeneration.signature) || !inputsUnchanged(createProjectSignature(project.dir))) {
|
|
4414
4481
|
return generated;
|
|
4415
4482
|
}
|
|
4416
4483
|
if (!existsSync7(cacheDir)) mkdirSync5(cacheDir, { recursive: true });
|
|
@@ -4439,7 +4506,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4439
4506
|
}
|
|
4440
4507
|
|
|
4441
4508
|
// src/routes/waveform.ts
|
|
4442
|
-
import { existsSync as existsSync8, readFileSync as
|
|
4509
|
+
import { existsSync as existsSync8, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
4443
4510
|
import { join as join12 } from "path";
|
|
4444
4511
|
function registerWaveformRoutes(api, adapter) {
|
|
4445
4512
|
api.get("/projects/:id/waveform/*", async (c) => {
|
|
@@ -4455,7 +4522,7 @@ function registerWaveformRoutes(api, adapter) {
|
|
|
4455
4522
|
const cachePath = join12(cacheDir, buildWaveformCacheKey(assetPath, stats));
|
|
4456
4523
|
try {
|
|
4457
4524
|
if (isWaveformCacheDirectory(cacheDir) && existsSync8(cachePath)) {
|
|
4458
|
-
const peaks2 = JSON.parse(
|
|
4525
|
+
const peaks2 = JSON.parse(readFileSync13(cachePath, "utf-8"));
|
|
4459
4526
|
return c.json({ peaks: peaks2 });
|
|
4460
4527
|
}
|
|
4461
4528
|
} catch {
|
|
@@ -4941,14 +5008,14 @@ function registerMediaRoutes(api, adapter, options = {}) {
|
|
|
4941
5008
|
}
|
|
4942
5009
|
|
|
4943
5010
|
// src/routes/globalAssets.ts
|
|
4944
|
-
import { existsSync as existsSync10, readFileSync as
|
|
5011
|
+
import { existsSync as existsSync10, readFileSync as readFileSync14 } from "fs";
|
|
4945
5012
|
import { homedir } from "os";
|
|
4946
5013
|
import { join as join14 } from "path";
|
|
4947
5014
|
function readGlobalAssets(home = homedir()) {
|
|
4948
5015
|
const manifestPath = join14(home, ".media", "manifest.jsonl");
|
|
4949
5016
|
if (!existsSync10(manifestPath)) return [];
|
|
4950
5017
|
const out = [];
|
|
4951
|
-
for (const line of
|
|
5018
|
+
for (const line of readFileSync14(manifestPath, "utf8").split("\n")) {
|
|
4952
5019
|
if (!line.trim()) continue;
|
|
4953
5020
|
try {
|
|
4954
5021
|
const rec = JSON.parse(line);
|
|
@@ -5108,6 +5175,7 @@ export {
|
|
|
5108
5175
|
affectsProjectSignature,
|
|
5109
5176
|
applyFileMutations,
|
|
5110
5177
|
buildSubCompositionHtml,
|
|
5178
|
+
compositionsAffectedBy,
|
|
5111
5179
|
consumeFileWriteReceipt,
|
|
5112
5180
|
createBackgroundRemovalJob,
|
|
5113
5181
|
createProjectSignature,
|