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..a14776d08 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.20", "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"] } 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..015cae007 100644 --- a/packages/producer/src/regression-harness.ts +++ b/packages/producer/src/regression-harness.ts @@ -37,10 +37,27 @@ 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. +// +// 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(LAMBDA_LOCAL_MODULE)) 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" + ] } 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, "..");