@hyperframes/studio-server 0.8.69 → 0.8.71
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 +17 -1
- package/dist/index.js +61 -17
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -80,6 +80,13 @@ interface AppliedFileMutation {
|
|
|
80
80
|
/** Applies prepared HTML mutations with the same journal and receipt semantics as Studio. */
|
|
81
81
|
declare function applyFileMutations(projectDir: string, mutations: readonly FileMutationInput[], requestToken?: string, writeFile?: (path: string, content: string, encoding: "utf-8") => void): AppliedFileMutation[];
|
|
82
82
|
|
|
83
|
+
/** The bundler options every adapter's `bundle()` uses. This route serves project files under a
|
|
84
|
+
* `<base href>`, so assets keep their URLs: inlined base64 multiplies the document per reference. */
|
|
85
|
+
declare const PREVIEW_BUNDLE_OPTIONS: {
|
|
86
|
+
readonly runtime: "placeholder";
|
|
87
|
+
readonly inlineAssets: false;
|
|
88
|
+
};
|
|
89
|
+
|
|
83
90
|
declare const MIME_TYPES: Record<string, string>;
|
|
84
91
|
declare function getMimeType(path: string): string;
|
|
85
92
|
|
|
@@ -90,6 +97,15 @@ interface FileWriteReceipt {
|
|
|
90
97
|
}
|
|
91
98
|
/** Strong content version used as both the JSON version and HTTP ETag. */
|
|
92
99
|
declare function fileContentVersion(content: string | Uint8Array): string;
|
|
100
|
+
/** A validator from a file's inode, change time and size, or null while the change is under three
|
|
101
|
+
* seconds old. Change time, not mtime: copy tools (`cp -p`, rsync, robocopy) set mtime back but not
|
|
102
|
+
* ctime, which Node reads from NTFS ChangeTime on Windows. A same-size rewrite within one tick keeps
|
|
103
|
+
* both, so only a settled file is tagged. */
|
|
104
|
+
declare function settledFileTag(stat: {
|
|
105
|
+
ino: number;
|
|
106
|
+
ctimeMs: number;
|
|
107
|
+
size: number;
|
|
108
|
+
}, now?: number): string | null;
|
|
93
109
|
/**
|
|
94
110
|
* Attach one API write's identity to the watcher echo for its exact bytes.
|
|
95
111
|
*
|
|
@@ -167,4 +183,4 @@ type BackgroundRemovalRender = (options: {
|
|
|
167
183
|
}>;
|
|
168
184
|
declare function createBackgroundRemovalJob(opts: BackgroundRemovalJobOptions, render: BackgroundRemovalRender): MediaProcessingJobState;
|
|
169
185
|
|
|
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 };
|
|
186
|
+
export { type AppliedFileMutation, type BackgroundRemovalRender, type DuplicateElementResult, type FileMutationInput, type FileWriteReceipt, MIME_TYPES, MediaProcessingJobState, PREVIEW_BUNDLE_OPTIONS, SourceMutationTarget, StudioApiAdapter, type ThumbnailOutputDimensions, affectsProjectSignature, applyFileMutations, buildSubCompositionHtml, compositionsAffectedBy, consumeFileWriteReceipt, createBackgroundRemovalJob, createProjectSignature, createStudioApi, duplicateElementInHtml, fileContentVersion, getMimeType, identifyFileWrite, settledFileTag, thumbnailDeviceScaleFactor, walkDir };
|
package/dist/index.js
CHANGED
|
@@ -302,8 +302,10 @@ import { bodyLimit } from "hono/body-limit";
|
|
|
302
302
|
import {
|
|
303
303
|
closeSync,
|
|
304
304
|
existsSync as existsSync3,
|
|
305
|
+
lstatSync as lstatSync3,
|
|
305
306
|
openSync,
|
|
306
307
|
readFileSync as readFileSync5,
|
|
308
|
+
readlinkSync,
|
|
307
309
|
writeFileSync as writeFileSync4,
|
|
308
310
|
writeSync,
|
|
309
311
|
mkdirSync as mkdirSync3,
|
|
@@ -620,6 +622,10 @@ var receipts = /* @__PURE__ */ new Map();
|
|
|
620
622
|
function fileContentVersion(content) {
|
|
621
623
|
return `"sha256:${createHash2("sha256").update(content).digest("hex")}"`;
|
|
622
624
|
}
|
|
625
|
+
function settledFileTag(stat, now = Date.now()) {
|
|
626
|
+
if (now - stat.ctimeMs < 3e3) return null;
|
|
627
|
+
return [stat.ino, stat.ctimeMs, stat.size].map((n) => n.toString(36)).join("-");
|
|
628
|
+
}
|
|
623
629
|
function createWriteToken(requestToken) {
|
|
624
630
|
const token = requestToken?.trim();
|
|
625
631
|
return token && token.length <= 200 ? token : randomUUID2();
|
|
@@ -1016,6 +1022,24 @@ function resolveGsapWriter(env) {
|
|
|
1016
1022
|
async function loadGsapParser() {
|
|
1017
1023
|
return import("@hyperframes/parsers/gsap-parser-recast");
|
|
1018
1024
|
}
|
|
1025
|
+
function isDanglingSymlinkInProject(projectDir, lexicalPath) {
|
|
1026
|
+
if (!isSafePath(projectDir, dirname3(lexicalPath))) return false;
|
|
1027
|
+
let stats;
|
|
1028
|
+
try {
|
|
1029
|
+
stats = lstatSync3(lexicalPath);
|
|
1030
|
+
} catch {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
if (!stats.isSymbolicLink()) return false;
|
|
1034
|
+
const target = resolve3(dirname3(lexicalPath), readlinkSync(lexicalPath));
|
|
1035
|
+
if (!isSafePath(projectDir, target)) return false;
|
|
1036
|
+
try {
|
|
1037
|
+
statSync3(lexicalPath);
|
|
1038
|
+
return false;
|
|
1039
|
+
} catch {
|
|
1040
|
+
return true;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1019
1043
|
async function resolveProjectPath(c, adapter, pathPrefix, opts) {
|
|
1020
1044
|
const id = c.req.param("id");
|
|
1021
1045
|
const project = await adapter.resolveProject(id);
|
|
@@ -1033,6 +1057,9 @@ async function resolveProjectPath(c, adapter, pathPrefix, opts) {
|
|
|
1033
1057
|
}
|
|
1034
1058
|
const absPath = resolveWithinProject(project.dir, filePath);
|
|
1035
1059
|
if (!absPath) {
|
|
1060
|
+
if (isDanglingSymlinkInProject(project.dir, resolve3(project.dir, filePath))) {
|
|
1061
|
+
return { error: c.json({ error: "not found", why: "dangling_symlink" }, 404) };
|
|
1062
|
+
}
|
|
1036
1063
|
return { error: c.json({ error: "forbidden", why: "outside_project" }, 403) };
|
|
1037
1064
|
}
|
|
1038
1065
|
if (opts?.mustExist && !existsSync3(absPath)) {
|
|
@@ -2406,21 +2433,31 @@ function registerFileRoutes(api, adapter) {
|
|
|
2406
2433
|
api.get("/projects/:id/files/*", async (c) => {
|
|
2407
2434
|
const res = await resolveProjectFile(c, adapter);
|
|
2408
2435
|
if ("error" in res) return res.error;
|
|
2409
|
-
|
|
2436
|
+
let fd;
|
|
2437
|
+
try {
|
|
2438
|
+
fd = openSync(res.absPath, "r");
|
|
2439
|
+
} catch {
|
|
2410
2440
|
if (c.req.query("optional") === "1") {
|
|
2411
2441
|
return c.json({ filename: res.filePath, content: "", missing: true });
|
|
2412
2442
|
}
|
|
2413
2443
|
return c.json({ error: "not found" }, 404);
|
|
2414
2444
|
}
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
version
|
|
2422
|
-
|
|
2423
|
-
|
|
2445
|
+
try {
|
|
2446
|
+
if (!fstatSync(fd).isFile()) {
|
|
2447
|
+
return c.json({ error: "not found", why: "not_a_file" }, 404);
|
|
2448
|
+
}
|
|
2449
|
+
const content = readFileSync5(fd);
|
|
2450
|
+
const version = fileContentVersion(content);
|
|
2451
|
+
c.header("ETag", version);
|
|
2452
|
+
return c.json({
|
|
2453
|
+
filename: res.filePath,
|
|
2454
|
+
content: content.toString("utf-8"),
|
|
2455
|
+
version,
|
|
2456
|
+
missing: false
|
|
2457
|
+
});
|
|
2458
|
+
} finally {
|
|
2459
|
+
closeSync(fd);
|
|
2460
|
+
}
|
|
2424
2461
|
});
|
|
2425
2462
|
api.put("/projects/:id/files/*", async (c) => {
|
|
2426
2463
|
const res = await resolveProjectFile(c, adapter);
|
|
@@ -3182,7 +3219,10 @@ import { createReadStream, existsSync as existsSync5, readFileSync as readFileSy
|
|
|
3182
3219
|
import { Readable } from "stream";
|
|
3183
3220
|
import { join as join8, resolve as resolve4 } from "path";
|
|
3184
3221
|
import { createHash as createHash3 } from "crypto";
|
|
3185
|
-
import {
|
|
3222
|
+
import {
|
|
3223
|
+
injectScriptsIntoHtml,
|
|
3224
|
+
stripEmbeddedRuntimeScripts as stripEmbeddedRuntimeScripts2
|
|
3225
|
+
} from "@hyperframes/core/compiler";
|
|
3186
3226
|
import { isWithinProjectRoot } from "@hyperframes/parsers/asset-resolution";
|
|
3187
3227
|
|
|
3188
3228
|
// src/helpers/subComposition.ts
|
|
@@ -3627,6 +3667,10 @@ function resolveProjectMainHtml(projectDir, projectId) {
|
|
|
3627
3667
|
}
|
|
3628
3668
|
return null;
|
|
3629
3669
|
}
|
|
3670
|
+
var PREVIEW_BUNDLE_OPTIONS = {
|
|
3671
|
+
runtime: "placeholder",
|
|
3672
|
+
inlineAssets: false
|
|
3673
|
+
};
|
|
3630
3674
|
function registerPreviewRoutes(api, adapter) {
|
|
3631
3675
|
const previewCacheHeaders = (etag) => ({
|
|
3632
3676
|
"Cache-Control": "private, no-cache",
|
|
@@ -3789,12 +3833,10 @@ ${runtimeTag}`;
|
|
|
3789
3833
|
return c.text("media proxy variant does not match asset", 422);
|
|
3790
3834
|
}
|
|
3791
3835
|
}
|
|
3792
|
-
const
|
|
3793
|
-
const
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
};
|
|
3797
|
-
if (!isText) {
|
|
3836
|
+
const tag = settledFileTag(stat);
|
|
3837
|
+
const etag = tag && `"${tag}${proxyEtagSalt(proxyVariant)}"`;
|
|
3838
|
+
const cacheHeaders = isText ? { "Cache-Control": "no-store" } : { "Cache-Control": "private, no-cache", ...etag && { ETag: etag } };
|
|
3839
|
+
if (!isText && etag) {
|
|
3798
3840
|
const ifNoneMatch = c.req.header("If-None-Match");
|
|
3799
3841
|
if (ifNoneMatch === etag) {
|
|
3800
3842
|
return new Response(null, { status: 304, headers: cacheHeaders });
|
|
@@ -5170,6 +5212,7 @@ function updateBackgroundRemovalProgress(state, event) {
|
|
|
5170
5212
|
}
|
|
5171
5213
|
export {
|
|
5172
5214
|
MIME_TYPES,
|
|
5215
|
+
PREVIEW_BUNDLE_OPTIONS,
|
|
5173
5216
|
STUDIO_MANUAL_EDITS_PATH,
|
|
5174
5217
|
STUDIO_MOTION_PATH,
|
|
5175
5218
|
affectsProjectSignature,
|
|
@@ -5194,6 +5237,7 @@ export {
|
|
|
5194
5237
|
parseSourceDocument,
|
|
5195
5238
|
patchElementInHtml,
|
|
5196
5239
|
removeElementFromHtml,
|
|
5240
|
+
settledFileTag,
|
|
5197
5241
|
splitElementInHtml,
|
|
5198
5242
|
thumbnailDeviceScaleFactor,
|
|
5199
5243
|
walkDir
|