From 405189953300ec8f5a08a6ce6d5cd649920d6732 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 17 May 2026 19:16:56 +0000 Subject: [PATCH 1/4] chore(lambda): publish-readiness for @hyperframes/aws-lambda MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Lambda adapter has been on `main` since PR #909 but its package manifest still shipped TypeScript source (`main: ./src/index.ts`, `build: tsc --noEmit`, `version: 0.0.1`) and the publish workflow didn't list it. This wires it up to publish alongside the other `@hyperframes/*` packages on the next `v*` tag. Changes: - **packages/aws-lambda/build.mjs (new)** — mirrors `packages/producer/build.mjs`: esbuild bundles four entry points (`src/index.ts`, `src/handler.ts`, `src/sdk/index.ts`, `src/cdk/index.ts`) → `dist/`, then `tsc --emitDeclarationOnly` emits .d.ts via `tsconfig.build.json`. All runtime/peer deps (@aws-sdk/*, @hyperframes/producer*, @sparticuz/chromium, aws-cdk-lib, constructs, ffmpeg-static, ffprobe-static, puppeteer-core, tar) are external so consumers resolve them through their own node_modules. - **packages/aws-lambda/tsconfig.build.json (new)** — drops the workspace `paths` overrides so `@hyperframes/producer*` resolves through node_modules to producer's already-built `dist/` types instead of pulling its full source tree into emit (which would violate `rootDir`). - **packages/aws-lambda/tsconfig.json** — keeps `noEmit: true` + workspace `paths` for fast in-place typechecks; also excludes `src/**/__fixtures__/**` so test-only helpers (fakeS3) don't leak into emitted declarations. - **packages/aws-lambda/package.json**: * version bumped 0.0.1 → 0.6.18 (matches the repo's lockstep release cadence) * main / types / exports map points at `dist/...` * files: ["dist/", "scripts/", "README.md"] (scripts/ kept whole because build-zip.ts and verify-zip-size.ts both import scripts/_formatBytes.ts) * scripts.build = `node build.mjs` - **package.json** — root `build` filter includes `aws-lambda` so `bun run build` builds it in topological order after producer. - **.github/workflows/publish.yml** — one new `publish_pkg "@hyperframes/aws-lambda" "@hyperframes/aws-lambda"` line. First publish is automatic via the `--access public` flag in `publish_pkg`; the @hyperframes scope already owns the name. Verification: bun run build # full root build green bun run verify:packed-manifests # aws-lambda passes pnpm pack packages/cli # @hyperframes/aws-lambda # rewrites workspace:* → 0.6.18 npm install -g # smoke-install still works hyperframes lambda deploy # friendly missing-package # error still fires when # aws-lambda isn't installed --- .github/workflows/publish.yml | 1 + package.json | 2 +- packages/aws-lambda/build.mjs | 62 +++++++++++++++++++++++++ packages/aws-lambda/package.json | 30 ++++++++---- packages/aws-lambda/tsconfig.build.json | 12 +++++ packages/aws-lambda/tsconfig.json | 2 +- 6 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 packages/aws-lambda/build.mjs create mode 100644 packages/aws-lambda/tsconfig.build.json diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6b01b164b..9f9fd2a82 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -130,6 +130,7 @@ jobs: publish_pkg "@hyperframes/producer" "@hyperframes/producer" publish_pkg "@hyperframes/shader-transitions" "@hyperframes/shader-transitions" publish_pkg "@hyperframes/studio" "@hyperframes/studio" + publish_pkg "@hyperframes/aws-lambda" "@hyperframes/aws-lambda" # CLI is @hyperframes/cli in the monorepo but published as unscoped "hyperframes" on npm. # Rewrite the name in package.json before publishing, then use npm publish directly diff --git a/package.json b/package.json index 916af0bde..11ac3ade0 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "type": "module", "scripts": { "dev": "bun run studio", - "build": "bun run --filter @hyperframes/core build && bun run --filter '@hyperframes/{core,engine,producer,player,studio,shader-transitions}' build && bun run --filter @hyperframes/cli build", + "build": "bun run --filter @hyperframes/core build && bun run --filter '@hyperframes/{core,engine,producer,player,studio,shader-transitions,aws-lambda}' build && bun run --filter @hyperframes/cli build", "build:producer": "bun run --filter @hyperframes/producer build", "studio": "bun run --filter @hyperframes/studio dev", "build:hyperframes-runtime": "bun run --filter @hyperframes/core build:hyperframes-runtime", diff --git a/packages/aws-lambda/build.mjs b/packages/aws-lambda/build.mjs new file mode 100644 index 000000000..f6918a248 --- /dev/null +++ b/packages/aws-lambda/build.mjs @@ -0,0 +1,62 @@ +#!/usr/bin/env node +/** + * Build script for @hyperframes/aws-lambda (public OSS package). + * + * Bundles each subpath barrel via esbuild → dist/, then emits .d.ts via tsc. + * + * Subpaths (each gets its own dist entry so adopters that import one path + * don't load the others' transitive graphs at module-load time): + * + * . (the umbrella barrel: handler + sdk + cdk types re-exported) + * ./handler (the Lambda runtime entry — what `scripts/build-zip.ts` ZIPs) + * ./sdk (client-side helpers — AWS-SDK only, no chromium/puppeteer) + * ./cdk (CDK L2 construct — aws-cdk-lib is a peer dep) + * + * All production deps and peer deps are kept external so consumers resolve + * them via their own node_modules. + */ + +import { build } from "esbuild"; +import { execSync } from "node:child_process"; +import { mkdirSync, rmSync } from "node:fs"; + +rmSync("dist", { recursive: true, force: true }); +mkdirSync("dist", { recursive: true }); + +const sharedOpts = { + bundle: true, + platform: "node", + target: "node22", + format: "esm", + minify: false, + sourcemap: true, + external: [ + "@aws-sdk/client-s3", + "@aws-sdk/client-sfn", + "@hyperframes/producer", + "@hyperframes/producer/distributed", + "@sparticuz/chromium", + "aws-cdk-lib", + "constructs", + "ffmpeg-static", + "ffprobe-static", + "puppeteer-core", + "tar", + ], +}; + +await Promise.all([ + build({ ...sharedOpts, entryPoints: ["src/index.ts"], outfile: "dist/index.js" }), + build({ ...sharedOpts, entryPoints: ["src/handler.ts"], outfile: "dist/handler.js" }), + build({ ...sharedOpts, entryPoints: ["src/sdk/index.ts"], outfile: "dist/sdk/index.js" }), + build({ ...sharedOpts, entryPoints: ["src/cdk/index.ts"], outfile: "dist/cdk/index.js" }), +]); + +// esbuild doesn't emit .d.ts. tsc does, with a build-only tsconfig that +// drops the workspace `paths` overrides so `@hyperframes/producer` resolves +// through node_modules to the sibling package's already-built `dist/` +// types instead of pulling its full source tree into emit (which would +// violate rootDir). +execSync("tsc -p tsconfig.build.json --emitDeclarationOnly", { stdio: "inherit" }); + +console.log("[Build] Complete: dist/{index,handler,sdk/index,cdk/index}.js + .d.ts"); diff --git a/packages/aws-lambda/package.json b/packages/aws-lambda/package.json index a94e09624..c027624e7 100644 --- a/packages/aws-lambda/package.json +++ b/packages/aws-lambda/package.json @@ -1,6 +1,6 @@ { "name": "@hyperframes/aws-lambda", - "version": "0.0.1", + "version": "0.6.18", "description": "AWS Lambda adapter for HyperFrames distributed rendering — handler, client-side SDK, and CDK construct.", "repository": { "type": "git", @@ -8,25 +8,37 @@ "directory": "packages/aws-lambda" }, "files": [ - "src/", + "dist/", "scripts/", "README.md" ], "type": "module", - "main": "./src/index.ts", - "types": "./src/index.ts", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { - ".": "./src/index.ts", - "./handler": "./src/handler.ts", - "./sdk": "./src/sdk/index.ts", - "./cdk": "./src/cdk/index.ts" + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./handler": { + "import": "./dist/handler.js", + "types": "./dist/handler.d.ts" + }, + "./sdk": { + "import": "./dist/sdk/index.js", + "types": "./dist/sdk/index.d.ts" + }, + "./cdk": { + "import": "./dist/cdk/index.js", + "types": "./dist/cdk/index.d.ts" + } }, "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" }, "scripts": { - "build": "tsc --noEmit", + "build": "node build.mjs", "build:zip": "tsx scripts/build-zip.ts", "probe:beginframe": "tsx scripts/probe-beginframe.ts", "probe:beginframe:docker": "docker build -f scripts/probe-beginframe.dockerfile -t hyperframes-lambda-probe:local ../.. && docker run --rm hyperframes-lambda-probe:local", diff --git a/packages/aws-lambda/tsconfig.build.json b/packages/aws-lambda/tsconfig.build.json new file mode 100644 index 000000000..c93bd474e --- /dev/null +++ b/packages/aws-lambda/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "paths": {}, + "noEmit": false, + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "sourceMap": true + } +} diff --git a/packages/aws-lambda/tsconfig.json b/packages/aws-lambda/tsconfig.json index faa4cf6d7..b15c984cb 100644 --- a/packages/aws-lambda/tsconfig.json +++ b/packages/aws-lambda/tsconfig.json @@ -15,5 +15,5 @@ } }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "src/**/*.test.ts", "scripts"] + "exclude": ["node_modules", "dist", "src/**/*.test.ts", "src/**/__fixtures__/**", "scripts"] } From a18fc04bf40552e75faa1896a47d280eba386b9f Mon Sep 17 00:00:00 2001 From: James Date: Sun, 17 May 2026 21:43:46 +0000 Subject: [PATCH 2/4] =?UTF-8?q?fix(producer):=20break=20aws-lambda?= =?UTF-8?q?=E2=86=94producer=20build=20cycle=20for=20lambda-local=20harnes?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The aws-lambda publish-readiness changes earlier in this PR moved aws-lambda's types from `./src/index.ts` → `./dist/index.d.ts`. That flipped producer's emit pass from "always works" to "needs aws-lambda built first," because producer's `regression-harness-lambda-local.ts` imports `@hyperframes/aws-lambda{,/handler}`. aws-lambda's own emit pass in turn imports `@hyperframes/producer{,/distributed}` → circular build dependency, so neither side can emit declarations in a single pass. CI's first run on this PR failed Build, Typecheck, CLI smoke, windows tests, and every perf job for this reason. Fix: extract the public surface of `regression-harness-lambda-local.ts` into a new types-only file that has no aws-lambda imports, point `regression-harness.ts` at that types file, and exclude `regression-harness-lambda-local.ts` from producer's tsconfig `include`. The implementation file is still loaded at runtime via `tsx` (lambda-local mode runs the harness through tsx, not from producer's dist/), so the runtime contract is unchanged — producer's tsc just no longer has to type-check it. - `regression-harness-lambda-local-types.ts` (new): exports `RunLambdaLocalInput` + `RunLambdaLocalRender` with zero aws-lambda imports. - `regression-harness-lambda-local.ts`: re-exports `RunLambdaLocalInput` from the new types file (so the public name stays stable for any future direct imports). - `regression-harness.ts`: drops the `typeof import("...")` trick and uses the explicit `RunLambdaLocalRender` signature for the dynamically-loaded function. - `tsconfig.json`: excludes `src/regression-harness-lambda-local.ts` so producer's emit pass never resolves `@hyperframes/aws-lambda`. Verified: bun run build # full root build green bun run verify:packed-manifests # all packages publish-safe --- .../regression-harness-lambda-local-types.ts | 37 +++++++++++++++++++ .../src/regression-harness-lambda-local.ts | 25 +------------ packages/producer/src/regression-harness.ts | 17 +++++++-- packages/producer/tsconfig.json | 8 +++- 4 files changed, 59 insertions(+), 28 deletions(-) create mode 100644 packages/producer/src/regression-harness-lambda-local-types.ts diff --git a/packages/producer/src/regression-harness-lambda-local-types.ts b/packages/producer/src/regression-harness-lambda-local-types.ts new file mode 100644 index 000000000..bb021951a --- /dev/null +++ b/packages/producer/src/regression-harness-lambda-local-types.ts @@ -0,0 +1,37 @@ +/** + * Public-facing types for `./regression-harness-lambda-local.ts`. + * + * Kept in its own file because the implementation imports + * `@hyperframes/aws-lambda`, which can't be resolved by producer's + * tsc emit pass until aws-lambda's own dist/ is built. Splitting the + * types out lets producer's regression harness reference the lambda + * adapter's shape without pulling the aws-lambda graph into producer's + * type-check pass. + */ + +/** Inputs for {@link runLambdaLocalRender}. Same contract as `runDistributedSimulatedRender`. */ +export interface RunLambdaLocalInput { + projectDir: string; + tempRoot: string; + renderedOutputPath: string; + fps: 24 | 30 | 60; + /** + * Width/height from the fixture's renderConfig. Forwarded directly to + * the Lambda event so this mode catches drift if the handler ever + * starts honouring `Config.width/height` for canvas sizing rather + * than reading the composition's `data-width`/`data-height`. The + * `distributed-simulated` mode hardcodes 1920×1080 because it + * bypasses the event-serialization boundary; lambda-local goes + * through it, which is the whole point. + */ + width: number; + height: number; + format: "mp4" | "mov" | "png-sequence"; + codec?: "h264" | "h265"; + chunkSize?: number; + maxParallelChunks?: number; + variables?: Record; +} + +/** Public signature of the dynamically-loaded `runLambdaLocalRender`. */ +export type RunLambdaLocalRender = (input: RunLambdaLocalInput) => Promise; diff --git a/packages/producer/src/regression-harness-lambda-local.ts b/packages/producer/src/regression-harness-lambda-local.ts index b731ac35d..b1c342e62 100644 --- a/packages/producer/src/regression-harness-lambda-local.ts +++ b/packages/producer/src/regression-harness-lambda-local.ts @@ -46,29 +46,8 @@ import type { SerializableDistributedRenderConfig, } from "@hyperframes/aws-lambda"; -/** Inputs for {@link runLambdaLocalRender}. Same contract as `runDistributedSimulatedRender`. */ -export interface RunLambdaLocalInput { - projectDir: string; - tempRoot: string; - renderedOutputPath: string; - fps: 24 | 30 | 60; - /** - * Width/height from the fixture's renderConfig. Forwarded directly to - * the Lambda event so this mode catches drift if the handler ever - * starts honouring `Config.width/height` for canvas sizing rather - * than reading the composition's `data-width`/`data-height`. The - * `distributed-simulated` mode hardcodes 1920×1080 because it - * bypasses the event-serialization boundary; lambda-local goes - * through it, which is the whole point. - */ - width: number; - height: number; - format: "mp4" | "mov" | "png-sequence"; - codec?: "h264" | "h265"; - chunkSize?: number; - maxParallelChunks?: number; - variables?: Record; -} +export type { RunLambdaLocalInput } from "./regression-harness-lambda-local-types.js"; +import type { RunLambdaLocalInput } from "./regression-harness-lambda-local-types.js"; const FAKE_BUCKET = "harness-lambda-local"; diff --git a/packages/producer/src/regression-harness.ts b/packages/producer/src/regression-harness.ts index 5b9d3c153..9d413a79d 100644 --- a/packages/producer/src/regression-harness.ts +++ b/packages/producer/src/regression-harness.ts @@ -37,10 +37,19 @@ import { // In Dockerfile.test the workspace copy of aws-lambda's src isn't present, // so a static import here would fail at module-load time even when // running `--mode=in-process`. Load it on demand instead. -async function loadLambdaLocalRender(): Promise< - typeof import("./regression-harness-lambda-local.js").runLambdaLocalRender -> { - const mod = await import("./regression-harness-lambda-local.js"); +// +// The signature is typed via `RunLambdaLocalRender` (in its own types-only +// file) instead of `typeof import(...)` so producer's tsc doesn't have to +// type-check the implementation. The implementation imports +// `@hyperframes/aws-lambda`, whose types come from `dist/index.d.ts` after +// aws-lambda's build runs — a chicken-and-egg with producer's tsc that +// would otherwise fail the whole-repo build. +import type { RunLambdaLocalRender } from "./regression-harness-lambda-local-types.js"; + +async function loadLambdaLocalRender(): Promise { + const mod = (await import("./regression-harness-lambda-local.js")) as { + runLambdaLocalRender: RunLambdaLocalRender; + }; return mod.runLambdaLocalRender; } diff --git a/packages/producer/tsconfig.json b/packages/producer/tsconfig.json index cbe56306a..4e876a30f 100644 --- a/packages/producer/tsconfig.json +++ b/packages/producer/tsconfig.json @@ -20,5 +20,11 @@ } }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "src/**/*.test.ts", "src/**/__test_utils__/**"] + "exclude": [ + "node_modules", + "dist", + "src/**/*.test.ts", + "src/**/__test_utils__/**", + "src/regression-harness-lambda-local.ts" + ] } From 6935eaabc44cb6b54ad9c025b1e073d71c998395 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 17 May 2026 23:06:19 +0000 Subject: [PATCH 3/4] chore(lambda): bump initial version to 0.6.20 + add to set-version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups to keep the new package in lockstep with the rest of the @hyperframes/* release cadence from day one: - Bump packages/aws-lambda/package.json version 0.6.18 → 0.6.20 so it matches what main released while this PR was in review. Without this, the package would land below the rest of the lockstep and the next release-bump would jump aws-lambda from 0.6.18 → 0.6.21 in one step. - Add packages/aws-lambda to PACKAGES in scripts/set-version.ts so the next `chore: release vX.Y.Z` commit bumps aws-lambda alongside the other publishable packages. Without this, set-version silently skips aws-lambda — package.json stays frozen, pnpm publish would re-publish the same version on every release, and the npm-view precheck in publish.yml would skip-with-success and never actually push a new version of the package. --- packages/aws-lambda/package.json | 2 +- scripts/set-version.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/aws-lambda/package.json b/packages/aws-lambda/package.json index c027624e7..a14776d08 100644 --- a/packages/aws-lambda/package.json +++ b/packages/aws-lambda/package.json @@ -1,6 +1,6 @@ { "name": "@hyperframes/aws-lambda", - "version": "0.6.18", + "version": "0.6.20", "description": "AWS Lambda adapter for HyperFrames distributed rendering — handler, client-side SDK, and CDK construct.", "repository": { "type": "git", diff --git a/scripts/set-version.ts b/scripts/set-version.ts index b7d45f92a..a66f1f9b8 100644 --- a/scripts/set-version.ts +++ b/scripts/set-version.ts @@ -25,6 +25,7 @@ const PACKAGES = [ "packages/shader-transitions", "packages/studio", "packages/cli", + "packages/aws-lambda", ]; const ROOT = join(import.meta.dirname, ".."); From 187dfd5b6cb3a1cbfba17262d66f8ab0c1ef7f76 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 17 May 2026 23:11:45 +0000 Subject: [PATCH 4/4] fix(producer): indirect lambda-local import path so tsc can't resolve it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tsconfig `exclude` list isn't enough to keep producer's tsc emit pass from pulling `regression-harness-lambda-local.ts` (and its `@hyperframes/aws-lambda` static imports) into the program — tsc still statically resolves the path in `await import("./regression-harness-lambda-local.js")` from `regression-harness.ts`, walks into the excluded file, and fails on the missing aws-lambda type declarations. Reproduction (clean workspace, no aws-lambda dist yet, mirrors CI): rm -rf packages/{aws-lambda,producer,core}/dist bun run build # @hyperframes/producer build: src/regression-harness-lambda-local.ts(36,70): # error TS2307: Cannot find module '@hyperframes/aws-lambda' or its # corresponding type declarations. Fix: route the dynamic import path through a top-level string constant so tsc can't statically resolve the target. tsc keeps the type-only imports (`RunLambdaLocalRender` from the no-aws-lambda types file) and treats the dynamic-import target as opaque. `tsx` resolves the path normally at runtime, so `--mode=lambda-local` is unchanged. --- packages/producer/src/regression-harness.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/producer/src/regression-harness.ts b/packages/producer/src/regression-harness.ts index 9d413a79d..015cae007 100644 --- a/packages/producer/src/regression-harness.ts +++ b/packages/producer/src/regression-harness.ts @@ -44,10 +44,18 @@ import { // `@hyperframes/aws-lambda`, whose types come from `dist/index.d.ts` after // aws-lambda's build runs — a chicken-and-egg with producer's tsc that // would otherwise fail the whole-repo build. +// +// The dynamic import path is indirected through a variable so tsc can't +// statically resolve the target file. Without this indirection tsc still +// pulls `regression-harness-lambda-local.ts` (and its `@hyperframes/aws-lambda` +// imports) into the program even though the tsconfig `exclude` list +// nominally hides it. `tsx` resolves the path normally at runtime. import type { RunLambdaLocalRender } from "./regression-harness-lambda-local-types.js"; +const LAMBDA_LOCAL_MODULE = "./regression-harness-lambda-local.js"; + async function loadLambdaLocalRender(): Promise { - const mod = (await import("./regression-harness-lambda-local.js")) as { + const mod = (await import(LAMBDA_LOCAL_MODULE)) as { runLambdaLocalRender: RunLambdaLocalRender; }; return mod.runLambdaLocalRender;