fix(cli): ship player + slideshow bundles so present/play work from npm#1706
Conversation
`present` and `play` render compositions in the standalone browser player, resolving the player/slideshow IIFE bundles via resolvePlayerPath / resolveSlideshowPath. Those resolvers look for the bundles alongside the built CLI (dist/hyperframes-player.global.js, dist/hyperframes-slideshow.global.js), but build-copy.mjs never staged them into dist/. The remaining candidate paths are monorepo-dev only, so an npm install has nothing to resolve. Result: `npx hyperframes present` always failed with "@hyperframes/player not found", forcing users to run the presenter from a monorepo checkout. Copy both player globals from packages/player/dist into the CLI dist during build:copy (existsSync-guarded + warn, matching the surrounding pattern). The runtime bundle is already handled by build:runtime. Verified: the globals now appear in `npm pack`, and `node dist/cli.js present` starts without the player-not-found error. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
miga-heygen
left a comment
There was a problem hiding this comment.
LGTM. Tight, well-scoped fix for a real usability pain point.
The change is exactly right: resolvePlayerPath / resolveSlideshowPath in compositionServer.ts already look for the globals alongside the built CLI (dist/<name>.global.js), but build-copy.mjs never staged them there — so npx hyperframes present always fails from an npm install. This plugs that gap.
Good patterns:
existsSync-guarded with warn-and-skip matches the existingmotionSampleScriptpattern directly above. A missing bundle at build time downgrades to a warning instead of failing the whole build — correct, since a contributor who hasn't built the player package might still want to iterate on other CLI changes.- Source paths are correct:
hyperframes-player.global.jsatpackages/player/dist/andhyperframes-slideshow.global.jsatpackages/player/dist/slideshow/— these match the player package's build output structure. - The flat
dist/destination matches whatresolvePlayerPath/resolveSlideshowPathexpect for the non-monorepo (npm install) resolution path.
No issues. CI still running.
Review by Miga
vanceingalls
left a comment
There was a problem hiding this comment.
Small, surgical build-script fix: stages hyperframes-player.global.js and hyperframes-slideshow.global.js into packages/cli/dist/ during build:copy so npx hyperframes present / play actually find the player when the CLI is installed from npm. Production delta is 21 lines, single file (packages/cli/scripts/build-copy.mjs), pattern matches the surrounding existsSync-guarded warn-and-skip blocks. Customer-facing pain point (the "BIG GOTCHA" people writing decks had to work around).
Verdict: LGTM
Findings (numbered, with severity tag):
None. Read both the resolver and the build script at HEAD and the contract is exact:
resolvePlayerPathatpackages/cli/src/utils/compositionServer.ts:35-43looks forhyperframes-player.global.jsatresolve(d, "..", "..", "..", "player", "dist", ...)(monorepo dev), thenresolve(d, "hyperframes-player.global.js")andresolve(d, "..", "hyperframes-player.global.js")(alongside CLI dist root or one level up — coversdist/cli.jsanddist/commands/*.jscallsites).resolveSlideshowPath(compositionServer.ts:45-53) — same three-candidate shape for the slideshow global.- The fix copies to flat
DIST = packages/cli/dist/root, which hits candidates 2 and 3 in both resolvers from any callsite underdist/. - Source paths (
packages/player/dist/hyperframes-player.global.jsandpackages/player/dist/slideshow/hyperframes-slideshow.global.js) match the player'spackage.jsonexports[*].scriptentries exactly.
Verified (band-aid sweep clean):
- Pattern #1 duplicate guards —
existsSync-loop reuses the same warn-and-skip shape as the skills loop above (build-copy.mjs:84-90) and the standalonemotionSampleScriptblock (build-copy.mjs:107-109). No new helper needed at this scale. - Pattern #3 silent scope gap — PR body claims
presentANDplay; grep confirms onlypresent.tsandplay.tscall the resolvers (present.ts:77-78,play.ts:99).preview.tsdoes not use them, so it's correctly out of scope. No third command silently affected. - Pattern #6 title/comment vs code drift — title says "ship player + slideshow bundles so present/play work from npm"; diff ships exactly those two bundles, used by exactly those two commands. The inline comment block re-states the same explanation.
- Pattern #10 decorative gate — the gate (
existsSync(src)) has a real populate path: source files come frompackages/player/dist/, which is built before CLI in the rootpackage.jsonbuildscript (bun run --filter '...,player,...' build && bun run --filter @hyperframes/cli build). In the release pipeline the sources will always exist; the skip-with-warn only fires for a contributor iterating on CLI without prebuilding player — same intentional behavior as the existing skill/script blocks. - Build pipeline ordering — root
package.jsonbuilds@hyperframes/playerbefore@hyperframes/cli, sobuild:copyruns after both, finds the player globals, and copies them. Confirmed. - npm-pack inclusion — CLI
package.jsonfiles: ["dist"]ships everything underdist/, so the copied globals will be in the published tarball. - Cross-PR family non-regression — orthogonal to HF #1671 (skills install path), #1670 (engine fast-screenshot), #1702/#1703 (runtime + engine fixes). None touch
build-copy.mjsor the resolver path. - CLI smoke coverage —
CLI smoke (required),CLI: npx shim (ubuntu/macos/windows),Smoke: global install,SDK: unit + contract + smoke,Studio: load smokeall SUCCESS on this SHA — exactly the right cohort for a dist-staging fix.
CI state at HEAD 109f8f5a27d3f9a618fe813d3883df5b587a7d31: All required checks green (CI Build/Test/Typecheck/Lint/Fallow/Format, CLI smoke required, CLI npx shim ubuntu/macos/windows, Smoke global install, preview-regression, regression, player-perf, SDK + Studio smoke). Two orthogonal Windows render-verification jobs still IN_PROGRESS (Render / Tests on windows-latest) — unrelated to a dist/ copy fix.
Prior reviewer state: Miga LGTM (COMMENTED, 2026-06-24T23:45:49Z) + Miguel APPROVED (2026-06-24T23:48:48Z, no body — silent stamp). Converged. Miga's coverage focused on the in-component contract (resolver paths, existsSync pattern parity, flat-dist destination correctness). This review adds the complementary cross-package read: scope-vs-claim (present+play only, preview correctly untouched), root build-orchestration ordering (player builds before CLI), files: ["dist"] ship-list inclusion, and CI-cohort verification (all CLI smoke variants green).
Review by Via
Problem
npx hyperframes present(andplay) always fail with:The only workaround has been to run the presenter from a monorepo checkout — which is exactly the "BIG GOTCHA" people writing decks have had to document and pass around.
Root cause
present/playrender compositions in the standalone browser player and resolve the player + slideshow IIFE bundles at runtime viaresolvePlayerPath/resolveSlideshowPath(packages/cli/src/utils/compositionServer.ts). Those resolvers check, in order:../../../player/dist/...)dist/hyperframes-player.global.js,dist/hyperframes-slideshow.global.js)In an npm install only #2 can ever hit — but
build-copy.mjsnever staged those two bundles intodist/, so the published package shipspresent/playwith no player to load. (The runtime bundle is already handled bybuild:runtime, which is why only the player/slideshow globals were missing.)Fix
Copy both player globals from
packages/player/distinto the CLIdist/duringbuild:copy,existsSync-guarded with a warn-and-skip, matching the surrounding pattern in the file.Verification
npm pack --dry-runnow includesdist/hyperframes-player.global.jsanddist/hyperframes-slideshow.global.js.node dist/cli.js present <deck>starts the presenter cleanly (HTTP 200) with no "@hyperframes/player not found".🤖 Generated with Claude Code