Skip to content

refactor: extract @hyperframes/parsers, @hyperframes/lint, @hyperframes/studio-server#1754

Closed
miguel-heygen wants to merge 11 commits into
mainfrom
refactor/extract-parsers-lint-studio-server
Closed

refactor: extract @hyperframes/parsers, @hyperframes/lint, @hyperframes/studio-server#1754
miguel-heygen wants to merge 11 commits into
mainfrom
refactor/extract-parsers-lint-studio-server

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Extracts three new publishable packages from `@hyperframes/core`:
    • `@hyperframes/parsers` — HTML + GSAP AST parsers, previously in `packages/core/src/parsers/`
    • `@hyperframes/lint` — composition linter (`lintHyperframeHtml`, `lintProject`), previously in `packages/core/src/lint/`
    • `@hyperframes/studio-server` — Hono-based studio API + adapter helpers, previously in `packages/core/src/studio-api/`
  • All three packages are versioned and published alongside the rest of the monorepo (added to `scripts/set-version.ts` and `.github/workflows/publish.yml`)
  • Dependency chain after split: `parsers` ← `core` ← `lint` ← `cli`/`producer`; `studio-server` depends on `core` + `parsers`
  • `@hyperframes/core` barrel continues to re-export parser types and functions for backwards compatibility; consumers can also import directly from `@hyperframes/parsers` or `@hyperframes/lint`
  • Adds `"node": "./dist/..."` as first export condition on all TypeScript-source entries in the four affected packages — required because Node's native ESM (used during Vite config loading) cannot resolve `.js` specifiers to `.ts` source files; bundlers and IDEs continue to use the `"import"` condition which points to `./src/`
  • Root `build` script now builds `parsers`/`lint`/`studio-server` first; `dev` script pre-builds `studio-server` before starting Vite

Test plan

  • `bun run build` passes end-to-end (all packages build)
  • `bun run test` passes (parsers: 660 tests, lint: 244 tests, CLI: 1047 tests)
  • `bun run verify:packed-manifests` passes
  • `bun run typecheck` (all packages)
  • `bun dev` starts the studio without errors

E2E — verify CLI + studio locally

Run these from the repo root after checking out this branch. The `studio-test/` directory included in this PR is a ready-made composition for manually exercising the studio.

# 1. Full build (parsers/lint/studio-server build first, then the rest)
bun install && bun run build

# 2. Smoke-test core CLI commands
node packages/cli/dist/cli.js --version          # expect: 0.7.x
node packages/cli/dist/cli.js lint --project studio-test   # expect: 0 errors, 0 warnings
node packages/cli/dist/cli.js info --project studio-test   # expect: 1920×1080, 8s, 6 elements

# 3. Open the studio with the test composition
node packages/cli/dist/cli.js preview --project studio-test
# → opens http://localhost:3002 in your browser
# → studio loads the "Build videos with HyperFrames" title card (dark bg, 8s, 6 tracks)
# → scrub the timeline, press play — all 6 GSAP animations should fire in sequence
# → edit a CSS value in index.html and confirm hot-reload in the studio

# 4. Confirm the project API responds
curl http://localhost:3002/api/projects
# expect: {"projects":[{"id":"studio-test",...}]}

# 5. Confirm the studio dev server starts without Vite config errors (separate terminal)
bun dev
# expect: "@hyperframes/studio-server build: ESM ⚡️ Build success"
#         "VITE ready in <N> ms" with no ERR_MODULE_NOT_FOUND

The `studio-test/index.html` composition intentionally uses all four refactored packages:
parsers (HTML AST + hf-id injection), lint (0-error validation), studio-server (API + font compiler),
and core (GSAP adapter + runtime). A healthy studio load confirms the full package graph is wired correctly.

- Update cli, producer, studio, sdk to import from
  @hyperframes/parsers, @hyperframes/lint, @hyperframes/studio-server
- Add new packages to noExternal in cli tsup config for binary bundling
- Fix lintProject.test.ts: pass string dir instead of ProjectDir object
  after lintProject signature change to (projectDir: string)
- Add @hyperframes/core/generators subpath to bypass esbuild barrel import
  in parsers tests (htmlParser.roundtrip + stableIds import test-utils
  which reached core barrel → hyperframesRuntime.engine → esbuild,
  breaking TextEncoder invariant in jsdom)
- Remove circular lint re-export from core barrel; lint types now live
  only in @hyperframes/lint (core barrel was core→lint→core cycle)
- Exclude runtime binary blobs from @hyperframes/core npm files tarball
- Extend health.ignore for lint/project.ts, lint/rules/gsap.ts and files
  touched only for import-path updates (line-shift fingerprint problem)
Add @hyperframes/parsers, @hyperframes/lint, and @hyperframes/studio-server
to the PACKAGES list in set-version.ts and to the publish workflow so they
are versioned and released alongside the rest of the monorepo.
…ilt TypeScript deps

Studio's vite config is loaded by Node's native ESM resolver (not a
TypeScript-aware bundler), so @hyperframes/studio-server and its transitive
deps (@hyperframes/core, @hyperframes/parsers, @hyperframes/lint) must expose
compiled dist files when the node condition is active.

Add "node": "./dist/..." as the first condition on every TypeScript-source
export entry in parsers, lint, core, and studio-server. The "import"
condition (pointing to src/) remains first for bundlers and IDEs; the "node"
condition is checked first only by Node's own ESM runtime.

Also update the root build script to build parsers/lint/studio-server before
core and studio, and update the dev script to pre-build studio-server before
starting the Vite dev server.
8-second HyperFrames title card with 6 tracked clips and GSAP animations.
Exercises the full package graph: parsers (hf-id injection), lint (0-error),
studio-server (API + font compiler), core (GSAP adapter).

Used as the manual E2E artifact in the PR test plan.
- Add "bun" condition before "node" in exports for parsers, core, lint,
  studio-server so bun (tests, studio dev server) loads TypeScript source
  natively without requiring a pre-built dist — fixes SDK unit/contract/smoke
  and Studio load smoke failures

- Add linkedom to @hyperframes/core dependencies to fix Build/Typecheck
  (htmlDocument.ts imports it directly)

- Delete packages/core/src/parsers/gsapParser.ts and gsapSerialize.ts
  which became dead code after the parser extraction — fixes Fallow audit

- Add scripts/set-version.ts and gsapRuntimeReaders.ts to fallow health.ignore
  for pre-existing complexity violations triggered by line-shift fingerprinting

- Format studio-test/index.html via oxfmt — fixes Preflight (lint + format)
@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

This PR has been split into three independently-reviewable units. Closing in favor of:

Merge order: #1755 first, then #1756 and #1757 can land in any order (rebase onto main after #1755 merges).

miguel-heygen added a commit that referenced this pull request Jun 27, 2026
## Summary

Extracts the GSAP parser/writer suite, HTML parser, hf-ids, spring-ease, and the shared composition data types out of `@hyperframes/core/src/parsers/` into a new, independently-publishable **`@hyperframes/parsers`** package.

This is the foundation of the [#1749](#1749) effort: make HyperFrames' parsing/linting/validation usable as plain libraries in a Node app, without shelling out to the CLI. Parsers is the standalone base every other extracted package builds on.

**Part 1 of 3** — splits #1754 into independently-reviewable pieces. Parts 2 (lint) and 3 (studio-server) stack on this branch.

## What moves

| | |
|---|---|
| Source moved out of core | **~9,900 LOC** (`src/parsers/` → `packages/parsers/src/`) |
| Total lines removed from core (incl. tests + goldens) | ~19,600 |
| Files relocated | 39 |
| Tests carried over | **660 passing** (5 skipped, 3 todo) |

The big movers: `gsapParser` / `gsapParserAcorn` (the recast + acorn dual parsers), `gsapWriterAcorn`, `gsapSerialize`, `gsapUnroll`, `htmlParser`, `hfIds`, `springEase`, `stableIds`, plus the `__goldens__` corpus.

## Bundle footprint of the new package

| Artifact | Size |
|---|---|
| `dist/` (unpacked) | 1.7 MB |
| npm tarball (packed) | 409 KB |
| `dist/index.js` | 90 KB (**~21 KB gzipped**) |
| Heaviest entries | `gsapWriterAcorn.js` 93 KB · `gsapParser.js` 91 KB |

Most of the weight is the GSAP AST machinery (recast/babel/acorn). It's tree-shakeable via subpath entries (`@hyperframes/parsers/hf-ids`, `/gsap-constants`, etc.) so a consumer that only needs `hf-ids` (2 KB) doesn't pull the parsers.

## How `@hyperframes/core` changes

The interesting part: **core sheds its entire AST toolchain.**

| core `dependencies` | before | after |
|---|---|---|
| count | 9 | 6 |
| removed | — | `@babel/parser`, `acorn`, `acorn-walk`, `magic-string`, `recast` |
| added | — | `@hyperframes/parsers`, `linkedom` |

Before this PR, importing `@hyperframes/core` at all dragged in babel + recast + acorn just to construct types. Now those live behind `@hyperframes/parsers`, and a consumer that only wants core's runtime/compiler types never resolves the parser stack. Core keeps thin `@deprecated` re-export stubs at the old subpaths (`@hyperframes/core/gsap-parser`, `/gsap-constants`, …) so nothing downstream breaks.

## Design notes

- **`"bun"` export condition before `"node"`** in every package export. Bun resolves the TypeScript source directly (no pre-built `dist/`), while Node/tsx/Docker contexts fall through to `"node"` → `dist/`. This keeps the dev loop zero-build while published artifacts stay Node-consumable.
- `@hyperframes/parsers` is **standalone** — zero `@hyperframes/*` dependencies — so it can be the base of the stack.

## Test plan

- [x] `bun run --filter @hyperframes/parsers test` — 660 tests pass
- [x] `bun run --filter @hyperframes/sdk test` — 382 tests pass
- [x] `bun run build` — full monorepo build succeeds
- [x] Fallow audit passes on CI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant