fix: escape NUL bytes in HFMASK regex (Bun blank renders) + Windows junction for studio preview links (#2140)

* fix(core): escape NUL delimiters in HFMASK mask token and restore regex

Raw 0x00 bytes in the maskInertRegions token and restore regex made
timingCompiler.ts binary to git and shipped raw NULs into dist/cli.js.
Bun's transpiler (<= 1.3.11) corrupts raw NULs in regex literals into
literal backslash-uFFFD text, so restore never matched: every masked
<style>/<script> region was dropped, the player never initialized, and
bunx renders produced blank white frames showing HFMASK tokens.

Use \u0000 escapes instead, which survive any transpile layer, and add
a byte-level regression test (behavior is identical under Node, so only
a byte check catches this).

Fixes the first half of #2139.

* fix(cli): use NTFS junctions for studio project links on Windows

linkProjectIntoStudioData called symlinkSync(dir, path, "dir"), which
needs Developer Mode or elevation on Windows, so preview and dev in
local-studio mode died with EPERM for default-configured users.
Junctions need no privilege, work for directories, and keep the live
write-back the studio depends on (a copy fallback would decouple the
studio from the real project). Covers both preview and dev, which share
the helper.

Fixes the second half of #2139.
This commit is contained in:
Miguel Ángel
2026-07-10 16:22:50 -04:00
committed by GitHub
parent 6152437d2a
commit 718c67b387
3 changed files with 14 additions and 1 deletions
@@ -1,3 +1,5 @@
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { describe, it, expect } from "vitest";
import {
compileTimingAttrs,
@@ -6,6 +8,15 @@ import {
clampDurations,
} from "./timingCompiler.js";
// Raw 0x00 bytes in the HFMASK delimiters shipped once and broke every render
// under Bun's transpiler while behaving fine under Node (issue #2139) — only a
// byte-level check catches that, so keep the delimiters as \x00 escapes.
it("source contains no raw NUL bytes", () => {
const testPath = expect.getState().testPath ?? "";
const src = readFileSync(join(dirname(testPath), "timingCompiler.ts"), "latin1");
expect(src.includes("\x00")).toBe(false);
});
describe("compileTimingAttrs", () => {
it("adds data-end when data-start and data-duration are present on a video", () => {
const html = '<video id="v1" src="a.mp4" data-start="2" data-duration="5">';
Binary file not shown.