From 2ed70a6838926d54f5993b3959f5f6eeed545eac Mon Sep 17 00:00:00 2001 From: "hanhan.ghh" Date: Fri, 30 Oct 2026 03:47:05 -0700 Subject: [PATCH 1/3] fix(ci): configure runtime paths after runner startup --- .github/workflows/ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e152fbc..5e23a43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,14 +13,16 @@ jobs: node: [22, 24] runs-on: ${{ matrix.os }} timeout-minutes: 30 - env: - TAPVID_VIDEO_HOME: ${{ runner.temp }}/tapvid test home - PLAYWRIGHT_BROWSERS_PATH: ${{ runner.temp }}/tapvid browsers steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 with: node-version: ${{ matrix.node }} + - name: Configure isolated runtime paths + shell: bash + run: | + echo "TAPVID_VIDEO_HOME=$RUNNER_TEMP/tapvid test home" >> "$GITHUB_ENV" + echo "PLAYWRIGHT_BROWSERS_PATH=$RUNNER_TEMP/tapvid browsers" >> "$GITHUB_ENV" - name: Install FFmpeg (macOS) if: runner.os == 'macOS' run: brew list ffmpeg >/dev/null 2>&1 || brew install ffmpeg From 514f107063e237e7e15ed6e0d5603d00afbdf020 Mon Sep 17 00:00:00 2001 From: "hanhan.ghh" Date: Fri, 30 Oct 2026 03:54:34 -0700 Subject: [PATCH 2/3] test(remix): isolate offline review estimates and diagnose standalone copies --- tests/remix/review.test.mjs | 19 ++++++++++++++----- tests/remix/standalone.test.mjs | 3 ++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/remix/review.test.mjs b/tests/remix/review.test.mjs index 35e7a3e..a89055f 100644 --- a/tests/remix/review.test.mjs +++ b/tests/remix/review.test.mjs @@ -1,11 +1,11 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; -import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; +import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; -import { readJson, requireRuntime, writeJson } from '../../skills/remix/scripts/lib/common.mjs'; +import { RUNTIME, readJson, requireRuntime, writeJson } from '../../skills/remix/scripts/lib/common.mjs'; import { slotRequestId, speechRequestId } from '../../skills/remix/scripts/lib/media/plan.mjs'; import { loadReplica } from '../../skills/remix/scripts/lib/replica.mjs'; import { assetGroups, fidelity, finalCuts, knownLimits, placeSfx, sfxKinds } from '../../skills/remix/scripts/review.mjs'; @@ -186,20 +186,29 @@ test("template.html's bridge translates every overlay code project.mjs can emit" for (const p of parts) assert.equal(L[`part:${p}`].en, p, 'English part names show as written'); }); -test('review data carries codes, not Chinese: kinds, modes, units, notes, flags, BGM and asset labels', { skip, timeout: 120_000 }, () => { +test('review data carries codes, not Chinese: kinds, modes, units, notes, flags, BGM and asset labels', { skip, timeout: 120_000 }, (t) => { + // Estimation uses static provider prices, but routing still requires a configured key. + // Keep this offline fixture independent of personal credentials, routes and model caches. + const home = tmp(); + t.after(() => rmSync(home, { recursive: true, force: true })); + symlinkSync(RUNTIME, join(home, 'runtime'), process.platform === 'win32' ? 'junction' : 'dir'); + const env = { ...process.env, TAPVID_VIDEO_HOME: home, TAPVID_API_KEY: 'offline-review-test', TAPVID_API_BASE: 'http://127.0.0.1:1' }; const clone = makeClone(); const dir = makeReplica(clone, { edit: (r) => { r.audio.mode = 'narration'; r.audio.bgm = { brief: '', candidates: [{ path: 'bgm/a.mp3' }], selected: 0, path: null, beats: null }; + r.scenes[0].media.cover.provider = 'tapvid'; + r.scenes[0].media.cover.model = 'seedance-2.0-fast'; r.scenes[0].narration = 'Meet Nova, the notes app that writes back to you, sorts your week, drafts the replies you owe and reminds you of every promise you made in a meeting.'; }, }); touch(join(dir, 'bgm', 'a.mp3')); fakeTasks(dir); touch(join(dir, 'out', 'tapvid.mp4')); - execFileSync(process.execPath, [join(SCRIPTS, 'media.mjs'), 'plan', join(dir, 'replica.json')], { cwd: dir, stdio: 'ignore' }); - const d = pageData(node(clone, 'demo')); + execFileSync(process.execPath, [join(SCRIPTS, 'media.mjs'), 'plan', join(dir, 'replica.json')], { cwd: dir, env, stdio: 'ignore' }); + const page = execFileSync(process.execPath, [join(SCRIPTS, 'review.mjs'), clone, 'demo', '--no-open'], { env, encoding: 'utf8' }).trim(); + const d = pageData(page); const dict = pageDict(); assert.equal(CJK.test(JSON.stringify(d)), false, 'no Chinese in the review data'); assert.deepEqual(d.preview.bgm[0].title, { index: 1 }); diff --git a/tests/remix/standalone.test.mjs b/tests/remix/standalone.test.mjs index f9017b6..2df88ed 100644 --- a/tests/remix/standalone.test.mjs +++ b/tests/remix/standalone.test.mjs @@ -1,6 +1,6 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; -import { cpSync, mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'node:fs'; +import { cpSync, existsSync, mkdtempSync, readdirSync, rmSync, mkdirSync, writeFileSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; @@ -14,6 +14,7 @@ test('a standalone remix install replays providers, validates templates, and loa const home = join(dir, 'user data'); cpSync(new URL('../../skills/remix/', import.meta.url), skill, { recursive: true }); cpSync(new URL('./fixtures/sample-clone/', import.meta.url), join(dir, 'clone sample'), { recursive: true }); + assert.ok(existsSync(join(skill, 'scripts', 'media.mjs')), `standalone copy has no entry script: ${JSON.stringify({ root: readdirSync(dir), skill: readdirSync(skill, { recursive: true }).slice(0, 15) })}`); const invoke = (script, ...args) => { const r = spawnSync(process.execPath, [join(skill, 'scripts', script), ...args], { cwd: dir, env: { ...process.env, TAPVID_VIDEO_HOME: home }, encoding: 'utf8', timeout: 30000, From fe0e8c9f7cad2af60bf4c553e307d10e7f3fdb60 Mon Sep 17 00:00:00 2001 From: "hanhan.ghh" Date: Fri, 30 Oct 2026 03:58:41 -0700 Subject: [PATCH 3/3] fix(ci): isolate recording tests and copy Unicode fixtures portably --- .github/workflows/ci.yml | 4 +++- tests/remix/standalone.test.mjs | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e23a43..c3ba900 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,4 +34,6 @@ jobs: - name: Verify reusable installation run: node skills/remix/scripts/ensure-runtime.mjs - name: Test including render and standalone installation - run: npm run test:full + # Browser recording tests compare real-time events with captured frames. + # Run test files sequentially so competing renders do not starve the recorder. + run: npm run test:full -- --test-concurrency=1 diff --git a/tests/remix/standalone.test.mjs b/tests/remix/standalone.test.mjs index 2df88ed..38f7ced 100644 --- a/tests/remix/standalone.test.mjs +++ b/tests/remix/standalone.test.mjs @@ -1,20 +1,22 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; -import { cpSync, existsSync, mkdtempSync, readdirSync, rmSync, mkdirSync, writeFileSync } from 'node:fs'; +import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'node:fs'; +import { cp } from 'node:fs/promises'; import { spawnSync } from 'node:child_process'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; // Catches dependencies on repository-root assets, cwd, or package metadata when a // user installs only this skill. Spaces and non-ASCII paths must work as well. -test('a standalone remix install replays providers, validates templates, and loads user extensions', (t) => { +test('a standalone remix install replays providers, validates templates, and loads user extensions', async (t) => { const dir = mkdtempSync(join(tmpdir(), 'tapvid standalone ')); t.after(() => rmSync(dir, { recursive: true, force: true })); const skill = join(dir, '安装 remix'); const home = join(dir, 'user data'); - cpSync(new URL('../../skills/remix/', import.meta.url), skill, { recursive: true }); - cpSync(new URL('./fixtures/sample-clone/', import.meta.url), join(dir, 'clone sample'), { recursive: true }); - assert.ok(existsSync(join(skill, 'scripts', 'media.mjs')), `standalone copy has no entry script: ${JSON.stringify({ root: readdirSync(dir), skill: readdirSync(skill, { recursive: true }).slice(0, 15) })}`); + // Node 22's native cpSync path mishandles Unicode destinations on Windows. + // The asynchronous copy keeps the non-ASCII installation path covered there too. + await cp(new URL('../../skills/remix/', import.meta.url), skill, { recursive: true }); + await cp(new URL('./fixtures/sample-clone/', import.meta.url), join(dir, 'clone sample'), { recursive: true }); const invoke = (script, ...args) => { const r = spawnSync(process.execPath, [join(skill, 'scripts', script), ...args], { cwd: dir, env: { ...process.env, TAPVID_VIDEO_HOME: home }, encoding: 'utf8', timeout: 30000,