@hyperframes/core 0.8.208 → 0.8.209
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/atomicFile.js +18 -19
- package/dist/compiler/compositionScoping.d.ts +2 -0
- package/dist/compiler/compositionScoping.js +9 -0
- package/dist/compiler/htmlBundler.js +2 -8
- package/dist/compiler/htmlDocument.d.ts +4 -0
- package/dist/compiler/htmlDocument.js +22 -0
- package/dist/compiler/index.d.ts +1 -1
- package/dist/compiler/index.js +1 -1
- package/dist/generated/runtime-inline.js +1 -1
- package/dist/hyperframe.runtime.iife.js +64 -64
- package/dist/removeScratch.d.ts +5 -0
- package/dist/removeScratch.js +23 -0
- package/dist/renameWithRetry.d.ts +16 -0
- package/dist/renameWithRetry.js +81 -0
- package/dist/runtime/applyVariableBindings.d.ts +6 -4
- package/dist/runtime/applyVariableBindings.js +23 -11
- package/dist/runtime/getVariables.js +5 -4
- package/dist/runtime/variableScope.d.ts +2 -0
- package/dist/runtime/variableScope.js +9 -3
- package/package.json +12 -4
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Removes a scratch folder or file the mix is already done with. A refusal (Windows temp folders that deny
|
|
2
|
+
* delete) leaves it for the render's own cleanup and never fails the audio it served. */
|
|
3
|
+
export declare function removeScratch(path: string): void;
|
|
4
|
+
/** Runs `run`; whatever removeScratch left behind inside it (this run's only) goes to `report` once it ends. */
|
|
5
|
+
export declare function collectScratchLeftovers<T>(run: () => Promise<T>, report: (left: string[]) => void): Promise<T>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
const collecting = new AsyncLocalStorage();
|
|
4
|
+
/** Removes a scratch folder or file the mix is already done with. A refusal (Windows temp folders that deny
|
|
5
|
+
* delete) leaves it for the render's own cleanup and never fails the audio it served. */
|
|
6
|
+
export function removeScratch(path) {
|
|
7
|
+
try {
|
|
8
|
+
rmSync(path, { recursive: true, force: true });
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
collecting.getStore()?.push(path);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Runs `run`; whatever removeScratch left behind inside it (this run's only) goes to `report` once it ends. */
|
|
15
|
+
export async function collectScratchLeftovers(run, report) {
|
|
16
|
+
const left = [];
|
|
17
|
+
try {
|
|
18
|
+
return await collecting.run(left, run);
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
report(left);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface Options {
|
|
2
|
+
platform?: NodeJS.Platform;
|
|
3
|
+
/** Copy the bytes when Windows keeps refusing (a TEMP that denies delete). Not atomic: scratch files only. */
|
|
4
|
+
copyFallback?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/** `renameSync` that rides out Windows' transient file locks; other platforms and errors throw at once. */
|
|
7
|
+
export declare function renameWithRetry(from: string, to: string, options?: Options & {
|
|
8
|
+
rename?: (from: string, to: string) => void;
|
|
9
|
+
copy?: (from: string, to: string) => void;
|
|
10
|
+
}): void;
|
|
11
|
+
/** Async twin of {@link renameWithRetry}. */
|
|
12
|
+
export declare function renameWithRetryAsync(from: string, to: string, options?: Options & {
|
|
13
|
+
rename?: (from: string, to: string) => Promise<void>;
|
|
14
|
+
copy?: (from: string, to: string) => Promise<void>;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { copyFileSync, renameSync } from "node:fs";
|
|
2
|
+
import { copyFile, rename } from "node:fs/promises";
|
|
3
|
+
import { removeScratch } from "./removeScratch.js";
|
|
4
|
+
// Windows reports EPERM/EACCES/EBUSY while antivirus or a just-exited process still holds the
|
|
5
|
+
// file; the hold clears within milliseconds. Total wait before giving up: about 1.3 s.
|
|
6
|
+
const RETRYABLE = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
7
|
+
const BACKOFF_MS = [10, 20, 40, 80, 160, 320, 640];
|
|
8
|
+
function isRetryable(error, platform) {
|
|
9
|
+
if (platform !== "win32")
|
|
10
|
+
return false;
|
|
11
|
+
return RETRYABLE.has(error?.code ?? "");
|
|
12
|
+
}
|
|
13
|
+
/** What a refused rename does next: wait and try again, fall back to a copy, or give up with the error. */
|
|
14
|
+
function nextStep(error, attempt, options, platform) {
|
|
15
|
+
if (!isRetryable(error, platform))
|
|
16
|
+
return "throw";
|
|
17
|
+
if (attempt < BACKOFF_MS.length)
|
|
18
|
+
return "wait";
|
|
19
|
+
return options.copyFallback ? "copy" : "throw";
|
|
20
|
+
}
|
|
21
|
+
function sleepSync(ms) {
|
|
22
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
23
|
+
}
|
|
24
|
+
/** `renameSync` that rides out Windows' transient file locks; other platforms and errors throw at once. */
|
|
25
|
+
export function renameWithRetry(from, to, options = {}) {
|
|
26
|
+
const doRename = options.rename ?? renameSync;
|
|
27
|
+
const doCopy = options.copy ?? copyFileSync;
|
|
28
|
+
const platform = options.platform ?? process.platform;
|
|
29
|
+
for (let attempt = 0;; attempt++) {
|
|
30
|
+
try {
|
|
31
|
+
doRename(from, to);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const step = nextStep(error, attempt, options, platform);
|
|
36
|
+
if (step === "throw")
|
|
37
|
+
throw error;
|
|
38
|
+
if (step === "wait") {
|
|
39
|
+
sleepSync(BACKOFF_MS[attempt]);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
doCopy(from, to);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
removeScratch(from);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Async twin of {@link renameWithRetry}. */
|
|
54
|
+
export async function renameWithRetryAsync(from, to, options = {}) {
|
|
55
|
+
const doRename = options.rename ?? rename;
|
|
56
|
+
const doCopy = options.copy ?? copyFile;
|
|
57
|
+
const platform = options.platform ?? process.platform;
|
|
58
|
+
for (let attempt = 0;; attempt++) {
|
|
59
|
+
try {
|
|
60
|
+
await doRename(from, to);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
const step = nextStep(error, attempt, options, platform);
|
|
65
|
+
if (step === "throw")
|
|
66
|
+
throw error;
|
|
67
|
+
if (step === "wait") {
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, BACKOFF_MS[attempt]));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
await doCopy(from, to);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
removeScratch(from);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
* chain the color-grading runtime uses: `__hfVariablesByComp[compId]` for
|
|
22
22
|
* inlined sub-compositions, then the top-level merged `getVariables()`.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
* external / template sub-compositions (their
|
|
26
|
-
* values don't exist at init). Idempotent:
|
|
27
|
-
* values.
|
|
24
|
+
* Text is bound before composition scripts run; all of it at init and after
|
|
25
|
+
* the composition loader inlines external / template sub-compositions (their
|
|
26
|
+
* DOM and per-instance scoped values don't exist at init). Idempotent:
|
|
27
|
+
* re-applying writes the same values.
|
|
28
28
|
*/
|
|
29
29
|
/**
|
|
30
30
|
* Strip characters that could smuggle additional declarations or markup out of
|
|
@@ -45,3 +45,5 @@ export declare function sanitizeCssValue(value: string): string;
|
|
|
45
45
|
export declare function unproxiedMediaSrc(el: Element): string | null;
|
|
46
46
|
/** Applies the bindings in `doc`, or only those inside `within` and `within` itself. */
|
|
47
47
|
export declare function applyVariableBindings(doc: Document, within?: Element): void;
|
|
48
|
+
/** Binds only text, in `doc` or inside `within`: what composition scripts read before they run. */
|
|
49
|
+
export declare function applyTextBindings(doc: Document, within?: Element): void;
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
* chain the color-grading runtime uses: `__hfVariablesByComp[compId]` for
|
|
22
22
|
* inlined sub-compositions, then the top-level merged `getVariables()`.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
* external / template sub-compositions (their
|
|
26
|
-
* values don't exist at init). Idempotent:
|
|
27
|
-
* values.
|
|
24
|
+
* Text is bound before composition scripts run; all of it at init and after
|
|
25
|
+
* the composition loader inlines external / template sub-compositions (their
|
|
26
|
+
* DOM and per-instance scoped values don't exist at init). Idempotent:
|
|
27
|
+
* re-applying writes the same values.
|
|
28
28
|
*/
|
|
29
29
|
import { readVariablesForElement } from "./variableScope.js";
|
|
30
30
|
import { isScalarVariableValue as isScalar, isSafeMediaUrl, } from "@hyperframes/parsers/composition";
|
|
@@ -154,12 +154,13 @@ function variableSrcFor(el, cache, warn = true) {
|
|
|
154
154
|
export function unproxiedMediaSrc(el) {
|
|
155
155
|
return variableSrcFor(el, new Map(), false) ?? unproxiedSrc(el);
|
|
156
156
|
}
|
|
157
|
+
const matching = (doc, within, selector) => within
|
|
158
|
+
? [...(within.matches(selector) ? [within] : []), ...within.querySelectorAll(selector)]
|
|
159
|
+
: Array.from(doc.querySelectorAll(selector));
|
|
157
160
|
/** Applies the bindings in `doc`, or only those inside `within` and `within` itself. */
|
|
158
161
|
export function applyVariableBindings(doc, within) {
|
|
159
162
|
const cache = new Map();
|
|
160
|
-
const all = (selector) => within
|
|
161
|
-
? [...(within.matches(selector) ? [within] : []), ...within.querySelectorAll(selector)]
|
|
162
|
-
: Array.from(doc.querySelectorAll(selector));
|
|
163
|
+
const all = (selector) => matching(doc, within, selector);
|
|
163
164
|
const topRoot = within ? null : findTopRoot(doc);
|
|
164
165
|
applyCssCustomProperties([...(topRoot ? [topRoot] : []), ...all("[data-composition-id]")], cache);
|
|
165
166
|
for (const el of all("[data-var-src]")) {
|
|
@@ -167,12 +168,23 @@ export function applyVariableBindings(doc, within) {
|
|
|
167
168
|
if (url !== null && unproxiedSrc(el) !== url)
|
|
168
169
|
el.setAttribute("src", url);
|
|
169
170
|
}
|
|
170
|
-
|
|
171
|
+
bindText(all("[data-var-text]"), cache);
|
|
172
|
+
}
|
|
173
|
+
/** Binds only text, in `doc` or inside `within`: what composition scripts read before they run. */
|
|
174
|
+
export function applyTextBindings(doc, within) {
|
|
175
|
+
bindText(matching(doc, within, "[data-var-text]"), new Map());
|
|
176
|
+
}
|
|
177
|
+
const textWrittenOnce = new WeakSet();
|
|
178
|
+
/** Writes each binding once: a script may then reshape the text, which a second write would duplicate. */
|
|
179
|
+
function bindText(elements, cache) {
|
|
180
|
+
for (const el of elements) {
|
|
171
181
|
const id = el.getAttribute("data-var-text")?.trim();
|
|
172
|
-
if (!id)
|
|
182
|
+
if (!id || textWrittenOnce.has(el))
|
|
173
183
|
continue;
|
|
174
184
|
const value = valuesForElement(el, cache)[id];
|
|
175
|
-
if (isScalar(value))
|
|
176
|
-
|
|
185
|
+
if (!isScalar(value))
|
|
186
|
+
continue;
|
|
187
|
+
setOwnTextPreservingChildren(el, String(value));
|
|
188
|
+
textWrittenOnce.add(el);
|
|
177
189
|
}
|
|
178
190
|
}
|
|
@@ -20,18 +20,19 @@
|
|
|
20
20
|
* const { title = "Untitled", theme = "light" } = getVariables<MyVars>();
|
|
21
21
|
*/
|
|
22
22
|
import { cssVariableName, detectSlugCollisions } from "../tokenSlug.js";
|
|
23
|
+
import { scopedVariablesFor } from "./variableScope.js";
|
|
23
24
|
export function getVariables() {
|
|
24
25
|
if (typeof document === "undefined")
|
|
25
26
|
return {};
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// render-time overrides.
|
|
27
|
+
// <html> first, then any element carrying the attribute (later declarers win), then render-time
|
|
28
|
+
// overrides. A sub-composition's declaration is its own scope's, read from its scoped table.
|
|
29
29
|
const declarers = new Set();
|
|
30
30
|
if (document.documentElement?.hasAttribute("data-composition-variables")) {
|
|
31
31
|
declarers.add(document.documentElement);
|
|
32
32
|
}
|
|
33
33
|
for (const el of Array.from(document.querySelectorAll("[data-composition-variables]"))) {
|
|
34
|
-
|
|
34
|
+
if (!scopedVariablesFor(el))
|
|
35
|
+
declarers.add(el);
|
|
35
36
|
}
|
|
36
37
|
const declaredDefaults = {};
|
|
37
38
|
for (const el of declarers) {
|
|
@@ -5,4 +5,6 @@
|
|
|
5
5
|
* runtime consumer of variables (color grading, declarative bindings) so the
|
|
6
6
|
* scope chain can never diverge between channels.
|
|
7
7
|
*/
|
|
8
|
+
/** The per-instance values of the inlined sub-composition an element belongs to, if any. */
|
|
9
|
+
export declare function scopedVariablesFor(element: Element): Record<string, unknown> | undefined;
|
|
8
10
|
export declare function readVariablesForElement(element: Element): Record<string, unknown>;
|
|
@@ -5,11 +5,17 @@
|
|
|
5
5
|
* runtime consumer of variables (color grading, declarative bindings) so the
|
|
6
6
|
* scope chain can never diverge between channels.
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/** The per-instance values of the inlined sub-composition an element belongs to, if any. */
|
|
9
|
+
export function scopedVariablesFor(element) {
|
|
10
10
|
const scope = element.closest("[data-composition-id]");
|
|
11
11
|
const compositionId = scope?.getAttribute("data-composition-id")?.trim() ?? "";
|
|
12
|
-
|
|
12
|
+
return compositionId
|
|
13
|
+
? window.__hfVariablesByComp?.[compositionId]
|
|
14
|
+
: undefined;
|
|
15
|
+
}
|
|
16
|
+
export function readVariablesForElement(element) {
|
|
17
|
+
const win = window;
|
|
18
|
+
const scoped = scopedVariablesFor(element);
|
|
13
19
|
if (scoped)
|
|
14
20
|
return scoped;
|
|
15
21
|
const fromHelper = win.__hyperframes?.getVariables?.();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.209",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -64,6 +64,14 @@
|
|
|
64
64
|
"import": "./dist/removeTree.js",
|
|
65
65
|
"types": "./dist/removeTree.d.ts"
|
|
66
66
|
},
|
|
67
|
+
"./rename-retry": {
|
|
68
|
+
"import": "./dist/renameWithRetry.js",
|
|
69
|
+
"types": "./dist/renameWithRetry.d.ts"
|
|
70
|
+
},
|
|
71
|
+
"./remove-scratch": {
|
|
72
|
+
"import": "./dist/removeScratch.js",
|
|
73
|
+
"types": "./dist/removeScratch.d.ts"
|
|
74
|
+
},
|
|
67
75
|
"./project-folders": {
|
|
68
76
|
"import": "./dist/projectFolders.js",
|
|
69
77
|
"types": "./dist/projectFolders.d.ts"
|
|
@@ -417,9 +425,9 @@
|
|
|
417
425
|
"linkedom": "^0.18.12",
|
|
418
426
|
"postcss": "^8.5.8",
|
|
419
427
|
"wawoff2": "^2.0.1",
|
|
420
|
-
"@hyperframes/lint": "0.8.
|
|
421
|
-
"@hyperframes/
|
|
422
|
-
"@hyperframes/
|
|
428
|
+
"@hyperframes/lint": "0.8.209",
|
|
429
|
+
"@hyperframes/parsers": "0.8.209",
|
|
430
|
+
"@hyperframes/studio-server": "0.8.209"
|
|
423
431
|
},
|
|
424
432
|
"devDependencies": {
|
|
425
433
|
"@fontsource/archivo-black": "^5.2.8",
|