@hyperframes/studio-server 0.7.46 → 0.7.47
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 +6 -0
- package/dist/index.js +92 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -149,6 +149,12 @@ interface StudioApiAdapter {
|
|
|
149
149
|
outputResolution?: CanvasResolution;
|
|
150
150
|
/** Entry file relative to projectDir (e.g. "compositions/intro.html"). Defaults to index.html. */
|
|
151
151
|
composition?: string;
|
|
152
|
+
/**
|
|
153
|
+
* Composition-variable overrides ({variableId: value}), forwarded to the
|
|
154
|
+
* producer's RenderConfig.variables and injected as window.__hfVariables —
|
|
155
|
+
* the same channel `hyperframes render --variables` uses.
|
|
156
|
+
*/
|
|
157
|
+
variables?: Record<string, unknown>;
|
|
152
158
|
/**
|
|
153
159
|
* Telemetry id of the browser user who triggered the render. Lets the
|
|
154
160
|
* adapter attribute the server-emitted render_complete/render_error to
|
package/dist/index.js
CHANGED
|
@@ -2200,6 +2200,19 @@ ${bootstrap}`;
|
|
|
2200
2200
|
}
|
|
2201
2201
|
block = extractGsapScriptBlock(html);
|
|
2202
2202
|
}
|
|
2203
|
+
if (!block && (body.type === "shift-positions" || body.type === "scale-positions")) {
|
|
2204
|
+
return c.json({
|
|
2205
|
+
ok: true,
|
|
2206
|
+
changed: false,
|
|
2207
|
+
mutated: false,
|
|
2208
|
+
parsed: { animations: [], timelineVar: "tl", preamble: "", postamble: "" },
|
|
2209
|
+
before: html,
|
|
2210
|
+
after: html,
|
|
2211
|
+
scriptText: "",
|
|
2212
|
+
path: res.filePath,
|
|
2213
|
+
backupPath: null
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2203
2216
|
if (!block) {
|
|
2204
2217
|
return c.json({ error: "no GSAP script found in file" }, 400);
|
|
2205
2218
|
}
|
|
@@ -2228,6 +2241,7 @@ ${bootstrap}`;
|
|
|
2228
2241
|
const responsePayload = {
|
|
2229
2242
|
ok: true,
|
|
2230
2243
|
changed,
|
|
2244
|
+
mutated: changed,
|
|
2231
2245
|
parsed: freshParsed,
|
|
2232
2246
|
before: html,
|
|
2233
2247
|
after: newHtml,
|
|
@@ -2245,6 +2259,7 @@ ${bootstrap}`;
|
|
|
2245
2259
|
// src/routes/preview.ts
|
|
2246
2260
|
import { existsSync as existsSync5, readFileSync as readFileSync7, statSync as statSync2 } from "fs";
|
|
2247
2261
|
import { join as join8 } from "path";
|
|
2262
|
+
import { createHash as createHash2 } from "crypto";
|
|
2248
2263
|
import { injectScriptsIntoHtml, stripEmbeddedRuntimeScripts as stripEmbeddedRuntimeScripts2 } from "@hyperframes/core/compiler";
|
|
2249
2264
|
|
|
2250
2265
|
// src/helpers/subComposition.ts
|
|
@@ -2316,6 +2331,9 @@ function extractTemplateInnerHtml(rawComp) {
|
|
|
2316
2331
|
const template = doc.querySelector("template");
|
|
2317
2332
|
return template ? template.innerHTML : null;
|
|
2318
2333
|
}
|
|
2334
|
+
function escapeAttrValue(value) {
|
|
2335
|
+
return value.replace(/&/g, "&").replace(/"/g, """);
|
|
2336
|
+
}
|
|
2319
2337
|
function extractElementAttrs(el) {
|
|
2320
2338
|
const parts = [];
|
|
2321
2339
|
for (let i = 0; i < el.attributes.length; i++) {
|
|
@@ -2323,7 +2341,7 @@ function extractElementAttrs(el) {
|
|
|
2323
2341
|
if (attr.value === "") {
|
|
2324
2342
|
parts.push(attr.name);
|
|
2325
2343
|
} else {
|
|
2326
|
-
parts.push(`${attr.name}="${attr.value}"`);
|
|
2344
|
+
parts.push(`${attr.name}="${escapeAttrValue(attr.value)}"`);
|
|
2327
2345
|
}
|
|
2328
2346
|
}
|
|
2329
2347
|
return parts.join(" ");
|
|
@@ -2812,6 +2830,12 @@ function stampFileHfIds(filePath) {
|
|
|
2812
2830
|
}
|
|
2813
2831
|
}
|
|
2814
2832
|
|
|
2833
|
+
// src/helpers/variablesPayload.ts
|
|
2834
|
+
var VARIABLES_PAYLOAD_ERROR = "variables must be a JSON object of {variableId: value}";
|
|
2835
|
+
function isVariablesPayload(value) {
|
|
2836
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2815
2839
|
// src/routes/preview.ts
|
|
2816
2840
|
var PROJECT_SIGNATURE_META = "hyperframes-project-signature";
|
|
2817
2841
|
var GSAP_CDN_VERSION = "3.15.0";
|
|
@@ -2934,6 +2958,40 @@ function injectGsapCdnFallback(html) {
|
|
|
2934
2958
|
if (html.includes("<head>")) return html.replace("<head>", "<head>" + GSAP_CDN_FALLBACK_SCRIPT);
|
|
2935
2959
|
return GSAP_CDN_FALLBACK_SCRIPT + html;
|
|
2936
2960
|
}
|
|
2961
|
+
function injectPreviewVariables(html, values) {
|
|
2962
|
+
const json = JSON.stringify(values).replace(/</g, "\\u003c");
|
|
2963
|
+
const tag = `<script data-hf-preview-variables>window.__hfVariables=${json};</script>`;
|
|
2964
|
+
for (const pattern of [/<head[^>]*>/i, /<html[^>]*>/i, /^\s*<!doctype[^>]*>/i]) {
|
|
2965
|
+
const match = pattern.exec(html);
|
|
2966
|
+
if (match) {
|
|
2967
|
+
const at = match.index + match[0].length;
|
|
2968
|
+
return html.slice(0, at) + tag + html.slice(at);
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
return tag + html;
|
|
2972
|
+
}
|
|
2973
|
+
function parsePreviewVariablesParam(raw) {
|
|
2974
|
+
if (raw === void 0 || raw === "") return { ok: true, values: null };
|
|
2975
|
+
let parsed;
|
|
2976
|
+
try {
|
|
2977
|
+
parsed = JSON.parse(raw);
|
|
2978
|
+
} catch {
|
|
2979
|
+
return { ok: false, error: "variables must be valid JSON" };
|
|
2980
|
+
}
|
|
2981
|
+
if (!isVariablesPayload(parsed)) {
|
|
2982
|
+
return { ok: false, error: VARIABLES_PAYLOAD_ERROR };
|
|
2983
|
+
}
|
|
2984
|
+
return { ok: true, values: parsed };
|
|
2985
|
+
}
|
|
2986
|
+
function variablesEtagSalt(raw) {
|
|
2987
|
+
if (!raw) return "";
|
|
2988
|
+
return `:vars:${createHash2("sha1").update(raw).digest("hex").slice(0, 12)}`;
|
|
2989
|
+
}
|
|
2990
|
+
function previewVariablesFromRequest(rawVariables) {
|
|
2991
|
+
const parse = parsePreviewVariablesParam(rawVariables);
|
|
2992
|
+
if (!parse.ok) return { error: parse.error };
|
|
2993
|
+
return { raw: rawVariables, values: parse.values };
|
|
2994
|
+
}
|
|
2937
2995
|
function injectStudioPreviewAugmentations(html, adapter, projectDir, activeCompositionPath) {
|
|
2938
2996
|
return injectStudioMotionScript(
|
|
2939
2997
|
injectMotionPathPluginIfNeeded(
|
|
@@ -2977,8 +3035,11 @@ function registerPreviewRoutes(api, adapter) {
|
|
|
2977
3035
|
api.get("/projects/:id/preview", async (c) => {
|
|
2978
3036
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
2979
3037
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
3038
|
+
const vars = previewVariablesFromRequest(c.req.query("variables"));
|
|
3039
|
+
if (vars.error !== void 0) return c.json({ error: vars.error }, 400);
|
|
3040
|
+
const previewVariables = vars.values;
|
|
2980
3041
|
const signature = resolveProjectSignature(adapter, project.dir);
|
|
2981
|
-
const etag = `"preview:${signature}"`;
|
|
3042
|
+
const etag = `"preview:${signature}${variablesEtagSalt(vars.raw)}"`;
|
|
2982
3043
|
const ifNoneMatch = c.req.header("If-None-Match");
|
|
2983
3044
|
if (ifNoneMatch === etag) {
|
|
2984
3045
|
return new Response(null, { status: 304, headers: previewCacheHeaders(etag) });
|
|
@@ -3009,6 +3070,7 @@ ${runtimeTag}`;
|
|
|
3009
3070
|
project.dir,
|
|
3010
3071
|
mainCompositionPath
|
|
3011
3072
|
);
|
|
3073
|
+
if (previewVariables) bundled = injectPreviewVariables(bundled, previewVariables);
|
|
3012
3074
|
return c.html(bundled, 200, previewCacheHeaders(etag));
|
|
3013
3075
|
} catch {
|
|
3014
3076
|
const fallback = resolveProjectMainHtml(project.dir, project.id);
|
|
@@ -3017,16 +3079,16 @@ ${runtimeTag}`;
|
|
|
3017
3079
|
join8(project.dir, fallback.compositionPath),
|
|
3018
3080
|
fallback.html
|
|
3019
3081
|
);
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
fallback.compositionPath
|
|
3026
|
-
),
|
|
3027
|
-
200,
|
|
3028
|
-
previewCacheHeaders(etag)
|
|
3082
|
+
let fallbackAugmented = injectStudioPreviewAugmentations(
|
|
3083
|
+
await transformPreviewHtml(fallbackHtml, adapter, project, fallback.compositionPath),
|
|
3084
|
+
adapter,
|
|
3085
|
+
project.dir,
|
|
3086
|
+
fallback.compositionPath
|
|
3029
3087
|
);
|
|
3088
|
+
if (previewVariables) {
|
|
3089
|
+
fallbackAugmented = injectPreviewVariables(fallbackAugmented, previewVariables);
|
|
3090
|
+
}
|
|
3091
|
+
return c.html(fallbackAugmented, 200, previewCacheHeaders(etag));
|
|
3030
3092
|
}
|
|
3031
3093
|
return c.text("not found", 404);
|
|
3032
3094
|
}
|
|
@@ -3038,6 +3100,9 @@ ${runtimeTag}`;
|
|
|
3038
3100
|
api.get("/projects/:id/preview/comp/*", async (c) => {
|
|
3039
3101
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
3040
3102
|
if (!project) return c.json({ error: "not found" }, 404);
|
|
3103
|
+
const vars = previewVariablesFromRequest(c.req.query("variables"));
|
|
3104
|
+
if (vars.error !== void 0) return c.json({ error: vars.error }, 400);
|
|
3105
|
+
const previewVariables = vars.values;
|
|
3041
3106
|
const signature = resolveProjectSignature(adapter, project.dir);
|
|
3042
3107
|
const compPath = decodeURIComponent(
|
|
3043
3108
|
c.req.path.replace(`/projects/${project.id}/preview/comp/`, "").split("?")[0] ?? ""
|
|
@@ -3046,7 +3111,7 @@ ${runtimeTag}`;
|
|
|
3046
3111
|
if (!compFile || !existsSync5(compFile) || !statSync2(compFile).isFile()) {
|
|
3047
3112
|
return c.text("not found", 404);
|
|
3048
3113
|
}
|
|
3049
|
-
const etag = `"comp:v2:${compPath}:${signature}"`;
|
|
3114
|
+
const etag = `"comp:v2:${compPath}:${signature}${variablesEtagSalt(vars.raw)}"`;
|
|
3050
3115
|
const ifNoneMatch = c.req.header("If-None-Match");
|
|
3051
3116
|
if (ifNoneMatch === etag) {
|
|
3052
3117
|
return new Response(null, { status: 304, headers: previewCacheHeaders(etag) });
|
|
@@ -3063,11 +3128,9 @@ ${runtimeTag}`;
|
|
|
3063
3128
|
);
|
|
3064
3129
|
if (!html) return c.text("not found", 404);
|
|
3065
3130
|
html = ensureHfIds2(await transformPreviewHtml(html, adapter, project, compPath));
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
previewCacheHeaders(etag)
|
|
3070
|
-
);
|
|
3131
|
+
html = injectStudioPreviewAugmentations(html, adapter, project.dir, compPath);
|
|
3132
|
+
if (previewVariables) html = injectPreviewVariables(html, previewVariables);
|
|
3133
|
+
return c.html(html, 200, previewCacheHeaders(etag));
|
|
3071
3134
|
});
|
|
3072
3135
|
api.get("/projects/:id/preview/*", async (c) => {
|
|
3073
3136
|
const project = await adapter.resolveProject(c.req.param("id"));
|
|
@@ -3206,6 +3269,13 @@ function registerRenderRoutes(api, adapter) {
|
|
|
3206
3269
|
}
|
|
3207
3270
|
composition = body.composition;
|
|
3208
3271
|
}
|
|
3272
|
+
let variables;
|
|
3273
|
+
if (body.variables !== void 0) {
|
|
3274
|
+
if (!isVariablesPayload(body.variables)) {
|
|
3275
|
+
return c.json({ error: VARIABLES_PAYLOAD_ERROR }, 400);
|
|
3276
|
+
}
|
|
3277
|
+
variables = body.variables;
|
|
3278
|
+
}
|
|
3209
3279
|
const now = /* @__PURE__ */ new Date();
|
|
3210
3280
|
const datePart = now.toISOString().slice(0, 10);
|
|
3211
3281
|
const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
|
|
@@ -3223,6 +3293,7 @@ function registerRenderRoutes(api, adapter) {
|
|
|
3223
3293
|
jobId,
|
|
3224
3294
|
outputResolution,
|
|
3225
3295
|
composition,
|
|
3296
|
+
variables,
|
|
3226
3297
|
distinctId: typeof body.telemetryDistinctId === "string" ? body.telemetryDistinctId : void 0
|
|
3227
3298
|
});
|
|
3228
3299
|
jobState.createdAt = Date.now();
|
|
@@ -3388,7 +3459,7 @@ function registerRenderRoutes(api, adapter) {
|
|
|
3388
3459
|
// src/routes/thumbnail.ts
|
|
3389
3460
|
import { existsSync as existsSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, statSync as statSync4 } from "fs";
|
|
3390
3461
|
import { join as join11 } from "path";
|
|
3391
|
-
import { createHash as
|
|
3462
|
+
import { createHash as createHash3 } from "crypto";
|
|
3392
3463
|
|
|
3393
3464
|
// src/helpers/manualEditsRenderScript.ts
|
|
3394
3465
|
var STUDIO_MANUAL_EDITS_PATH = ".hyperframes/studio-manual-edits.json";
|
|
@@ -4000,7 +4071,7 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4000
4071
|
const htmlFile = join11(project.dir, compPath);
|
|
4001
4072
|
if (existsSync7(htmlFile)) {
|
|
4002
4073
|
const html = readFileSync10(htmlFile, "utf-8");
|
|
4003
|
-
sourceKey = `_${
|
|
4074
|
+
sourceKey = `_${createHash3("sha1").update(html).digest("hex").slice(0, 16)}`;
|
|
4004
4075
|
sourceMtime = Math.round(statSync4(htmlFile).mtimeMs);
|
|
4005
4076
|
if (!vpWidth) {
|
|
4006
4077
|
const wMatch = html.match(/data-width=["'](\d+)["']/);
|
|
@@ -4013,14 +4084,14 @@ function registerThumbnailRoutes(api, adapter) {
|
|
|
4013
4084
|
let manualEditsKey = "";
|
|
4014
4085
|
if (existsSync7(manualEditsFile)) {
|
|
4015
4086
|
const manualEditsContent = readFileSync10(manualEditsFile, "utf-8");
|
|
4016
|
-
manualEditsKey = `_${
|
|
4087
|
+
manualEditsKey = `_${createHash3("sha1").update(manualEditsContent).digest("hex").slice(0, 16)}`;
|
|
4017
4088
|
sourceMtime = Math.max(sourceMtime, Math.round(statSync4(manualEditsFile).mtimeMs));
|
|
4018
4089
|
}
|
|
4019
4090
|
const motionFile = join11(project.dir, STUDIO_MOTION_PATH);
|
|
4020
4091
|
let motionKey = "";
|
|
4021
4092
|
if (existsSync7(motionFile)) {
|
|
4022
4093
|
const motionContent = readFileSync10(motionFile, "utf-8");
|
|
4023
|
-
motionKey = `_${
|
|
4094
|
+
motionKey = `_${createHash3("sha1").update(motionContent).digest("hex").slice(0, 16)}`;
|
|
4024
4095
|
sourceMtime = Math.max(sourceMtime, Math.round(statSync4(motionFile).mtimeMs));
|
|
4025
4096
|
}
|
|
4026
4097
|
const previewUrl = compPath === "index.html" ? `http://${c.req.header("host")}/api/projects/${project.id}/preview` : `http://${c.req.header("host")}/api/projects/${project.id}/preview/comp/${compPath}`;
|