Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
43c9d30
chore(arcade): add rebuild and GitHub Release pack scripts
valeriangalliat Aug 17, 2026
4bbef99
fix(arcade): pin Release publish to origin repo and enable pnpm
valeriangalliat Aug 17, 2026
69f4141
chore(arcade): include clamp-embedded-media in rebuild manifest
valeriangalliat Aug 18, 2026
acb88d1
chore(arcade): track upstream issue/PR links in patches.json
valeriangalliat Aug 18, 2026
f3db36b
chore(arcade): link clamp-embedded-media to upstream #3331 / #3332
valeriangalliat Aug 18, 2026
06a5f47
chore(arcade): include host-window-bare-video in rebuild manifest
valeriangalliat Aug 18, 2026
cdc7eea
chore(arcade): drop clamp-embedded-media from rebuild (superseded)
valeriangalliat Aug 18, 2026
5e85800
chore(arcade): link host-window-bare-video to upstream #3335
valeriangalliat Aug 18, 2026
4493d2b
chore(arcade): include probe-seek-swallow in rebuild manifest
valeriangalliat Aug 19, 2026
ca8a0ab
chore(arcade): include jump-seek-css-settle in rebuild manifest
valeriangalliat Aug 19, 2026
ee9c6f1
chore(arcade): drop host-window-bare-video from rebuild (already on tip)
valeriangalliat Aug 19, 2026
21546b7
chore(arcade): link visibility-scrub to upstream #3360
valeriangalliat Aug 20, 2026
05e5643
chore(arcade): link data-start-idref to upstream #3361
valeriangalliat Aug 20, 2026
3ab1185
chore(arcade): drop visibility-scrub from rebuild (merged upstream #3…
valeriangalliat Aug 24, 2026
e292031
chore(arcade): drop data-start-idref from rebuild (merged upstream #3…
valeriangalliat Aug 26, 2026
f933913
chore(arcade): drop video-source from rebuild (merged upstream #3238)
valeriangalliat Aug 26, 2026
f32b960
chore(arcade): include runtime-src-duplicate-ids in rebuild manifest
valeriangalliat Aug 26, 2026
39641cb
chore(arcade): drop runtime-src-duplicate-ids from rebuild (merged up…
valeriangalliat Aug 31, 2026
8c3a45f
chore(arcade): drop player-xss from rebuild (preexisting upstream #3472)
valeriangalliat Aug 31, 2026
5ae79d4
fix(arcade): pack GitHub Release tarballs with pnpm, not npm
valeriangalliat Aug 31, 2026
52da1d8
fix(arcade): keep arcade tracking origin/arcade after rebuild
valeriangalliat Aug 31, 2026
bb6b14a
chore(arcade): drop GitHub Release tarball publish
valeriangalliat Aug 31, 2026
9639121
chore(arcade): include discover-host-window in rebuild manifest
valeriangalliat Sep 2, 2026
63a03a9
chore(arcade): include discover-video-opacity in rebuild manifest
valeriangalliat Sep 2, 2026
be2ef22
feat(arcade): let rebuild pin the integration base
valeriangalliat Sep 2, 2026
93155d2
chore(arcade): include render-settle-pass in rebuild manifest
valeriangalliat Sep 3, 2026
588b802
docs(arcade): record sync tag layout after each rebuild
valeriangalliat Sep 14, 2026
83f04fd
docs(arcade): split shared hunks; do not resolve on arcade
valeriangalliat Sep 14, 2026
1631bda
chore(arcade): register patch/extract-hold-past-eof
valeriangalliat Sep 15, 2026
aad3d11
chore(arcade): keep patch/render-settle-pass local
valeriangalliat Sep 15, 2026
19d3789
chore(arcade): link patch/extract-hold-past-eof to upstream #3958 / #…
valeriangalliat Sep 15, 2026
f1bdfd1
chore(arcade): include host-duration-bound in rebuild manifest
valeriangalliat Sep 15, 2026
c667903
chore(arcade): link host-duration-bound to upstream #3964 / #3965
valeriangalliat Sep 15, 2026
b79e57d
chore(arcade): drop extract-hold-past-eof from rebuild (upstream #3962)
valeriangalliat Sep 15, 2026
c69c737
chore(arcade): drop host-duration-bound from rebuild (upstream #3975)
valeriangalliat Sep 23, 2026
87d8c2a
chore(arcade): add patch/vp9-alpha-probe to the patch list
valeriangalliat Sep 29, 2026
7d22454
chore(arcade): carry patch/cloud-run-chrome-152
valeriangalliat Oct 5, 2026
3d96929
chore(arcade): drop playback-start-nested from rebuild (upstream #541…
valeriangalliat Oct 24, 2026
81a6e73
chore(arcade): drop patch/cloud-run-chrome-152, upstream #5338 shippe…
valeriangalliat Oct 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions scripts/arcade/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Arcade fork maintenance

Public fork of Heygen Hyperframes with Arcade keep-local / pending-contrib patches.

## Branches

| Branch | Role |
| ---------------------- | ------------------------------------------------------------------------------------ |
| `main` | Upstream mirror only — no Arcade commits |
| `patch/<slug>` | One patch; open upstream PRs from these |
| `arcade` | Disposable integration: `base` (`main`) + cherry-picks from `patches.json` |
| `patch/arcade-tooling` | This tooling (first entry in the manifest; never upstreamed) |

## Rebuild `arcade`

```bash
git fetch origin upstream
git checkout patch/arcade-tooling
node scripts/arcade/rebuild.mjs --yes # onto patches.json `base` (main)
node scripts/arcade/rebuild.mjs --yes --base upstream/main # onto latest upstream
node scripts/arcade/rebuild.mjs --yes --push # force-with-lease origin/arcade
```

Conflict → abort names the patch. Do not resolve on `arcade`. If the patch itself is clean on `main`, two patches share a hunk: keep them both on `main` and split the file (one owner, the other a new module). `createHostWindowMapper` is `packages/producer/src/services/hostWindowMapper.ts` so it does not rewrite `renderMediaCollector.ts`. If both must edit the same function, add a keep-local join patch.

Rebase that `patch/*` onto the same `base` only when *that* patch failed on `main`, then re-run.

## Tags

Version the delta against `main`, not a merge of `main` into the patch. After a rebuild, tag this date (`YYYY-MM-DD`):

- `sync/<date>/base` → `main`
- `sync/<date>/<patch>` → each `patch/*` tip (`playback-start-nested`, not `patch/playback-start-nested`)
- `sync/<date>/arcade` → `arcade`

```
git range-diff sync/2026-09-09/base..sync/2026-09-09/playback-start-nested \
sync/<date>/base..sync/<date>/playback-start-nested
```

Push `refs/tags/sync/<date>/*`. Do not move a tag already on origin. If `.tmp` is mid-rebase, tag last week's tips from a clean sibling checkout (`../hyperframes-next`), not from a dirty worktree.

When an upstream PR merges, delete its row from `patches.json` (and optionally the branch).

Each `patches` entry is `{ branch, issue?, pr?, upstreamPr?, keepLocal? }`:

| Field | Meaning |
| ------------ | ------------------------------------------------- |
| `issue` | Upstream issue we filed |
| `pr` | Our PR (usually from this `patch/*` branch) |
| `upstreamPr` | Their PR fixing our issue (when not `pr`) |
| `keepLocal` | `true` — intentional Arcade-only; do not upstream |
35 changes: 35 additions & 0 deletions scripts/arcade/patches.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"upstream": "upstream/main",
"base": "main",
"integrationBranch": "arcade",
"patches": [
{
"branch": "patch/arcade-tooling",
"keepLocal": true
},
{
"branch": "patch/probe-seek-swallow",
"keepLocal": true
},
{
"branch": "patch/jump-seek-css-settle",
"keepLocal": true
},
{
"branch": "patch/discover-host-window",
"keepLocal": true
},
{
"branch": "patch/discover-video-opacity",
"keepLocal": true
},
{
"branch": "patch/render-settle-pass",
"keepLocal": true
},
{
"branch": "patch/vp9-alpha-probe",
"keepLocal": true
}
]
}
184 changes: 184 additions & 0 deletions scripts/arcade/rebuild.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#!/usr/bin/env node
/**
* Rebuild the disposable `arcade` integration branch:
* reset to `base` (--base, else patches.json `base`, else `upstream`), then cherry-pick
* each patch tip in patches.json order.
*
* Bootstrap (scripts may not be on current checkout):
* git fetch origin upstream
* git checkout patch/arcade-tooling
* node scripts/arcade/rebuild.mjs --yes
*
* On conflict: script aborts and names the failing patch — rebase that
* patch/* onto the same base, then re-run.
*
* Usage:
* node scripts/arcade/rebuild.mjs --yes
* node scripts/arcade/rebuild.mjs --yes --base main
* node scripts/arcade/rebuild.mjs --yes --base upstream/main
* node scripts/arcade/rebuild.mjs --yes --push
* node scripts/arcade/rebuild.mjs --dry-run
*/

import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const ROOT = join(dirname(fileURLToPath(import.meta.url)), "../..");
const MANIFEST_PATH = join(dirname(fileURLToPath(import.meta.url)), "patches.json");

const argv = process.argv.slice(2);
const args = new Set(argv);
const dryRun = args.has("--dry-run");
const push = args.has("--push");
const yes = args.has("--yes");

function takeArg(name) {
const i = argv.indexOf(name);
if (i === -1) return null;
const value = argv[i + 1];
if (!value || value.startsWith("-")) die(`${name} requires a git ref`);
return value;
}

function git(gitArgs, opts = {}) {
return execFileSync("git", gitArgs, {
cwd: ROOT,
encoding: "utf8",
stdio: opts.stdio ?? ["ignore", "pipe", "pipe"],
...opts,
}).trim();
}

function gitOk(gitArgs) {
try {
git(gitArgs);
return true;
} catch {
return false;
}
}

function die(message, code = 1) {
console.error(message);
process.exit(code);
}

const manifest = JSON.parse(readFileSync(MANIFEST_PATH, "utf8"));
const upstream = typeof manifest.upstream === "string" ? manifest.upstream : null;
const base = takeArg("--base") || (typeof manifest.base === "string" ? manifest.base : null) || upstream;
const branch = manifest.integrationBranch;
if (!base) die("patches.json: set base or upstream, or pass --base <ref>");
if (!Array.isArray(manifest.patches) || manifest.patches.length === 0) {
die("patches.json: patches must be a non-empty array");
}
const patches = manifest.patches.map((p) => {
if (typeof p === "string") return p;
if (p && typeof p.branch === "string") return p.branch;
die("patches.json: each patch must be a string or { branch }");
});

if (!yes && !dryRun) {
die("Refusing to reset arcade without --yes (or pass --dry-run).");
}

const status = git(["status", "--porcelain"]);
if (status) {
die(`Working tree dirty:\n${status}`);
}

if (!gitOk(["rev-parse", "--verify", base])) {
die(`Missing ${base}. Run: git fetch origin upstream`);
}

for (const patch of patches) {
if (!gitOk(["rev-parse", "--verify", patch])) {
die(`Missing patch ref ${patch}. Fetch or create it before rebuilding.`);
}
}

const baseSha = git(["rev-parse", base]);
console.log(`base: ${base} @ ${baseSha.slice(0, 12)}`);
if (upstream && upstream !== base && gitOk(["rev-parse", "--verify", upstream])) {
console.log(`upstream: ${upstream} @ ${git(["rev-parse", upstream]).slice(0, 12)}`);
}
console.log(`branch: ${branch}`);
console.log(`patches: ${patches.length}`);
for (const patch of patches) {
const tip = git(["rev-parse", patch]);
const commits = git(["rev-list", "--reverse", `${base}..${patch}`])
.split("\n")
.filter(Boolean);
console.log(
` - ${patch} (${commits.length} commit${commits.length === 1 ? "" : "s"} @ ${tip.slice(0, 12)})`,
);
if (commits.length === 0) {
console.warn(
` warn: no commits in ${base}..${patch} — already in base or empty; will skip`,
);
}
}

if (dryRun) {
console.log("dry-run: no refs updated");
process.exit(0);
}

git(["checkout", "-B", branch, base]);
// -B from a remote-tracking start-point inherits that upstream.
// arcade is a fork integration branch — it should track origin/arcade.
if (gitOk(["rev-parse", "--verify", `origin/${branch}`])) {
git(["branch", "--set-upstream-to", `origin/${branch}`]);
}
console.log(`reset ${branch} → ${base}`);

for (const patch of patches) {
const commits = git(["rev-list", "--reverse", `${base}..${patch}`])
.split("\n")
.filter(Boolean);
if (commits.length === 0) {
console.log(`skip ${patch} (empty range)`);
continue;
}

console.log(`cherry-pick ${patch} (${commits.length})`);
try {
execFileSync("git", ["cherry-pick", ...commits], {
cwd: ROOT,
encoding: "utf8",
stdio: "inherit",
});
} catch {
console.error(`\nConflict while cherry-picking ${patch}.`);
console.error(`Fix that patch branch onto ${base}, then re-run:`);
console.error(` git checkout ${patch}`);
console.error(` git rebase ${base}`);
console.error(` # resolve, then:`);
console.error(` git checkout patch/arcade-tooling`);
console.error(` node scripts/arcade/rebuild.mjs --yes`);
console.error(`\nAborting cherry-pick and leaving ${branch} mid-rebuild.`);
try {
execFileSync("git", ["cherry-pick", "--abort"], { cwd: ROOT, stdio: "inherit" });
} catch {
// already aborted or nothing to abort
}
process.exit(1);
}
}

const tip = git(["rev-parse", "HEAD"]);
console.log(`\n${branch} tip: ${tip}`);
console.log(`commits on ${branch} not in ${base}:`);
console.log(git(["log", "--oneline", `${base}..HEAD`]) || "(none)");

if (push) {
execFileSync("git", ["push", "--force-with-lease", "origin", branch], {
cwd: ROOT,
stdio: "inherit",
});
git(["branch", "--set-upstream-to", `origin/${branch}`]);
console.log(`pushed origin/${branch}`);
} else {
console.log(`\nPush when ready:\n git push --force-with-lease origin ${branch}`);
}
Loading